fix(approvals): fix sizing issue, multiple ui bugs in details window, separate scrollable timesheet from static one

This commit is contained in:
Nicolas Drolet 2026-01-06 15:10:27 -05:00
parent 07b52c854f
commit ec466bf6f2
4 changed files with 397 additions and 331 deletions

View File

@ -0,0 +1,102 @@
<script
setup
lang="ts"
>
import ShiftList from 'src/modules/timesheets/components/shift-list.vue';
import { useQuasar } from 'quasar';
import { computed, ref, watch } from 'vue';
import { useTimesheetStore } from 'src/stores/timesheet-store';
import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api';
import type { QScrollArea, TouchSwipeValue } from 'quasar';
const q = useQuasar();
const timesheet_store = useTimesheetStore();
const timesheet_api = useTimesheetApi();
const { mode = 'normal' } = defineProps<{
mode: 'normal' | 'approval';
}>();
const mobile_animation_direction = ref('fadeInLeft');
const timesheet_page = ref<QScrollArea | null>(null);
const currentDayComponent = ref<HTMLElement[] | null>(null);
const currentDayComponentWatcher = ref(currentDayComponent);
const scroll_y = computed(() => timesheet_page.value?.getScrollPosition().top ?? 0);
const handleSwipe: TouchSwipeValue = (details) => {
mobile_animation_direction.value = details.direction === 'left' ? 'fadeInRight' : 'fadeInLeft';
if (details.distance && details.distance.x && Math.abs(details.distance.x) > 10) {
timesheet_api.getTimesheetsBySwiping(details.direction === 'left' ? 1 : -1).catch(error => console.error(error));
}
};
watch(currentDayComponentWatcher, () => {
if (currentDayComponent.value && timesheet_page.value && q.platform.is.mobile) {
timesheet_page.value.setScrollPosition('vertical', currentDayComponent.value[0]!.offsetTop, 800);
return;
}
})
</script>
<template>
<div
class="column fit relative-position"
:style="$q.platform.is.mobile && $q.screen.width < $q.screen.height ? 'margin-bottom: 40px' : ''"
v-touch-swipe="handleSwipe"
>
<q-scroll-area
ref="timesheet_page"
:horizontal-offset="[0, 3]"
class="col absolute-full hide-scrollbar"
:thumb-style="{ opacity: '0' }"
:bar-style="{ opacity: '0' }"
>
<!-- Show if no timesheets found (further than one month from present) -->
<div
v-if="timesheet_store.timesheets.length < 1 && !timesheet_store.is_loading"
class="col-auto column flex-center fit q-py-lg"
style="min-height: 20vh;"
>
<span class="text-uppercase text-weight-bolder text-center">{{ $t('shared.error.no_data_found')
}}</span>
<q-icon
name="las la-calendar"
color="accent"
size="10em"
class="absolute"
style="opacity: 0.2;"
/>
</div>
<!-- Else show timesheets if found -->
<ShiftList />
</q-scroll-area>
<q-page-sticky
v-if="mode === 'normal'"
position="bottom-right"
:offset="$q.screen.width > $q.screen.height ? [15, 15] : [15, 65]"
class="z-top"
>
<transition
appear
enter-active-class="animated zoomIn"
leave-active-class="animated zoomOut"
>
<q-btn
v-if="scroll_y > 400"
fab
icon="las la-chevron-up"
color="white"
text-color="accent"
class="shadow-12"
@click="timesheet_page!.setScrollPosition('vertical', 0, 300)"
/>
</transition>
</q-page-sticky>
</div>
</template>

View File

