feat(docker): Add/Correct Dockerfile for remote Docker Lab deployment
This commit is contained in:
parent
33061ef2ab
commit
6c6cecbe7d
23
Dockerfile
Normal file
23
Dockerfile
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# targo-frontend
|
||||||
|
FROM node:22
|
||||||
|
|
||||||
|
# Set working directory inside container
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Set environment variables
|
||||||
|
ENV VITE_TARGO_BACKEND_URL="http://targo-backend:3000"
|
||||||
|
|
||||||
|
# Copy package.json & package-lock.json first (for caching)
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# Copy the rest of the code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Expose Quasar dev port
|
||||||
|
EXPOSE 9000
|
||||||
|
|
||||||
|
# Default command
|
||||||
|
CMD ["quasar", "dev"]
|
||||||
|
|
@ -15,7 +15,7 @@ declare module 'vue' {
|
||||||
// "export default () => {}" function below (which runs individually
|
// "export default () => {}" function below (which runs individually
|
||||||
// for each client)
|
// for each client)
|
||||||
const api = axios.create({
|
const api = axios.create({
|
||||||
baseURL: import.meta.env.VITE_TARGO_BACKEND_AUTH_URL,
|
baseURL: import.meta.env.VITE_TARGO_BACKEND_URL,
|
||||||
withCredentials: true
|
withCredentials: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// app global css in SCSS form
|
// app global css in SCSS form
|
||||||
@each $size in (1, 2, 3, 4, 5, 10, 15, 20, 25, 50, 75, 100) {
|
@each $size in (1, 2, 3, 4, 5, 10, 15, 20, 25, 50, 75, 100, 200) {
|
||||||
.rounded-#{$size} {
|
.rounded-#{$size} {
|
||||||
border-radius: #{$size}px !important;
|
border-radius: #{$size}px !important;
|
||||||
}
|
}
|
||||||
|
|
@ -37,3 +37,8 @@ body.body--dark {
|
||||||
.shift-highlight {
|
.shift-highlight {
|
||||||
background: #0195462a;
|
background: #0195462a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.frosted-glass {
|
||||||
|
background-color: #FFFA !important;
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
}
|
||||||
|
|
@ -27,6 +27,8 @@ $layout-shadow-dark : 0 0 10px 5px rgba($dark-shadow-color, 0.5);
|
||||||
|
|
||||||
$input-text-color : #455A64;
|
$input-text-color : #455A64;
|
||||||
$input-autofill-color : #AAD5C4;
|
$input-autofill-color : #AAD5C4;
|
||||||
|
$field-dense-label-top : 5px !default;
|
||||||
|
$field-dense-label-font-size : 16px !default;
|
||||||
|
|
||||||
|
|
||||||
$dark : #42444b;
|
$dark : #42444b;
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-drawer
|
<q-drawer
|
||||||
v-model="ui_store.isRightDrawerOpen"
|
v-model="ui_store.is_left_drawer_open"
|
||||||
persistent
|
persistent
|
||||||
mini-to-overlay
|
mini-to-overlay
|
||||||
elevated
|
elevated
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script
|
||||||
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useAuthApi } from 'src/modules/auth/composables/use-auth-api';
|
import { useAuthApi } from 'src/modules/auth/composables/use-auth-api';
|
||||||
|
|
||||||
|
|
@ -6,16 +9,20 @@
|
||||||
|
|
||||||
const email = defineModel<string>('email', { default: '', });
|
const email = defineModel<string>('email', { default: '', });
|
||||||
const is_remembered = ref<boolean>(false);
|
const is_remembered = ref<boolean>(false);
|
||||||
const is_employee_email = computed( () => email.value.includes('@targ'));
|
const is_employee_email = computed(() => email.value.includes('@targ'));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-card class="rounded-15">
|
<q-card class="rounded-15 shadow-10">
|
||||||
<q-card-section class="text-center bg-primary q-pa-lg">
|
<q-card-section class="text-center bg-primary q-pa-lg">
|
||||||
<q-img src="/src/assets/logo-targo-white.svg" ratio="4.6" fit="contain" />
|
<q-img
|
||||||
|
src="/src/assets/logo-targo-white.svg"
|
||||||
|
ratio="4.6"
|
||||||
|
fit="contain"
|
||||||
|
/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
<div class="q-pt-sm q-px-xl q-pb-lg">
|
<div class="q-pt-sm q-px-xl q-pb-lg ">
|
||||||
<q-card-section class="text-center text-uppercase">
|
<q-card-section class="text-center text-uppercase">
|
||||||
<div class="text-h6 text-weight-bold">
|
<div class="text-h6 text-weight-bold">
|
||||||
{{ $t('login.page_header') }}
|
{{ $t('login.page_header') }}
|
||||||
|
|
@ -28,8 +35,14 @@
|
||||||
dense
|
dense
|
||||||
outlined
|
outlined
|
||||||
label-color="primary"
|
label-color="primary"
|
||||||
:label="$t('login.email')"
|
class="rounded-5 inset-shadow bg-blue-grey-1"
|
||||||
/>
|
label-slot
|
||||||
|
input-class="text-weight-medium text-h6"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<span class="text-weight-bolder text-uppercase text-overline"> {{ $t('login.email') }} </span>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
|
||||||
<q-card-section class="q-ma-none q-pa-none text-uppercase text-caption text-weight-medium">
|
<q-card-section class="q-ma-none q-pa-none text-uppercase text-caption text-weight-medium">
|
||||||
<q-toggle
|
<q-toggle
|
||||||
|
|
@ -58,9 +71,16 @@
|
||||||
</q-form>
|
</q-form>
|
||||||
|
|
||||||
<q-card-section class="row q-pt-sm">
|
<q-card-section class="row q-pt-sm">
|
||||||
<q-separator color="primary" class="col self-center"/>
|
<q-separator
|
||||||
<span class="col text-primary text-weight-bolder text-center text-uppercase self-center">{{ $t('shared.misc.or') }}</span>
|
color="primary"
|
||||||
<q-separator color="primary" class="col self-center"/>
|
class="col self-center"
|
||||||
|
/>
|
||||||
|
<span class="col text-primary text-weight-bolder text-center text-uppercase self-center">{{
|
||||||
|
$t('shared.misc.or') }}</span>
|
||||||
|
<q-separator
|
||||||
|
color="primary"
|
||||||
|
class="col self-center"
|
||||||
|
/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
<q-card-section class="column q-px-sm q-pt-none">
|
<q-card-section class="column q-px-sm q-pt-none">
|
||||||
|
|
@ -73,7 +93,10 @@
|
||||||
:label="$t('login.button.facebook')"
|
:label="$t('login.button.facebook')"
|
||||||
class="full-width row q-mb-sm"
|
class="full-width row q-mb-sm"
|
||||||
>
|
>
|
||||||
<q-tooltip anchor="top middle" class="bg-primary">{{$t('login.tooltip.coming_soon')}}</q-tooltip>
|
<q-tooltip
|
||||||
|
anchor="top middle"
|
||||||
|
class="bg-primary"
|
||||||
|
>{{ $t('login.tooltip.coming_soon') }}</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-slide-transition>
|
<q-slide-transition>
|
||||||
<div v-if="is_employee_email">
|
<div v-if="is_employee_email">
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
<script
|
|
||||||
setup
|
|
||||||
lang="ts"
|
|
||||||
>
|
|
||||||
defineProps<{
|
|
||||||
isLoading: boolean;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<q-card
|
|
||||||
v-if="isLoading"
|
|
||||||
flat
|
|
||||||
class="column flex-center rounded-10"
|
|
||||||
style="width: 20vw !important; height: 20vh !important;"
|
|
||||||
>
|
|
||||||
<q-spinner
|
|
||||||
color="primary"
|
|
||||||
size="5em"
|
|
||||||
:thickness="10"
|
|
||||||
class="col-auto"
|
|
||||||
/>
|
|
||||||
<div class="col-auto text-primary text-h6 text-weight-bold text-center ">
|
|
||||||
{{ $t('shared.label.loading') }}
|
|
||||||
</div>
|
|
||||||
</q-card>
|
|
||||||
</template>
|
|
||||||
|
|
@ -72,7 +72,7 @@
|
||||||
>
|
>
|
||||||
<q-badge
|
<q-badge
|
||||||
v-if="expense.is_approved"
|
v-if="expense.is_approved"
|
||||||
class="absolute z-top rounded-20 bg-white q-pa-none"
|
class="absolute z-top rounded-20 bg-dark q-pa-none"
|
||||||
style="transform: translate(-15px, -15px);"
|
style="transform: translate(-15px, -15px);"
|
||||||
>
|
>
|
||||||
<q-icon
|
<q-icon
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@
|
||||||
{ type: 'REGULAR', color: 'secondary', label_type: 'timesheet.shift.types.REGULAR' },
|
{ type: 'REGULAR', color: 'secondary', label_type: 'timesheet.shift.types.REGULAR' },
|
||||||
{ type: 'EVENING', color: 'warning', label_type: 'timesheet.shift.types.EVENING' },
|
{ type: 'EVENING', color: 'warning', label_type: 'timesheet.shift.types.EVENING' },
|
||||||
{ type: 'EMERGENCY', color: 'amber-10', label_type: 'timesheet.shift.types.EMERGENCY' },
|
{ type: 'EMERGENCY', color: 'amber-10', label_type: 'timesheet.shift.types.EMERGENCY' },
|
||||||
{ type: 'OVERTIME', color: 'negative', label_type: 'timesheet.shift.types.OVERTIME' },
|
|
||||||
{ type: 'VACATION', color: 'purple-10', label_type: 'timesheet.shift.types.VACATION' },
|
{ type: 'VACATION', color: 'purple-10', label_type: 'timesheet.shift.types.VACATION' },
|
||||||
{ type: 'HOLIDAY', color: 'purple-5', label_type: 'timesheet.shift.types.HOLIDAY' },
|
{ type: 'HOLIDAY', color: 'purple-5', label_type: 'timesheet.shift.types.HOLIDAY' },
|
||||||
{ type: 'SICK', color: 'grey-8', label_type: 'timesheet.shift.types.SICK' },
|
{ type: 'SICK', color: 'grey-8', label_type: 'timesheet.shift.types.SICK' },
|
||||||
|
|
|
||||||
|
|
@ -2,60 +2,111 @@
|
||||||
setup
|
setup
|
||||||
lang="ts"
|
lang="ts"
|
||||||
>
|
>
|
||||||
import { ref } from 'vue';
|
/* eslint-disable*/
|
||||||
import type { Shift } from 'src/modules/timesheets/models/shift.models';
|
import { onMounted, ref, useTemplateRef } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { QSelect } from 'quasar';
|
||||||
|
import { Shift, ShiftType } from 'src/modules/timesheets/models/shift.models';
|
||||||
|
import { useUiStore } from 'src/stores/ui-store';
|
||||||
|
|
||||||
const { shift, dense = false } = defineProps<{
|
const { t } = useI18n();
|
||||||
shift: Shift;
|
const ui_store = useUiStore();
|
||||||
|
|
||||||
|
const shift = defineModel<Shift>('shift', { required: true });
|
||||||
|
|
||||||
|
const { dense = false } = defineProps<{
|
||||||
dense?: boolean;
|
dense?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
'save-comment': [comment: string, shift: Shift];
|
'saveComment': [comment: string, shift_id: number];
|
||||||
'request-update': [shift: Shift];
|
'requestUpdate': [shift_id: number];
|
||||||
|
'requestDelete': [void];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const shift_start = ref(shift.start_time);
|
|
||||||
const shift_end = ref(shift.end_time);
|
|
||||||
const time_picker_model = ref('');
|
const time_picker_model = ref('');
|
||||||
const is_showing_time_picker = ref(false);
|
const is_showing_time_picker = ref(false);
|
||||||
|
const select_ref = useTemplateRef<QSelect>('select');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const options: { label: string, value: ShiftType, icon: string, icon_color: string }[] = [
|
||||||
|
{ label: t('timesheet.shift.types.REGULAR'), value: 'REGULAR', icon: 'wb_sunny', icon_color: '' },
|
||||||
|
{ label: t('timesheet.shift.types.EVENING'), value: 'EVENING', icon: 'bedtime', icon_color: 'indigo-8' },
|
||||||
|
{ label: t('timesheet.shift.types.EMERGENCY'), value: 'EMERGENCY', icon: 'ring_volume', icon_color: 'red-8' },
|
||||||
|
{ label: t('timesheet.shift.types.VACATION'), value: 'VACATION', icon: 'beach_access', icon_color: 'yellow-8' },
|
||||||
|
{ label: t('timesheet.shift.types.HOLIDAY'), value: 'HOLIDAY', icon: 'forest', icon_color: 'green-8' },
|
||||||
|
{ label: t('timesheet.shift.types.SICK'), value: 'SICK', icon: 'medication_liquid', icon_color: 'cyan-8' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const shift_type_selected = ref(options.find(option => option.value == shift.value.type));
|
||||||
|
|
||||||
const showTimePicker = (time: string) => {
|
const showTimePicker = (time: string) => {
|
||||||
is_showing_time_picker.value = true;
|
is_showing_time_picker.value = true;
|
||||||
time_picker_model.value = time;
|
time_picker_model.value = time;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (ui_store.focus_next_component) {
|
||||||
|
select_ref.value?.focus();
|
||||||
|
select_ref.value?.showPopup();
|
||||||
|
ui_store.focus_next_component = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-card-section
|
<div
|
||||||
horizontal
|
v-if="shift.shift_id !== 0"
|
||||||
class="q-py-none q-mx-md text-uppercase text-center items-center rounded-10"
|
class="col row flex-center text-uppercase rounded-10"
|
||||||
style="line-height: 1;"
|
|
||||||
>
|
>
|
||||||
<!-- time-picker for mobile timesheet -->
|
<!-- shift type -->
|
||||||
<q-dialog
|
<q-select
|
||||||
v-model="is_showing_time_picker"
|
ref="select"
|
||||||
class="z-max"
|
v-model="shift_type_selected"
|
||||||
|
standout="bg-blue-grey-9"
|
||||||
|
dense
|
||||||
|
options-dense
|
||||||
|
hide-dropdown-icon
|
||||||
|
:menu-offset="[0, 10]"
|
||||||
|
:options="options"
|
||||||
|
class="rounded-5 q-mx-xs shadow-1"
|
||||||
|
:class="ui_store.is_mobile_mode ? 'col-auto' : 'col'"
|
||||||
|
popup-content-class="text-uppercase text-weight-bold text-center rounded-5"
|
||||||
|
popup-content-style="border: 2px solid var(--q-primary)"
|
||||||
>
|
>
|
||||||
<q-time
|
<template #selected-item="scope">
|
||||||
v-model="time_picker_model"
|
<div
|
||||||
format24h
|
class="row text-weight-bold q-ma-none q-pa-none no-wrap ellipsis"
|
||||||
color="primary"
|
:class="ui_store.is_mobile_mode ? 'items-center' : 'flex-center'"
|
||||||
|
:tabindex="scope.tabindex"
|
||||||
|
>
|
||||||
|
<q-icon
|
||||||
|
:name="scope.opt.icon"
|
||||||
|
:color="scope.opt.icon_color"
|
||||||
|
size="sm"
|
||||||
|
class="col-auto q-mx-xs"
|
||||||
/>
|
/>
|
||||||
</q-dialog>
|
<span
|
||||||
|
v-if="$q.screen.gt.md"
|
||||||
|
style="line-height: 0.9em;"
|
||||||
|
class="col ellipsis"
|
||||||
|
>{{ scope.opt.label }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
|
||||||
<div class="col row">
|
|
||||||
<!-- punch-in timestamp -->
|
<!-- punch-in timestamp -->
|
||||||
<q-input
|
<q-input
|
||||||
|
v-model="shift.start_time"
|
||||||
dense
|
dense
|
||||||
|
type="time"
|
||||||
standout="bg-blue-grey-9"
|
standout="bg-blue-grey-9"
|
||||||
label-slot
|
label-slot
|
||||||
v-model="shift_start"
|
|
||||||
class="col q-pa-none q-my-xs"
|
|
||||||
input-class="text-weight-bold text-h6"
|
|
||||||
label-color="primary"
|
label-color="primary"
|
||||||
mask="## : ##"
|
input-class="text-weight-medium"
|
||||||
lazy-rules
|
input-style="font-size: 1.2em;"
|
||||||
|
class="col q-mx-xs"
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
<span
|
<span
|
||||||
|
|
@ -66,51 +117,28 @@
|
||||||
|
|
||||||
<template #append>
|
<template #append>
|
||||||
<q-btn
|
<q-btn
|
||||||
|
v-if="ui_store.is_mobile_mode"
|
||||||
dense
|
dense
|
||||||
flat
|
flat
|
||||||
icon="access_time"
|
icon="access_time"
|
||||||
color="primary"
|
color="primary"
|
||||||
class="q-ma-none"
|
@click.stop="showTimePicker(shift.start_time)"
|
||||||
@click.stop="showTimePicker(shift_start)"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
|
|
||||||
<!-- arrows pointing to punch-out timestamps -->
|
|
||||||
<q-card-section
|
|
||||||
horizontal
|
|
||||||
class="col-auto items-center justify-center q-mx-sm"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-for="icon_data, index in [
|
|
||||||
{ transform: 'transform: translateX(5px);', color: 'accent' },
|
|
||||||
{ transform: 'transform: translateX(-5px);', color: 'primary' }]"
|
|
||||||
:key="index"
|
|
||||||
>
|
|
||||||
<q-icon
|
|
||||||
v-if="shift.type && !dense"
|
|
||||||
name="double_arrow"
|
|
||||||
:color="icon_data.color"
|
|
||||||
:size="dense ? '16px' : '24px'"
|
|
||||||
:style="icon_data.transform"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<span
|
|
||||||
v-else
|
|
||||||
class="text-primary"
|
|
||||||
> > </span>
|
|
||||||
</div>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<!-- punch-out timestamps -->
|
<!-- punch-out timestamps -->
|
||||||
<q-input
|
<q-input
|
||||||
|
v-model="shift.end_time"
|
||||||
dense
|
dense
|
||||||
|
type="time"
|
||||||
standout="bg-blue-grey-9"
|
standout="bg-blue-grey-9"
|
||||||
label-slot
|
label-slot
|
||||||
v-model="shift_end"
|
|
||||||
class="col q-pa-none q-my-xs"
|
|
||||||
input-class="text-weight-bold text-h6"
|
|
||||||
label-color="primary"
|
label-color="primary"
|
||||||
|
input-class="text-weight-medium"
|
||||||
|
input-style="font-size: 1.2em;"
|
||||||
|
class="col q-mx-xs"
|
||||||
|
lazy-rules
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
<span
|
<span
|
||||||
|
|
@ -121,32 +149,49 @@
|
||||||
|
|
||||||
<template #append>
|
<template #append>
|
||||||
<q-btn
|
<q-btn
|
||||||
|
v-if="ui_store.is_mobile_mode"
|
||||||
dense
|
dense
|
||||||
flat
|
flat
|
||||||
icon="access_time"
|
icon="access_time"
|
||||||
color="primary"
|
color="primary"
|
||||||
@click="showTimePicker(shift_end)"
|
@click="showTimePicker(shift.end_time)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
</div>
|
|
||||||
|
|
||||||
<q-card-section class="col-grow q-pa-none no-wrap q-mx-xs">
|
<!-- comment and delete buttons -->
|
||||||
<!-- comment btn -->
|
<div
|
||||||
|
v-if="$q.screen.gt.sm"
|
||||||
|
class="col-auto"
|
||||||
|
>
|
||||||
<q-icon
|
<q-icon
|
||||||
v-if="shift.type && dense"
|
v-if="shift.type && dense"
|
||||||
:name="shift.comment ? 'comment' : ''"
|
:name="shift.comment ? 'comment' : ''"
|
||||||
color="primary"
|
color="primary"
|
||||||
:size="dense ? 'xs' : 'sm'"
|
:size="dense ? 'xs' : 'sm'"
|
||||||
class="q-pa-none q-mr-xs"
|
class="col-auto q-pa-none q-mr-xs"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<q-btn
|
<q-btn
|
||||||
v-else
|
v-else
|
||||||
flat
|
flat
|
||||||
dense
|
dense
|
||||||
:icon="shift.comment ? 'chat' : 'chat_bubble_outline'"
|
:icon="shift.comment ? 'chat' : 'chat_bubble_outline'"
|
||||||
:text-color="shift.comment ? 'primary' : 'grey-8'"
|
:text-color="shift.comment ? 'primary' : 'grey-8'"
|
||||||
|
class="col-auto q-ma-none q-pl-md full-height"
|
||||||
/>
|
/>
|
||||||
</q-card-section>
|
|
||||||
</q-card-section>
|
<q-btn
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
unelevated
|
||||||
|
tabindex="-1"
|
||||||
|
icon="cancel"
|
||||||
|
color="negative"
|
||||||
|
class="q-pa-none q-mr-xs"
|
||||||
|
@click="$emit('requestDelete')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -2,103 +2,123 @@
|
||||||
setup
|
setup
|
||||||
lang="ts"
|
lang="ts"
|
||||||
>
|
>
|
||||||
/* eslint-disable */
|
|
||||||
import { date } from 'quasar';
|
import { date } from 'quasar';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
import ShiftListRow from 'src/modules/timesheets/components/shift-list-row.vue';
|
import ShiftListRow from 'src/modules/timesheets/components/shift-list-row.vue';
|
||||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||||
import { type Shift } from 'src/modules/timesheets/models/shift.models';
|
import { useShiftApi } from 'src/modules/timesheets/composables/use-shift-api';
|
||||||
import { computed } from 'vue';
|
import { Shift } from 'src/modules/timesheets/models/shift.models';
|
||||||
|
import { useUiStore } from 'src/stores/ui-store';
|
||||||
|
|
||||||
|
const q = useQuasar();
|
||||||
|
const ui_store = useUiStore();
|
||||||
const timesheet_store = useTimesheetStore();
|
const timesheet_store = useTimesheetStore();
|
||||||
|
const shift_api = useShiftApi();
|
||||||
|
|
||||||
const { dense = false } = defineProps<{
|
const { dense = false } = defineProps<{
|
||||||
dense?: boolean;
|
dense?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const is_mobile = computed(() => q.screen.lt.md);
|
||||||
const date_font_size = computed(() => dense ? '1.5em' : '2.5em');
|
const date_font_size = computed(() => dense ? '1.5em' : '2.5em');
|
||||||
const weekday_font_size = computed(() => dense ? '0.55em;' : '0.7em;');
|
const weekday_font_size = computed(() => dense ? '0.55em;' : '0.7em;');
|
||||||
const date_box_size = computed(() => dense ? 'width: 50px;' : 'width: 75px;');
|
const date_box_size = computed(() => dense || is_mobile.value ? 'width: 40px; height: 75px;' : 'width: 75px; height: 75px;');
|
||||||
|
|
||||||
|
const addNewShift = (day_shifts: Shift[], date: string, timesheet_id: number) => {
|
||||||
|
ui_store.focus_next_component = true;
|
||||||
// const get_date_from_short = (short_date: string): Date => {
|
const new_shift = new Shift;
|
||||||
// if (timesheet_store.pay_period === undefined) return new Date();
|
new_shift.date = date;
|
||||||
// return new Date(timesheet_store.pay_period.pay_year.toString() + '/' + short_date);
|
new_shift.timesheet_id = timesheet_id;
|
||||||
// };
|
day_shifts.push(new_shift);
|
||||||
|
|
||||||
// const to_iso_date = (short_date: string): string => {
|
|
||||||
// return date.formatDate(get_date_from_short(short_date), 'YYYY-MM-DD');
|
|
||||||
// };
|
|
||||||
|
|
||||||
const shifts_or_placeholder = (shifts: Shift[]): Shift[] => {
|
|
||||||
return shifts.length > 0 ? shifts : [];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteCurrentShift = async (shift: Shift) => {
|
||||||
|
console.log('shift to delete: ', shift);
|
||||||
|
if (shift.shift_id < 0) {
|
||||||
|
shift.shift_id = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await shift_api.deleteShiftById(shift.shift_id);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<div :class="$q.screen.lt.md ? 'column' : 'row'">
|
||||||
<div
|
<div
|
||||||
v-for="week, index in timesheet_store.timesheets"
|
v-for="timesheet in timesheet_store.timesheets"
|
||||||
:key="index"
|
:key="timesheet.timesheet_id"
|
||||||
class="column col q-mx-xs"
|
class="col column"
|
||||||
>
|
>
|
||||||
<div class="col row shadow-2 rounded-10 q-my-xs">
|
<div
|
||||||
|
v-for="day in timesheet.days"
|
||||||
<q-card
|
:key="day.date"
|
||||||
v-for="day, day_index in week.days"
|
class="col-auto row shadow-2 rounded-10 q-ma-xs"
|
||||||
:key="day_index + index"
|
>
|
||||||
class="row col items-center no-shadow"
|
<div
|
||||||
|
class="col row bg-dark"
|
||||||
style="border-radius: 10px 0 0 10px;"
|
style="border-radius: 10px 0 0 10px;"
|
||||||
>
|
>
|
||||||
|
|
||||||
<!-- Dates column -->
|
<!-- Dates column -->
|
||||||
<q-card-section class="col-auto q-pa-none text-white">
|
|
||||||
<div
|
<div
|
||||||
class="bg-primary rounded-10 q-pa-xs text-center q-ml-sm"
|
class="col-auto column flex-center bg-primary rounded-10 text-center q-ma-sm self-center"
|
||||||
|
:class="$q.screen.lt.md ? '' : ''"
|
||||||
:style="date_box_size"
|
:style="date_box_size"
|
||||||
>
|
>
|
||||||
<q-item-label
|
<span
|
||||||
v-if="!dense"
|
v-if="!dense"
|
||||||
|
class="col-auto text-uppercase text-white"
|
||||||
:style="'font-size: ' + weekday_font_size"
|
:style="'font-size: ' + weekday_font_size"
|
||||||
class="text-uppercase"
|
>
|
||||||
>{{ $d(date.extractDate(day.date, 'YYYY-MM-DD'), { weekday: $q.screen.lt.md ? 'short' : 'long' })
|
{{ $d(date.extractDate(day.date, 'YYYY-MM-DD'), {
|
||||||
}}</q-item-label>
|
weekday: $q.screen.lt.md ? 'short' :
|
||||||
<q-item-label
|
'long'
|
||||||
class="text-weight-bolder"
|
})
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="col-auto text-weight-bolder text-grey-1"
|
||||||
:style="'font-size: ' + date_font_size + '; line-height: 90% !important;'"
|
:style="'font-size: ' + date_font_size + '; line-height: 90% !important;'"
|
||||||
>{{ date.extractDate(day.date, 'YYYY-MM-DD').getDate() }}</q-item-label>
|
>
|
||||||
<q-item-label
|
{{ date.extractDate(day.date, 'YYYY-MM-DD').getDate() }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="col-auto text-uppercase text-white"
|
||||||
:style="'font-size: ' + weekday_font_size"
|
:style="'font-size: ' + weekday_font_size"
|
||||||
class="text-uppercase"
|
>
|
||||||
>{{ $d(date.extractDate(day.date, 'YYYY-MM-DD'), { month: $q.screen.lt.md ? 'short' : 'long' })
|
{{ $d(date.extractDate(day.date, 'YYYY-MM-DD'), {
|
||||||
}}</q-item-label>
|
month: $q.screen.lt.md ? 'short' : 'long'
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<!-- List of shifts column -->
|
<!-- List of shifts column -->
|
||||||
<q-card-section class="col column q-pa-none full-height">
|
<div class="col column">
|
||||||
<div
|
|
||||||
v-if="day.shifts.length > 0"
|
|
||||||
class="col-grow column justify-center"
|
|
||||||
>
|
|
||||||
<ShiftListRow
|
<ShiftListRow
|
||||||
v-for="shift, shift_index in shifts_or_placeholder(day.shifts)"
|
v-for="shift, shift_index in day.shifts"
|
||||||
:key="shift_index"
|
:key="shift_index"
|
||||||
class="col"
|
v-model:shift="day.shifts[shift_index]!"
|
||||||
:dense="dense"
|
:dense="dense"
|
||||||
:shift="shift"
|
@request-delete="deleteCurrentShift(shift)"
|
||||||
@request-update=""
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</div>
|
||||||
</q-card>
|
|
||||||
|
|
||||||
|
<div class="col-auto self-stretch">
|
||||||
<q-btn
|
<q-btn
|
||||||
unelevated
|
unelevated
|
||||||
class="col-auto bg-primary"
|
|
||||||
icon="more_time"
|
icon="more_time"
|
||||||
size="lg"
|
:size="$q.screen.lt.md ? 'md' : 'lg'"
|
||||||
|
color="primary"
|
||||||
text-color="white"
|
text-color="white"
|
||||||
|
class="full-height"
|
||||||
|
:class="$q.screen.lt.md ? 'q-px-xs' : ''"
|
||||||
style="border-radius: 0 10px 10px 0;"
|
style="border-radius: 0 10px 10px 0;"
|
||||||
|
@click="addNewShift(day.shifts, day.date, timesheet.timesheet_id)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -2,17 +2,18 @@
|
||||||
setup
|
setup
|
||||||
lang="ts"
|
lang="ts"
|
||||||
>
|
>
|
||||||
import GenericLoader from 'src/modules/shared/components/generic-loader.vue';
|
|
||||||
import ShiftList from 'src/modules/timesheets/components/shift-list.vue';
|
import ShiftList from 'src/modules/timesheets/components/shift-list.vue';
|
||||||
import ExpenseDialog from 'src/modules/timesheets/components/expense-dialog.vue';
|
import ExpenseDialog from 'src/modules/timesheets/components/expense-dialog.vue';
|
||||||
import PayPeriodNavigator from 'src/modules/shared/components/pay-period-navigator.vue';
|
import PayPeriodNavigator from 'src/modules/shared/components/pay-period-navigator.vue';
|
||||||
import ShiftListLegend from 'src/modules/timesheets/components/shift-list-legend.vue';
|
// import ShiftListLegend from 'src/modules/timesheets/components/shift-list-legend.vue';
|
||||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||||
import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api';
|
import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api';
|
||||||
import { useExpensesStore } from 'src/stores/expense-store';
|
import { useExpensesStore } from 'src/stores/expense-store';
|
||||||
import { provide } from 'vue';
|
import { provide } from 'vue';
|
||||||
|
import { useShiftApi } from 'src/modules/timesheets/composables/use-shift-api';
|
||||||
|
|
||||||
const { open } = useExpensesStore();
|
const { open } = useExpensesStore();
|
||||||
|
const shift_api = useShiftApi();
|
||||||
|
|
||||||
const { employeeEmail, dense = false } = defineProps<{
|
const { employeeEmail, dense = false } = defineProps<{
|
||||||
employeeEmail: string;
|
employeeEmail: string;
|
||||||
|
|
@ -27,21 +28,28 @@
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="column flex-center full-width">
|
<div class="column flex-center full-width">
|
||||||
<GenericLoader
|
|
||||||
:is-loading="timesheet_store.is_loading"
|
<q-dialog
|
||||||
class="col-auto text-center"
|
v-model="timesheet_store.is_loading"
|
||||||
|
transition-show="jump-down"
|
||||||
|
transition-hide="jump-down"
|
||||||
|
>
|
||||||
|
<q-card class="q-pa-xl rounded-200 bg-white frosted-glass">
|
||||||
|
<q-spinner-radio
|
||||||
|
color="primary"
|
||||||
|
size="20vh"
|
||||||
/>
|
/>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
|
||||||
<q-card
|
<q-card
|
||||||
v-if="!timesheet_store.is_loading"
|
|
||||||
flat
|
flat
|
||||||
class="transparent full-width"
|
class="transparent full-width"
|
||||||
>
|
>
|
||||||
|
|
||||||
<q-card-section
|
<q-card-section
|
||||||
v-if="!dense"
|
v-if="!dense"
|
||||||
:horizontal="$q.screen.gt.sm"
|
:horizontal="$q.screen.gt.sm"
|
||||||
class="q-px-md items-center"
|
class="q-px-md items-center q-mb-md"
|
||||||
:class="$q.screen.lt.md ? 'column' : ''"
|
:class="$q.screen.lt.md ? 'column' : ''"
|
||||||
>
|
>
|
||||||
<!-- navigation btn -->
|
<!-- navigation btn -->
|
||||||
|
|
@ -65,9 +73,21 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- shift's colored legend -->
|
<!-- shift's colored legend -->
|
||||||
<ShiftListLegend :is-loading="false" />
|
<!-- <ShiftListLegend :is-loading="false" /> -->
|
||||||
|
|
||||||
<q-space />
|
<q-space />
|
||||||
|
<!-- save timesheet changes button -->
|
||||||
|
<q-btn
|
||||||
|
v-if="$q.screen.gt.sm"
|
||||||
|
push
|
||||||
|
rounded
|
||||||
|
:disable="timesheet_store.is_loading"
|
||||||
|
color="primary"
|
||||||
|
icon="upload"
|
||||||
|
:label="$t('shared.label.save')"
|
||||||
|
class="q-mr-md"
|
||||||
|
@click="shift_api.saveShiftChanges"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- desktop expenses button -->
|
<!-- desktop expenses button -->
|
||||||
<q-btn
|
<q-btn
|
||||||
|
|
@ -82,14 +102,8 @@
|
||||||
|
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
<q-card-section
|
|
||||||
:horizontal="$q.screen.gt.sm"
|
|
||||||
class="bg-secondary q-pa-sm rounded-10"
|
|
||||||
>
|
|
||||||
<ShiftList :dense="dense" />
|
<ShiftList :dense="dense" />
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
||||||
<ExpenseDialog />
|
<ExpenseDialog />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
39
src/modules/timesheets/composables/use-shift-api.ts
Normal file
39
src/modules/timesheets/composables/use-shift-api.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { useAuthStore } from "src/stores/auth-store";
|
||||||
|
import { useShiftStore } from "src/stores/shift-store";
|
||||||
|
import { useTimesheetStore } from "src/stores/timesheet-store";
|
||||||
|
|
||||||
|
export const useShiftApi = () => {
|
||||||
|
const timesheet_store = useTimesheetStore();
|
||||||
|
const shift_store = useShiftStore();
|
||||||
|
const auth_store = useAuthStore();
|
||||||
|
|
||||||
|
const deleteShiftById = async (shift_id: number) => {
|
||||||
|
timesheet_store.is_loading = true;
|
||||||
|
const success = await shift_store.deleteShiftById(shift_id);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
await timesheet_store.getTimesheetsByEmployeeEmail(auth_store.user?.email ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
timesheet_store.is_loading = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveShiftChanges = async () => {
|
||||||
|
timesheet_store.is_loading = true;
|
||||||
|
const create_success = await shift_store.createNewShifts();
|
||||||
|
|
||||||
|
if (create_success) {
|
||||||
|
const update_success = await shift_store.updateShifts();
|
||||||
|
|
||||||
|
if (update_success) {
|
||||||
|
await timesheet_store.getTimesheetsByEmployeeEmail(auth_store.user?.email ?? '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timesheet_store.is_loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
deleteShiftById,
|
||||||
|
saveShiftChanges,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -6,21 +6,29 @@ export const useTimesheetApi = () => {
|
||||||
const auth_store = useAuthStore();
|
const auth_store = useAuthStore();
|
||||||
|
|
||||||
const getTimesheetsByDate = async (date_string: string, employee_email?: string) => {
|
const getTimesheetsByDate = async (date_string: string, employee_email?: string) => {
|
||||||
|
timesheet_store.is_loading = true;
|
||||||
const success = await timesheet_store.getPayPeriodByDateOrYearAndNumber(date_string);
|
const success = await timesheet_store.getPayPeriodByDateOrYearAndNumber(date_string);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
await timesheet_store.getTimesheetsByEmployeeEmail(employee_email ?? auth_store.user?.email ?? '');
|
await timesheet_store.getTimesheetsByEmployeeEmail(employee_email ?? auth_store.user?.email ?? '');
|
||||||
|
timesheet_store.is_loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
timesheet_store.is_loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTimesheetsByCurrentPayPeriod = async (employee_email?: string) => {
|
const getTimesheetsByCurrentPayPeriod = async (employee_email?: string) => {
|
||||||
if (timesheet_store.pay_period === undefined) return false;
|
if (timesheet_store.pay_period === undefined) return false;
|
||||||
|
|
||||||
|
timesheet_store.is_loading = true;
|
||||||
const success = await timesheet_store.getPayPeriodByDateOrYearAndNumber(timesheet_store.pay_period.pay_year, timesheet_store.pay_period.pay_period_no );
|
const success = await timesheet_store.getPayPeriodByDateOrYearAndNumber(timesheet_store.pay_period.pay_year, timesheet_store.pay_period.pay_period_no );
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
await timesheet_store.getTimesheetsByEmployeeEmail(employee_email ?? auth_store.user?.email ?? '');
|
await timesheet_store.getTimesheetsByEmployeeEmail(employee_email ?? auth_store.user?.email ?? '');
|
||||||
|
timesheet_store.is_loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
timesheet_store.is_loading = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,12 @@ export const SHIFT_TYPES: ShiftType[] = [
|
||||||
'REGULAR',
|
'REGULAR',
|
||||||
'EVENING',
|
'EVENING',
|
||||||
'EMERGENCY',
|
'EMERGENCY',
|
||||||
'OVERTIME',
|
|
||||||
'HOLIDAY',
|
'HOLIDAY',
|
||||||
'VACATION',
|
'VACATION',
|
||||||
'SICK'
|
'SICK'
|
||||||
];
|
];
|
||||||
|
|
||||||
export type ShiftType = 'REGULAR' | 'EVENING' | 'EMERGENCY' | 'OVERTIME' | 'HOLIDAY' | 'VACATION' | 'SICK' ;
|
export type ShiftType = 'REGULAR' | 'EVENING' | 'EMERGENCY' | 'HOLIDAY' | 'VACATION' | 'SICK';
|
||||||
|
|
||||||
export type ShiftLegendItem = {
|
export type ShiftLegendItem = {
|
||||||
type: ShiftType;
|
type: ShiftType;
|
||||||
|
|
@ -17,8 +16,9 @@ export type ShiftLegendItem = {
|
||||||
text_color?: string;
|
text_color?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface Shift {
|
export class Shift {
|
||||||
id: number;
|
shift_id: number;
|
||||||
|
timesheet_id: number;
|
||||||
date: string; //YYYY-MM-DD
|
date: string; //YYYY-MM-DD
|
||||||
type: ShiftType;
|
type: ShiftType;
|
||||||
start_time: string; //HH:mm:ss
|
start_time: string; //HH:mm:ss
|
||||||
|
|
@ -26,4 +26,21 @@ export interface Shift {
|
||||||
comment: string | undefined;
|
comment: string | undefined;
|
||||||
is_approved: boolean;
|
is_approved: boolean;
|
||||||
is_remote: boolean;
|
is_remote: boolean;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.shift_id = -1;
|
||||||
|
this.timesheet_id = -1;
|
||||||
|
this.date = '';
|
||||||
|
this.type = 'REGULAR';
|
||||||
|
this.start_time = '';
|
||||||
|
this.end_time = '';
|
||||||
|
this.comment = undefined;
|
||||||
|
this.is_approved = false;
|
||||||
|
this.is_remote = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NewShift {
|
||||||
|
timesheet_id: number;
|
||||||
|
shifts: Shift[];
|
||||||
}
|
}
|
||||||
|
|
@ -4,8 +4,13 @@ import type { Expense } from "src/modules/timesheets/models/expense.models";
|
||||||
export const TIME_FORMAT_PATTERN = /^(\d{2}:\d{2})?$/;
|
export const TIME_FORMAT_PATTERN = /^(\d{2}:\d{2})?$/;
|
||||||
export const DATE_FORMAT_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
export const DATE_FORMAT_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
||||||
|
|
||||||
|
export interface TimesheetResponse {
|
||||||
|
employee_full_name: string;
|
||||||
|
timesheets: Timesheet[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface Timesheet {
|
export interface Timesheet {
|
||||||
id: number;
|
timesheet_id: number;
|
||||||
is_approved: boolean;
|
is_approved: boolean;
|
||||||
weekly_hours: TotalHours;
|
weekly_hours: TotalHours;
|
||||||
weekly_expenses: TotalExpenses;
|
weekly_expenses: TotalExpenses;
|
||||||
|
|
@ -36,77 +41,77 @@ export interface TotalExpenses {
|
||||||
mileage: number;
|
mileage: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const test_timesheets: Timesheet[] = [
|
// export const test_timesheets: Timesheet[] = [
|
||||||
{
|
// {
|
||||||
id: 1,
|
// timehsid: 1,
|
||||||
is_approved: false,
|
// is_approved: false,
|
||||||
weekly_hours: { regular: 8, evening: 0, emergency: 0, overtime: 0, vacation: 0, holiday: 0, sick: 0, absent: 0 },
|
// weekly_hours: { regular: 8, evening: 0, emergency: 0, overtime: 0, vacation: 0, holiday: 0, sick: 0, absent: 0 },
|
||||||
weekly_expenses: { expenses: 15.5, mileage: 0 },
|
// weekly_expenses: { expenses: 15.5, mileage: 0 },
|
||||||
days: [
|
// days: [
|
||||||
{
|
// {
|
||||||
date: '2025-10-18',
|
// date: '2025-10-18',
|
||||||
daily_hours: { regular: 8, evening: 0, emergency: 0, overtime: 0, vacation: 0, holiday: 0, sick: 0, absent: 0 },
|
// daily_hours: { regular: 8, evening: 0, emergency: 0, overtime: 0, vacation: 0, holiday: 0, sick: 0, absent: 0 },
|
||||||
daily_expenses: { expenses: 15.5, mileage: 0 },
|
// daily_expenses: { expenses: 15.5, mileage: 0 },
|
||||||
shifts: [
|
// shifts: [
|
||||||
{ id: 101, date: '2025-01-06', type: 'REGULAR', start_time: '08:00', end_time: '12:00', comment: 'blah', is_approved: false, is_remote: false, },
|
// { id: 101, date: '2025-01-06', type: 'REGULAR', start_time: '08:00', end_time: '12:00', comment: 'blah', is_approved: false, is_remote: false, },
|
||||||
{ id: 102, date: '2025-01-06', type: 'REGULAR', start_time: '13:00', end_time: '17:00', comment: undefined, is_approved: false, is_remote: false, },
|
// { id: 102, date: '2025-01-06', type: 'REGULAR', start_time: '13:00', end_time: '17:00', comment: undefined, is_approved: false, is_remote: false, },
|
||||||
],
|
// ],
|
||||||
expenses: [
|
// expenses: [
|
||||||
{ id: 201, date: '2025-01-06', type: 'EXPENSES', amount: 15.5, comment: 'Lunch receipt', is_approved: false, },
|
// { id: 201, date: '2025-01-06', type: 'EXPENSES', amount: 15.5, comment: 'Lunch receipt', is_approved: false, },
|
||||||
],
|
// ],
|
||||||
},
|
// },
|
||||||
],
|
// ],
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
id: 2,
|
// id: 2,
|
||||||
is_approved: true,
|
// is_approved: true,
|
||||||
weekly_hours: {
|
// weekly_hours: {
|
||||||
regular: 0,
|
// regular: 0,
|
||||||
evening: 0,
|
// evening: 0,
|
||||||
emergency: 0,
|
// emergency: 0,
|
||||||
overtime: 8,
|
// overtime: 8,
|
||||||
vacation: 0,
|
// vacation: 0,
|
||||||
holiday: 0,
|
// holiday: 0,
|
||||||
sick: 0,
|
// sick: 0,
|
||||||
absent: 0,
|
// absent: 0,
|
||||||
},
|
// },
|
||||||
weekly_expenses: {
|
// weekly_expenses: {
|
||||||
expenses: 0,
|
// expenses: 0,
|
||||||
mileage: 32.4,
|
// mileage: 32.4,
|
||||||
},
|
// },
|
||||||
days: [
|
// days: [
|
||||||
{
|
// {
|
||||||
date: '2025-10-27',
|
// date: '2025-10-27',
|
||||||
daily_hours: {
|
// daily_hours: {
|
||||||
regular: 0,
|
// regular: 0,
|
||||||
evening: 0,
|
// evening: 0,
|
||||||
emergency: 0,
|
// emergency: 0,
|
||||||
overtime: 8,
|
// overtime: 8,
|
||||||
vacation: 0,
|
// vacation: 0,
|
||||||
holiday: 0,
|
// holiday: 0,
|
||||||
sick: 0,
|
// sick: 0,
|
||||||
absent: 0,
|
// absent: 0,
|
||||||
},
|
// },
|
||||||
daily_expenses: {
|
// daily_expenses: {
|
||||||
expenses: 0,
|
// expenses: 0,
|
||||||
mileage: 32.4,
|
// mileage: 32.4,
|
||||||
},
|
// },
|
||||||
shifts: [
|
// shifts: [
|
||||||
{ id: 101, date: '2025-10-27', type: 'REGULAR', start_time: '08:00', end_time: '12:00', comment: undefined, is_approved: false, is_remote: false, },
|
// { id: 101, date: '2025-10-27', type: 'REGULAR', start_time: '08:00', end_time: '12:00', comment: undefined, is_approved: false, is_remote: false, },
|
||||||
{ id: 102, date: '2025-10-27', type: 'REGULAR', start_time: '13:00', end_time: '17:00', comment: undefined, is_approved: false, is_remote: false, },
|
// { id: 102, date: '2025-10-27', type: 'REGULAR', start_time: '13:00', end_time: '17:00', comment: undefined, is_approved: false, is_remote: false, },
|
||||||
],
|
// ],
|
||||||
expenses: [
|
// expenses: [
|
||||||
{
|
// {
|
||||||
id: 202,
|
// id: 202,
|
||||||
date: '2025-10-27',
|
// date: '2025-10-27',
|
||||||
type: 'MILEAGE',
|
// type: 'MILEAGE',
|
||||||
amount: 0,
|
// amount: 0,
|
||||||
mileage: 32.4,
|
// mileage: 32.4,
|
||||||
comment: 'Travel to client site',
|
// comment: 'Travel to client site',
|
||||||
is_approved: true,
|
// is_approved: true,
|
||||||
},
|
// },
|
||||||
],
|
// ],
|
||||||
},
|
// },
|
||||||
],
|
// ],
|
||||||
},
|
// },
|
||||||
];
|
// ];
|
||||||
|
|
|
||||||
13
src/modules/timesheets/services/expense-service.ts
Normal file
13
src/modules/timesheets/services/expense-service.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { api } from "src/boot/axios";
|
||||||
|
|
||||||
|
export const ExpenseService = {
|
||||||
|
getExpensesByTimesheetId: async (timesheet_id: number) => {
|
||||||
|
const response = await api.get(`timesheet/${timesheet_id}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
upsertOrDeleteExpenseById: async (expense_id: number) => {
|
||||||
|
const response = await api.post(`epxense/${expense_id}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
};
|
||||||
24
src/modules/timesheets/services/shift-service.ts
Normal file
24
src/modules/timesheets/services/shift-service.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
import { api } from "src/boot/axios";
|
||||||
|
import type { Shift } from "src/modules/timesheets/models/shift.models";
|
||||||
|
|
||||||
|
export const ShiftService = {
|
||||||
|
deleteShiftById: async (shift_id: number) => {
|
||||||
|
const response = await api.delete(`/shift/${shift_id}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
createNewShifts: async (new_shifts: Shift[]) => {
|
||||||
|
// const response = await api.post(`/shift/`, { dtos: new_shifts });
|
||||||
|
// return response;
|
||||||
|
console.log('create shift payload: ', new_shifts);
|
||||||
|
return {status: 200};
|
||||||
|
},
|
||||||
|
|
||||||
|
updateShifts: async (existing_shifts: Shift[]) => {
|
||||||
|
// const response = await api.patch(`/shift/`, { dtos: existing_shifts });
|
||||||
|
// return response;
|
||||||
|
console.log('update shift payload: ', existing_shifts);
|
||||||
|
return {status: 200};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
import { api } from "src/boot/axios";
|
import { api } from "src/boot/axios";
|
||||||
import type { PayPeriod } from "src/modules/shared/models/pay-period.models";
|
import type { PayPeriod } from "src/modules/shared/models/pay-period.models";
|
||||||
import type { Timesheet } from "src/modules/timesheets/models/timesheet.models";
|
import type { TimesheetResponse } from "src/modules/timesheets/models/timesheet.models";
|
||||||
import type { TimesheetOverview } from "src/modules/timesheet-approval/models/timesheet-overview.models";
|
import type { TimesheetOverview } from "src/modules/timesheet-approval/models/timesheet-overview.models";
|
||||||
import type { Expense } from "src/modules/timesheets/models/expense.models";
|
|
||||||
import { Shift } from "src/modules/timesheets/models/shift.models";
|
|
||||||
|
|
||||||
export const timesheetService = {
|
export const timesheetService = {
|
||||||
getPayPeriodByDate: async (date_string: string): Promise<PayPeriod> => {
|
getPayPeriodByDate: async (date_string: string): Promise<PayPeriod> => {
|
||||||
|
|
@ -23,28 +19,8 @@ export const timesheetService = {
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
getTimesheetsByPayPeriodAndEmployeeEmail: async (employee_email: string, year: number, period_number: number): Promise<Timesheet[]> => {
|
getTimesheetsByPayPeriodAndEmployeeEmail: async (employee_email: string, year: number, period_number: number): Promise<TimesheetResponse> => {
|
||||||
const response = await api.get('timesheets', { params: { employee_email, year, period_number } });
|
const response = await api.get('timesheets', { params: { employee_email, year, period_number } });
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
getExpensesByTimesheetId: async (timesheet_id: number): Promise<Expense[]> => {
|
|
||||||
const response = await api.get(`/expenses/list/${timesheet_id}`);
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
upsertShiftsByEmployeeEmailAndAction: async (email: string, payload: Shift[]): Promise<Timesheet[]> => {
|
|
||||||
const response = await api.put(`/shifts/upsert/${email}`, payload);
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteShiftsByEmployeeEmailAndDate: async (email: string, date: string, payload: Shift[]) => {
|
|
||||||
const response = await api.delete(`/shifts/delete/${email}/${date}`, { data: payload });
|
|
||||||
return response;
|
|
||||||
},
|
|
||||||
|
|
||||||
upsertOrDeleteExpenseByEmailAndExpenseId: async (email: string, expense_id: number): Promise<Timesheet[]> => {
|
|
||||||
const response = await api.put(`/expenses/upsert/${email}`, expense_id);
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
@ -33,13 +33,10 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="col column items-center"
|
class="col"
|
||||||
:style="$q.screen.gt.sm ? 'width: 90vw' : ''"
|
:style="$q.screen.gt.sm ? 'width: 90vw' : ''"
|
||||||
>
|
>
|
||||||
<TimesheetWrapper
|
<TimesheetWrapper :employee-email="user?.email ?? ''" />
|
||||||
class="col-auto"
|
|
||||||
:employee-email="user?.email ?? ''"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</q-page>
|
</q-page>
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ export const useAuthStore = defineStore('auth', () => {
|
||||||
void handleAuthMessage(event);
|
void handleAuthMessage(event);
|
||||||
});
|
});
|
||||||
|
|
||||||
const oidc_popup = window.open('http://localhost:3000/auth/v1/login', 'authPopup', 'width=600,height=800');
|
const oidc_popup = window.open(`${import.meta.env.VITE_TARGO_BACKEND_AUTH_URL}auth/v1/login`, 'authPopup', 'width=600,height=800');
|
||||||
|
|
||||||
if (!oidc_popup)
|
if (!oidc_popup)
|
||||||
Notify.create({
|
Notify.create({
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { timesheetService } from "src/modules/timesheets/services/timesheet-service";
|
|
||||||
import { ExpensesApiError, type GenericApiError } from "src/modules/timesheets/models/expense-validation.models";
|
import { ExpensesApiError, type GenericApiError } from "src/modules/timesheets/models/expense-validation.models";
|
||||||
import { empty_expense, test_expenses, type Expense } from "src/modules/timesheets/models/expense.models";
|
import { empty_expense, test_expenses, type Expense } from "src/modules/timesheets/models/expense.models";
|
||||||
|
import { ExpenseService } from "src/modules/timesheets/services/expense-service";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ export const useExpensesStore = defineStore('expenses', () => {
|
||||||
error.value = null;
|
error.value = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const expenses = await timesheetService.getExpensesByTimesheetId(timesheet_id);
|
const expenses = await ExpenseService.getExpensesByTimesheetId(timesheet_id);
|
||||||
pay_period_expenses.value = expenses;
|
pay_period_expenses.value = expenses;
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
if (typeof err === 'object') {
|
if (typeof err === 'object') {
|
||||||
|
|
@ -62,15 +62,12 @@ export const useExpensesStore = defineStore('expenses', () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const upsertOrDeleteExpensesById = async (employee_email: string, date: string, expense_id: number): Promise<void> => {
|
const upsertOrDeleteExpensesById = async (expense_id: number): Promise<void> => {
|
||||||
is_loading.value = true;
|
is_loading.value = true;
|
||||||
error.value = null;
|
error.value = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await timesheetService.upsertOrDeleteExpenseByEmailAndExpenseId(
|
await ExpenseService.upsertOrDeleteExpenseById(expense_id);
|
||||||
encodeURIComponent(employee_email),
|
|
||||||
expense_id,
|
|
||||||
);
|
|
||||||
// TODO: Save response data into proper ref
|
// TODO: Save response data into proper ref
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// setErrorFrom(err);
|
// setErrorFrom(err);
|
||||||
|
|
|
||||||
71
src/stores/shift-store.ts
Normal file
71
src/stores/shift-store.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { ShiftService } from "src/modules/timesheets/services/shift-service";
|
||||||
|
import { useTimesheetStore } from "src/stores/timesheet-store";
|
||||||
|
import { Notify } from "quasar";
|
||||||
|
|
||||||
|
export const useShiftStore = defineStore('shift_store', () => {
|
||||||
|
const timesheet_store = useTimesheetStore();
|
||||||
|
const shift_error = ref();
|
||||||
|
|
||||||
|
const deleteShiftById = async (shift_id: number): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
await ShiftService.deleteShiftById(shift_id);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('DEV ERROR || error while deleting shift: ', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const createNewShifts = async (): Promise<boolean> => {
|
||||||
|
if (timesheet_store.timesheets === undefined) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const new_shifts = timesheet_store.timesheets.flatMap(week => week.days).flatMap(day => day.shifts).filter(shift => shift.shift_id < 0);
|
||||||
|
|
||||||
|
if (new_shifts?.length > 0) {
|
||||||
|
const response = await ShiftService.createNewShifts(new_shifts);
|
||||||
|
if (response.status <= 200) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('No new shifts to save');
|
||||||
|
Notify.create('no new shifts to save')
|
||||||
|
return false;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating new shifts: ', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateShifts = async (): Promise<boolean> => {
|
||||||
|
if (timesheet_store.timesheets === undefined) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const existing_shifts = timesheet_store.timesheets.flatMap(week => week.days).flatMap(day => day.shifts).filter(shift => shift.shift_id > 0);
|
||||||
|
|
||||||
|
if (existing_shifts?.length > 0) {
|
||||||
|
const response = await ShiftService.updateShifts(existing_shifts);
|
||||||
|
if (response.status <= 200) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('No shifts to update');
|
||||||
|
Notify.create('no shifts to update')
|
||||||
|
return false;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating shifts: ', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
shift_error,
|
||||||
|
deleteShiftById,
|
||||||
|
createNewShifts,
|
||||||
|
updateShifts,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
@ -1,28 +1,24 @@
|
||||||
|
import { ref } from 'vue';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { computed, ref } from 'vue';
|
|
||||||
import { withLoading } from 'src/utils/store-helpers';
|
|
||||||
import { useAuthStore } from 'src/stores/auth-store';
|
import { useAuthStore } from 'src/stores/auth-store';
|
||||||
import { timesheetApprovalService } from 'src/modules/timesheet-approval/services/timesheet-approval-service';
|
import { timesheetApprovalService } from 'src/modules/timesheet-approval/services/timesheet-approval-service';
|
||||||
import { timesheetService } from 'src/modules/timesheets/services/timesheet-service';
|
import { timesheetService } from 'src/modules/timesheets/services/timesheet-service';
|
||||||
import type { TimesheetOverview } from "src/modules/timesheet-approval/models/timesheet-overview.models";
|
import type { TimesheetOverview } from "src/modules/timesheet-approval/models/timesheet-overview.models";
|
||||||
import type { PayPeriod } from 'src/modules/shared/models/pay-period.models';
|
import type { PayPeriod } from 'src/modules/shared/models/pay-period.models';
|
||||||
import { test_timesheets, type Timesheet } from 'src/modules/timesheets/models/timesheet.models';
|
import type { Timesheet } from 'src/modules/timesheets/models/timesheet.models';
|
||||||
import type { TimesheetApprovalCSVReportFilters } from 'src/modules/timesheet-approval/models/timesheet-approval-csv-report.models';
|
import type { TimesheetApprovalCSVReportFilters } from 'src/modules/timesheet-approval/models/timesheet-approval-csv-report.models';
|
||||||
|
|
||||||
const auth_store = useAuthStore();
|
|
||||||
|
|
||||||
export const useTimesheetStore = defineStore('timesheet', () => {
|
export const useTimesheetStore = defineStore('timesheet', () => {
|
||||||
|
const auth_store = useAuthStore();
|
||||||
const is_loading = ref<boolean>(false);
|
const is_loading = ref<boolean>(false);
|
||||||
const pay_period = ref<PayPeriod>();
|
const pay_period = ref<PayPeriod>();
|
||||||
const pay_period_overviews = ref<TimesheetOverview[]>([]);
|
const pay_period_overviews = ref<TimesheetOverview[]>([]);
|
||||||
const current_pay_period_overview = ref<TimesheetOverview>();
|
const current_pay_period_overview = ref<TimesheetOverview>();
|
||||||
const timesheets = ref<Timesheet[]>(test_timesheets);
|
const timesheets = ref<Timesheet[]>();
|
||||||
const pay_period_report = ref();
|
const pay_period_report = ref();
|
||||||
const is_calendar_limit = computed(() => (pay_period.value?.pay_year === 2024 && pay_period.value?.pay_period_no <= 1) ?? false);
|
|
||||||
|
|
||||||
const getPayPeriodByDateOrYearAndNumber = async (date_or_year: string | number, period_number?: number): Promise<boolean> => {
|
const getPayPeriodByDateOrYearAndNumber = async (date_or_year: string | number, period_number?: number): Promise<boolean> => {
|
||||||
is_loading.value = true;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (typeof date_or_year === 'string') {
|
if (typeof date_or_year === 'string') {
|
||||||
pay_period.value = await timesheetService.getPayPeriodByDate(date_or_year);
|
pay_period.value = await timesheetService.getPayPeriodByDate(date_or_year);
|
||||||
|
|
@ -31,7 +27,6 @@ export const useTimesheetStore = defineStore('timesheet', () => {
|
||||||
pay_period.value = await timesheetService.getPayPeriodByYearAndPeriodNumber(date_or_year, period_number);
|
pay_period.value = await timesheetService.getPayPeriodByYearAndPeriodNumber(date_or_year, period_number);
|
||||||
}
|
}
|
||||||
else pay_period.value = undefined;
|
else pay_period.value = undefined;
|
||||||
is_loading.value = false;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -39,7 +34,6 @@ export const useTimesheetStore = defineStore('timesheet', () => {
|
||||||
pay_period.value = undefined;
|
pay_period.value = undefined;
|
||||||
pay_period_overviews.value = [];
|
pay_period_overviews.value = [];
|
||||||
//TODO: More in-depth error-handling here
|
//TODO: More in-depth error-handling here
|
||||||
is_loading.value = false;
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -66,10 +60,11 @@ export const useTimesheetStore = defineStore('timesheet', () => {
|
||||||
|
|
||||||
const getTimesheetsByEmployeeEmail = async (employee_email: string) => {
|
const getTimesheetsByEmployeeEmail = async (employee_email: string) => {
|
||||||
is_loading.value = true;
|
is_loading.value = true;
|
||||||
|
|
||||||
if (pay_period.value === undefined) return;
|
if (pay_period.value === undefined) return;
|
||||||
try {
|
try {
|
||||||
const response = await timesheetService.getTimesheetsByPayPeriodAndEmployeeEmail(employee_email, pay_period.value.pay_year, pay_period.value.pay_period_no);
|
const response = await timesheetService.getTimesheetsByPayPeriodAndEmployeeEmail(employee_email, pay_period.value.pay_year, pay_period.value.pay_period_no);
|
||||||
timesheets.value = response;
|
timesheets.value = response.timesheets;
|
||||||
is_loading.value = false;
|
is_loading.value = false;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('There was an error retrieving timesheet details for this employee: ', error);
|
console.error('There was an error retrieving timesheet details for this employee: ', error);
|
||||||
|
|
@ -80,7 +75,6 @@ export const useTimesheetStore = defineStore('timesheet', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPayPeriodReportByYearAndPeriodNumber = async (year: number, period_number: number, report_filters?: TimesheetApprovalCSVReportFilters) => {
|
const getPayPeriodReportByYearAndPeriodNumber = async (year: number, period_number: number, report_filters?: TimesheetApprovalCSVReportFilters) => {
|
||||||
return withLoading(is_loading.value, async () => {
|
|
||||||
try {
|
try {
|
||||||
const response = await timesheetApprovalService.getPayPeriodReportByYearAndPeriodNumber(
|
const response = await timesheetApprovalService.getPayPeriodReportByYearAndPeriodNumber(
|
||||||
year,
|
year,
|
||||||
|
|
@ -88,7 +82,6 @@ export const useTimesheetStore = defineStore('timesheet', () => {
|
||||||
report_filters
|
report_filters
|
||||||
);
|
);
|
||||||
pay_period_report.value = response;
|
pay_period_report.value = response;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('There was an error retrieving the report CSV: ', error);
|
console.error('There was an error retrieving the report CSV: ', error);
|
||||||
|
|
@ -96,12 +89,10 @@ export const useTimesheetStore = defineStore('timesheet', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
is_loading,
|
is_loading,
|
||||||
is_calendar_limit,
|
|
||||||
pay_period,
|
pay_period,
|
||||||
pay_period_overviews,
|
pay_period_overviews,
|
||||||
current_pay_period_overview,
|
current_pay_period_overview,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,22 @@
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { ref } from 'vue';
|
import { useQuasar } from 'quasar';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
|
||||||
export const useUiStore = defineStore('ui', () => {
|
export const useUiStore = defineStore('ui', () => {
|
||||||
const isRightDrawerOpen = ref(true);
|
const q = useQuasar();
|
||||||
|
const is_left_drawer_open = ref(true);
|
||||||
|
const focus_next_component = ref(false);
|
||||||
|
const is_mobile_mode = computed(() => q.screen.lt.md);
|
||||||
|
|
||||||
const toggleRightDrawer = () => {
|
const toggleRightDrawer = () => {
|
||||||
isRightDrawerOpen.value = !isRightDrawerOpen.value;
|
is_left_drawer_open.value = !is_left_drawer_open.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { isRightDrawerOpen, toggleRightDrawer };
|
return {
|
||||||
|
is_mobile_mode,
|
||||||
|
focus_next_component,
|
||||||
|
is_left_drawer_open,
|
||||||
|
toggleRightDrawer
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user