@ -1,323 +1,233 @@
<script <script
setup setup
lang="ts" lang="ts"
> >
import ShiftListDay from 'src/modules/timesheets/components/shift-list-day.vue'; import ShiftListDay from 'src/modules/timesheets/components/shift-list-day.vue';
import ShiftListDateWidget from 'src/modules/timesheets/components/shift-list-date-widget.vue'; import ShiftListDateWidget from 'src/modules/timesheets/components/shift-list-date-widget.vue';
import { date, useQuasar } from 'quasar'; import { ref, computed } from 'vue';
import { computed, ref, watch } from 'vue'; import { useUiStore } from 'src/stores/ui-store';
import { useUiStore } from 'src/stores/ui-store'; import { useTimesheetStore } from 'src/stores/timesheet-store';
import { useTimesheetStore } from 'src/stores/timesheet-store'; import { Shift } from 'src/modules/timesheets/models/shift.models';
import { Shift } from 'src/modules/timesheets/models/shift.models'; import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api';
import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api'; import type { TimesheetDay } from 'src/modules/timesheets/models/timesheet.models';
import type { QScrollArea, TouchSwipeValue } from 'quasar';
import type { TimesheetDay } from 'src/modules/timesheets/models/timesheet.models'; import { date } from 'quasar';
const CURRENT_DATE_STRING = new Date().toISOString().slice(0, 10); const CURRENT_DATE_STRING = new Date().toISOString().slice(0, 10);
const { extractDate } = date; const { extractDate } = date;
const q = useQuasar();
const ui_store = useUiStore();
const ui_store = useUiStore(); const timesheet_store = useTimesheetStore();
const timesheet_store = useTimesheetStore(); const timesheet_api = useTimesheetApi();
const timesheet_api = useTimesheetApi();
const mobile_animation_direction = ref('fadeInLeft');
const { mode = 'normal' } = defineProps<{
mode: 'normal' | 'approval'; const animation_style = computed(() => ui_store.is_mobile_mode ? mobile_animation_direction.value : 'fadeInDown');
}>();
const addNewShift = (day_shifts: Shift[], date: string, timesheet_id: number) => {
const mobile_animation_direction = ref('fadeInLeft'); ui_store.focus_next_component = true;
const animation_style = computed(() => ui_store.is_mobile_mode ? mobile_animation_direction.value : 'fadeInDown'); const new_shift = new Shift;
new_shift.date = date;
const timesheet_page = ref<QScrollArea | null>(null); new_shift.timesheet_id = timesheet_id;
const currentDayComponent = ref<HTMLElement[] | null>(null); day_shifts.push(new_shift);
const currentDayComponentWatcher = ref(currentDayComponent); };
const scroll_y = computed(() => timesheet_page.value?.getScrollPosition().top ?? 0); const deleteUnsavedShift = (timesheet_index: number, day_index: number) => {
const timesheet_container = ref<HTMLElement | null>(null); if (timesheet_store.timesheets !== undefined) {
const scroll_area_height = ref(0); const day = timesheet_store.timesheets[timesheet_index]!.days[day_index]!;
const shifts_without_deleted_shift = day.shifts.filter(shift => shift.id !== 0);
const addNewShift = (day_shifts: Shift[], date: string, timesheet_id: number) => { day.shifts = shifts_without_deleted_shift;
ui_store.focus_next_component = true; }
const new_shift = new Shift; };
new_shift.date = date;
new_shift.timesheet_id = timesheet_id; const getDayApproval = (day: TimesheetDay) => {
day_shifts.push(new_shift); if (day.shifts.length < 1) return false;
}; return day.shifts.every(shift => shift.is_approved === true);
};
const deleteUnsavedShift = (timesheet_index: number, day_index: number) => {
if (timesheet_store.timesheets !== undefined) { const getMobileDayRef = (iso_date_string: string): string => {
const day = timesheet_store.timesheets[timesheet_index]!.days[day_index]!; return iso_date_string === CURRENT_DATE_STRING ? 'currentDayComponent' : '';
const shifts_without_deleted_shift = day.shifts.filter(shift => shift.id !== 0); };
day.shifts = shifts_without_deleted_shift; </script>
}
}; <template>
<div
const getDayApproval = (day: TimesheetDay) => { class="fit"
if (day.shifts.length < 1) return false; :class="$q.platform.is.mobile ? 'column' : 'row'"
return day.shifts.every(shift => shift.is_approved === true); >
}; <div
v-for="timesheet, timesheet_index of timesheet_store.timesheets"
const handleSwipe: TouchSwipeValue = (details) => { :key="timesheet.timesheet_id"
mobile_animation_direction.value = details.direction === 'left' ? 'fadeInRight' : 'fadeInLeft'; class="col column fit items-center"
if (details.distance && details.distance.x && Math.abs(details.distance.x) > 10) { >
timesheet_api.getTimesheetsBySwiping(details.direction === 'left' ? 1 : -1).catch(error => console.error(error)); <transition
} appear
}; enter-active-class="animated fadeInDown"
leave-active-class="animated fadeOutUp"
const getMobileDayRef = (iso_date_string: string): string => { >
return iso_date_string === CURRENT_DATE_STRING ? 'currentDayComponent' : ''; <q-btn
}; v-if="!$q.platform.is.mobile && timesheet.days.every(day => day.shifts.length < 1) && timesheet_store.has_timesheet_preset"
:disable="!timesheet.days.every(day => day.shifts.length < 1)"
watch(currentDayComponentWatcher, () => { flat
if (currentDayComponent.value && timesheet_page.value && q.platform.is.mobile) { dense
console.log('setting scroll position to offsetTop of currentDayComponent: ', currentDayComponent.value[0]!.offsetTop); :label="$t('timesheet.apply_preset_week')"
timesheet_page.value.setScrollPosition('vertical', currentDayComponent.value[0]!.offsetTop, 800); class="col-auto text-uppercase text-weight-bold text-accent q-mx-lg q-py-none rounded-5"
return; @click="timesheet_api.applyPreset(timesheet.timesheet_id)"
} >
<q-icon
if (timesheet_container.value !== null && mode === 'approval') { name="las la-calendar-week"
scroll_area_height.value = timesheet_container.value.offsetHeight color="accent"
} size="md"
}) />
</script> </q-btn>
</transition>
<template>
<div <transition-group
class="column fit relative-position" appear
:style="$q.platform.is.mobile && $q.screen.width < $q.screen.height ? 'margin-bottom: 40px' : ''" :enter-active-class="`animated ${animation_style}`"
v-touch-swipe="handleSwipe" >
> <div
<q-scroll-area v-for="day, day_index in timesheet.days"
ref="timesheet_page" :key="day.date"
:horizontal-offset="[0, 3]" :ref="getMobileDayRef(day.date)"
class="col absolute-full hide-scrollbar" class="col-auto row q-pa-sm full-width"
:style="mode === 'approval' ? `height: ${scroll_area_height}px;` : ''" :style="`animation-delay: ${day_index / 15}s;`"
:thumb-style="{ opacity: '0' }" >
:bar-style="{ opacity: '0' }" <!-- mobile version in portrait mode -->
> <div
<!-- Show if no timesheets found (further than one month from present) --> v-if="$q.platform.is.mobile && ($q.screen.width < $q.screen.height)"
<div class="col column full-width q-px-md q-py-sm"
v-if="timesheet_store.timesheets.length < 1 && !timesheet_store.is_loading" >
class="col-auto column flex-center fit q-py-lg" <q-card
style="min-height: 20vh;" class="shadow-12"
> :class="(getDayApproval(day) || timesheet.is_approved) ? 'bg-accent rounded-10' : 'bg-dark mobile-rounded-10'"
<span class="text-uppercase text-weight-bolder text-center">{{ $t('shared.error.no_data_found') >
}}</span>
<q-icon <q-card-section
name="las la-calendar" class="text-weight-bolder text-uppercase text-h6 q-py-sm text-center relative-position"
color="accent" :class="(getDayApproval(day) || timesheet.is_approved) ? 'bg-accent text-white' : 'bg-primary text-white'"
size="10em" style="line-height: 1em;"
class="absolute" >
style="opacity: 0.2;" <span> {{ $d(extractDate(day.date, 'YYYY-MM-DD'), {
/> weekday: 'long', day: 'numeric', month:
</div> 'long'
}) }}</span>
<!-- Else show timesheets if found -->
<div <q-icon
v-else v-if="(getDayApproval(day) || timesheet.is_approved)"
ref="timesheet_container" name="verified"
class="col fit" size="3em"
:class="$q.platform.is.mobile ? 'column' : 'row'" color="white"
> class="absolute-top-left z-top"
<div style="top: -0.2em; left: 0px;"
v-for="timesheet, timesheet_index of timesheet_store.timesheets" />
:key="timesheet.timesheet_id" </q-card-section>
class="col column fit flex-center"
> <q-card-section
<transition v-if="day.shifts.filter(shift => shift.id !== 0).length > 0"
appear class="q-pa-none transparent"
enter-active-class="animated fadeInDown" >
leave-active-class="animated fadeOutUp" <ShiftListDay
> outlined
<q-btn :timesheet-id="timesheet.timesheet_id"
v-if="!$q.platform.is.mobile && timesheet.days.every(day => day.shifts.length < 1) && timesheet_store.has_timesheet_preset" :week-day-index="day_index"
:disable="!timesheet.days.every(day => day.shifts.length < 1)" :animation-delay-multiplier="day_index"
flat :approved="(getDayApproval(day) || timesheet.is_approved)"
dense :day="day"
:label="$t('timesheet.apply_preset_week')" @delete-unsaved-shift="deleteUnsavedShift(timesheet_index, day_index)"
class="col-auto text-uppercase text-weight-bold text-accent q-mx-lg q-py-none rounded-5" />
@click="timesheet_api.applyPreset(timesheet.timesheet_id)" </q-card-section>
>
<q-icon <q-card-section class="q-pa-none">
name="las la-calendar-week" <q-btn
color="accent" v-if="!(getDayApproval(day) || timesheet.is_approved)"
size="md" square
/> dense
</q-btn> size="xl"
</transition> color="accent"
icon="more_time"
<transition-group class="full-width"
appear style="border-radius: 0 0 10px 10px;"
:enter-active-class="`animated ${animation_style}`" @click="addNewShift(day.shifts, day.date, timesheet.timesheet_id)"
> />
<div </q-card-section>
v-for="day, day_index in timesheet.days" </q-card>
:key="day.date" </div>
:ref="getMobileDayRef(day.date)"
class="col-auto row q-pa-sm fit" <!-- desktop version -->
:style="`animation-delay: ${day_index / 15}s;`" <div
> v-else
<!-- mobile version in portrait mode --> class="col row full-width rounded-10 ellipsis shadow-10"
<div >
v-if="$q.platform.is.mobile && ($q.screen.width < $q.screen.height)" <div
class="col column full-width q-px-md q-py-sm" class="col row"
> :class="(getDayApproval(day) || timesheet.is_approved) ? 'bg-accent' : 'bg-dark'"
<q-card >
class="mobile-rounded-10 shadow-12" <!-- Date block -->
:class="(getDayApproval(day) || timesheet.is_approved) ? 'bg-accent' : 'bg-dark'" <ShiftListDateWidget
> :display-date="day.date"
:approved="(getDayApproval(day) || timesheet.is_approved)"
<q-card-section class="col-auto"
class="text-weight-bolder text-uppercase text-h6 q-py-sm text-center relative-position" />
:class="(getDayApproval(day) || timesheet.is_approved) ? 'bg-accent text-white' : 'bg-primary text-white'"
style="line-height: 1em;" <ShiftListDay
> :timesheet-id="timesheet.timesheet_id"
<span> {{ $d(extractDate(day.date, 'YYYY-MM-DD'), { :week-day-index="day_index"
weekday: 'long', day: 'numeric', month: :day="day"
'long' :approved="getDayApproval(day) || timesheet.is_approved"
}) }}</span> class="col"
@delete-unsaved-shift="deleteUnsavedShift(timesheet_index, day_index)"
<q-icon />
v-if="(getDayApproval(day) || timesheet.is_approved)" </div>
name="verified"
size="3em"
color="white" <div class="col-auto self-stretch">
class="absolute-top-left z-top" <q-icon
style="top: -0.2em; left: 0px;" v-if="(getDayApproval(day) || timesheet.is_approved)"
/> name="verified"
</q-card-section> color="white"
size="xl"
<q-card-section class="full-height"
v-if="day.shifts.filter(shift => shift.id !== 0).length > 0" :class="(getDayApproval(day) || timesheet.is_approved) ? 'bg-accent' : ''"
class="q-pa-none transparent" />
>
<ShiftListDay <q-btn
outlined v-else
:timesheet-id="timesheet.timesheet_id" :dense="!$q.platform.is.mobile"
:week-day-index="day_index" square
:animation-delay-multiplier="day_index" icon="more_time"
:approved="(getDayApproval(day) || timesheet.is_approved)" size="lg"
:day="day" color="accent"
@delete-unsaved-shift="deleteUnsavedShift(timesheet_index, day_index)" text-color="white"
/> class="full-height"
</q-card-section> :class="$q.platform.is.mobile ? 'q-px-xs' : ''"
@click="addNewShift(day.shifts, day.date, timesheet.timesheet_id)"
<q-card-section class="q-pa-none"> />
<q-btn </div>
v-if="!(getDayApproval(day) || timesheet.is_approved)" </div>
square </div>
dense </transition-group>
size="xl" </div>
color="accent" </div>
icon="more_time" </template>
class="full-width"
style="border-radius: 0 0 10px 10px;" <style
@click="addNewShift(day.shifts, day.date, timesheet.timesheet_id)" scoped
/> lang="scss"
</q-card-section> >
</q-card> @each $size in (1, 2, 3, 4, 5, 10, 15, 20, 25, 50, 75, 100, 200) {
</div> .mobile-rounded-#{$size} {
border-radius: #{$size}px !important;
<!-- desktop version --> }
<div
v-else .mobile-rounded-#{$size}>div:first-child {
class="col row full-width rounded-10 ellipsis shadow-10" border-radius: #{$size}px #{$size}px 0 0 !important;
> }
<div
class="col row" .mobile-rounded-#{$size}>div:last-child {
:class="(getDayApproval(day) || timesheet.is_approved) ? 'bg-accent' : 'bg-dark'" border-radius: 0 0 #{$size}px #{$size}px !important;
> }
<!-- Date block --> }
<ShiftListDateWidget
:display-date="day.date"
:approved="(getDayApproval(day) || timesheet.is_approved)"
class="col-auto"
/>
<ShiftListDay
:timesheet-id="timesheet.timesheet_id"
:week-day-index="day_index"
:day="day"
:approved="getDayApproval(day) || timesheet.is_approved"
class="col"
@delete-unsaved-shift="deleteUnsavedShift(timesheet_index, day_index)"
/>
</div>
<div class="col-auto self-stretch">
<q-icon
v-if="(getDayApproval(day) || timesheet.is_approved)"
name="verified"
color="white"
size="xl"
class="full-height"
:class="(getDayApproval(day) || timesheet.is_approved) ? 'bg-accent' : ''"
/>
<q-btn
v-else
:dense="!$q.platform.is.mobile"
square
icon="more_time"
size="lg"
color="accent"
text-color="white"
class="full-height"
:class="$q.platform.is.mobile ? 'q-px-xs' : ''"
@click="addNewShift(day.shifts, day.date, timesheet.timesheet_id)"
/>
</div>
</div>
</div>
</transition-group>
</div>
</div>
</q-scroll-area>
<q-page-sticky
v-if="mode === 'normal'"
position="bottom-right"
:offset="$q.screen.width > $q.screen.height ? [15, 15] : [15, 65]"
class="z-top"
>
<transition
appear
enter-active-class="animated zoomIn"
leave-active-class="animated zoomOut"
>
<q-btn
v-if="scroll_y > 400"
fab
icon="las la-chevron-up"
color="white"
text-color="accent"
class="shadow-12"
@click="timesheet_page!.setScrollPosition('vertical', 0, 300)"
/>
</transition>
</q-page-sticky>
</div>
</template>
<style
scoped
lang="scss"
>
@each $size in (1, 2, 3, 4, 5, 10, 15, 20, 25, 50, 75, 100, 200) {
.mobile-rounded-#{$size} {
border-radius: #{$size}px !important;
}
.mobile-rounded-#{$size}>div:first-child {
border-radius: #{$size}px #{$size}px 0 0 !important;
}
.mobile-rounded-#{$size}>div:last-child {
border-radius: 0 0 #{$size}px #{$size}px !important;
}
}
</style> </style>

View File

@ -4,6 +4,7 @@
> >
/* eslint-disable */ /* eslint-disable */
import ShiftList from 'src/modules/timesheets/components/shift-list.vue'; import ShiftList from 'src/modules/timesheets/components/shift-list.vue';
import ShiftListScrollable from 'src/modules/timesheets/components/shift-list-scrollable.vue';
import LoadingOverlay from 'src/modules/shared/components/loading-overlay.vue'; import LoadingOverlay from 'src/modules/shared/components/loading-overlay.vue';
import ExpenseDialog from 'src/modules/timesheets/components/expense-dialog.vue'; import ExpenseDialog from 'src/modules/timesheets/components/expense-dialog.vue';
import PageHeaderTemplate from 'src/modules/shared/components/page-header-template.vue'; import PageHeaderTemplate from 'src/modules/shared/components/page-header-template.vue';
@ -28,6 +29,21 @@
const is_timesheets_approved = computed(() => timesheet_store.timesheets.every(timesheet => timesheet.is_approved)) const is_timesheets_approved = computed(() => timesheet_store.timesheets.every(timesheet => timesheet.is_approved))
const total_hours = computed(() => timesheet_store.timesheets.reduce((sum, timesheet) =>
sum + timesheet.weekly_hours.regular
+ timesheet.weekly_hours.evening
+ timesheet.weekly_hours.emergency
+ timesheet.weekly_hours.overtime,
0) //initial value
);
const total_expenses = computed(() => timesheet_store.timesheets.reduce((sum, timesheet) =>
sum + timesheet.weekly_expenses.expenses
+ timesheet.weekly_expenses.on_call
+ timesheet.weekly_expenses.per_diem,
0) //initial value
);
const { mode = 'normal' } = defineProps<{ const { mode = 'normal' } = defineProps<{
mode?: 'approval' | 'normal'; mode?: 'approval' | 'normal';
}>(); }>();
@ -46,7 +62,7 @@
<span <span
v-if="mode === 'approval'" v-if="mode === 'approval'"
class="col-auto text-uppercase text-bold text-h5" class="col-auto text-uppercase text-bold text-h5"
> >
{{ $t('timesheet.page_header') }} {{ $t('timesheet.page_header') }}
</span> </span>
@ -54,8 +70,15 @@
<!-- weekly overview --> <!-- weekly overview -->
<div class="col-auto row q-px-lg full-width"> <div class="col-auto row q-px-lg full-width">
<!-- supervisor weekly overview --> <!-- supervisor weekly overview -->
<div class="col-xs-6 col-md-4 col-xl-3 q-pa-md"> <div
<ShiftListWeeklyOverview mode="total-hours" /> v-if="!$q.platform.is.mobile"
class="col-xs-6 col-md-4 col-xl-3 q-pa-md"
>
<ShiftListWeeklyOverview
mode="total-hours"
:total-hours="total_hours"
:total-expenses="total_expenses"
/>
</div> </div>
<PageHeaderTemplate <PageHeaderTemplate
@ -66,10 +89,13 @@
class="col" class="col"
/> />
<q-space v-else /> <q-space v-if="!$q.platform.is.mobile && mode === 'approval'" />
<!-- employee weekly overview --> <!-- employee weekly overview -->
<div class="col-xs-6 col-md-4 col-xl-3 q-pa-md"> <div
v-if="!$q.platform.is.mobile"
class="col-xs-6 col-md-4 col-xl-3 q-pa-md"
>
<ShiftListWeeklyOverview mode="off-hours" /> <ShiftListWeeklyOverview mode="off-hours" />
</div> </div>
</div> </div>
@ -77,7 +103,7 @@
<!-- top menu --> <!-- top menu -->
<div <div
class="col-auto row items-center full-width" class="col-auto row items-center full-width"
:class="$q.platform.is.mobile && ($q.screen.width < $q.screen.height) ? 'justify-between' : 'q-pb-sm q-px-md'" :class="$q.platform.is.mobile && ($q.screen.width < $q.screen.height) ? 'justify-between q-px-md' : 'q-pb-sm q-px-xl'"
> >
<!-- navigation btn --> <!-- navigation btn -->
<PayPeriodNavigator <PayPeriodNavigator
@ -133,12 +159,40 @@
<!-- mobile weekly overview widget --> <!-- mobile weekly overview widget -->
<ShiftListWeeklyOverviewMobile class="col-auto" /> <ShiftListWeeklyOverviewMobile class="col-auto" />
<ShiftList <!-- standard scrollable shift list for user input -->
<ShiftListScrollable
v-if="mode === 'normal'"
:mode="mode" :mode="mode"
:class="mode === 'normal' ? 'col' : 'col-auto'" :class="mode === 'normal' ? 'col' : 'col-auto'"
:style="mode === 'normal' ? '' : 'min-height: 100vh'"
/> />
<!-- full shift list for timesheet approval details dialog -->
<div
v-else
class="col-auto column full-width"
:style="$q.platform.is.mobile && $q.screen.width < $q.screen.height ? 'margin-bottom: 40px' : ''"
>
<!-- Show if no timesheets found (further than one month from present) -->
<div
v-if="timesheet_store.timesheets.length < 1 && !timesheet_store.is_loading"
class="col-auto column flex-center fit q-py-lg"
style="min-height: 20vh;"
>
<span class="text-uppercase text-weight-bolder text-center">{{ $t('shared.error.no_data_found')
}}</span>
<q-icon
name="las la-calendar"
color="accent"
size="10em"
class="absolute"
style="opacity: 0.2;"
/>
</div>
<!-- Else show timesheets if found -->
<ShiftList class="col" />
</div>
<q-btn <q-btn
v-if="$q.platform.is.mobile && $q.screen.width < $q.screen.height" v-if="$q.platform.is.mobile && $q.screen.width < $q.screen.height"
square square

View File

@ -20,7 +20,7 @@
const headerComponent = ref<HTMLElement | null>(null); const headerComponent = ref<HTMLElement | null>(null);
const table_max_height = computed(() => { const table_max_height = computed(() => {
const height = page_height.value - (headerComponent.value?.clientHeight ?? 0); const height = page_height.value - Math.min(headerComponent.value?.clientHeight ?? 0, headerComponent.value?.offsetHeight ?? 0);
return height; return height;
}); });