feat(timesheet): added pay-period interface to timesheet employee page
This commit is contained in:
parent
3be76b9c96
commit
5c0c9036c4
|
|
@ -267,6 +267,7 @@ export default {
|
|||
EMERGENCY: 'Emergency',
|
||||
EVENING: 'Evening',
|
||||
HOLIDAY: 'Holiday',
|
||||
OVERTIME: 'Overtime',
|
||||
REGULAR: 'Regular',
|
||||
SICK: 'Sick Leave',
|
||||
VACATION: 'Vacation',
|
||||
|
|
|
|||
|
|
@ -318,6 +318,7 @@ export default {
|
|||
EMERGENCY: 'Urgence',
|
||||
EVENING: 'Soir',
|
||||
HOLIDAY: 'Férier',
|
||||
OVERTIME: 'Supplémentaire',
|
||||
SICK: 'Absence',
|
||||
REGULAR: 'Régulier',
|
||||
VACATION: 'Vacance',
|
||||
|
|
|
|||
48
src/modules/timesheets/components/shift/shifts-legend.vue
Normal file
48
src/modules/timesheets/components/shift/shifts-legend.vue
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps<{ isLoading: boolean; }>();
|
||||
|
||||
type ShiftLegendItem = {
|
||||
type: 'REGULAR'|'EVENING'|'EMERGENCY'|'OVERTIME'|'VACATION'|'HOLIDAY'|'SICK';
|
||||
color: string;
|
||||
label_key: string;
|
||||
text_color?: string;
|
||||
};
|
||||
|
||||
const legend: ShiftLegendItem[] = [
|
||||
{type:'REGULAR' , color: 'secondary', label_key: 'timesheet.shift_types.REGULAR', text_color: 'grey-8'},
|
||||
{type:'EVENING' , color: 'warning' , label_key: 'timesheet.shift_types.EVENING'},
|
||||
{type:'EMERGENCY', color: 'amber-10' , label_key: 'timesheet.shift_types.EMERGENCY'},
|
||||
{type:'OVERTIME' , color: 'negative' , label_key: 'timesheet.shift_types.OVERTIME'},
|
||||
{type:'VACATION' , color: 'purple-10', label_key: 'timesheet.shift_types.VACATION'},
|
||||
{type:'HOLIDAY' , color: 'purple-8' , label_key: 'timesheet.shift_types.HOLIDAY'},
|
||||
{type:'SICK' , color: 'grey-8' , label_key: 'timesheet.shift_types.SICK'},
|
||||
]
|
||||
|
||||
const shift_type_legend = computed(()=>
|
||||
legend.map(item => ({ ...item, label: t(item.label_key)} ))
|
||||
);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-card class="q-px-xs q-pt-xs col rounded-10 q-mx-xs q-py-xs">
|
||||
<q-card-section
|
||||
class="q-py-xs q-pa-none text-center q-my-s"
|
||||
v-if="!props.isLoading"
|
||||
>
|
||||
<q-badge
|
||||
v-for="shift_type in shift_type_legend"
|
||||
:key="shift_type.type"
|
||||
:color="shift_type.color"
|
||||
:label="shift_type.label"
|
||||
:text-color="shift_type.text_color || 'white'"
|
||||
class="q-px-md q-py-xs q-mx-xs q-my-none text-uppercase text-weight-bolder justify-center"
|
||||
style="width: 120px; font-size: 0.8em;"
|
||||
/>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<q-card-section
|
||||
horizontal
|
||||
class="text-uppercase text-center items-center q-pa-none"
|
||||
>
|
||||
<!-- shift row itself -->
|
||||
<q-card-section class="col q-pa-none">
|
||||
<q-card-section horizontal class="col q-pa-none">
|
||||
<!-- punch-in timestamps -->
|
||||
<q-card-section class="col q-pa-none">
|
||||
<q-item-label class="text-weight-bolder text-primary" style="font-size: 0.7em;">
|
||||
{{ $t('shiftColumns.labelIn') }}
|
||||
</q-item-label>
|
||||
</q-card-section>
|
||||
|
||||
<!-- arrows pointing to punch-out timestamps -->
|
||||
<q-card-section class="col q-py-none q-px-sm">
|
||||
</q-card-section>
|
||||
|
||||
<!-- punch-out timestamps -->
|
||||
<q-card-section class="col q-pa-none">
|
||||
<q-item-label class="text-weight-bolder text-primary" style="font-size: 0.7em;">
|
||||
{{ $t('shiftColumns.labelOut') }}
|
||||
</q-item-label>
|
||||
</q-card-section>
|
||||
|
||||
<!-- comment button -->
|
||||
<q-card-section class="col column q-pa-none">
|
||||
</q-card-section>
|
||||
</q-card-section>
|
||||
</q-card-section>
|
||||
</q-card-section>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
<script setup lang="ts">
|
||||
import type { Shift } from 'src/modules/timesheets/types/timesheet-shift-interface';
|
||||
|
||||
const props = defineProps<{
|
||||
shift: Shift;
|
||||
}>();
|
||||
|
||||
const getShiftColor = (type: string): string => {
|
||||
switch(type) {
|
||||
case 'REGULAR': return 'secondary';
|
||||
case 'EVENING': return 'warning';
|
||||
case 'EMERGENCY': return 'amber-10';
|
||||
case 'OVERTIME': return 'negative';
|
||||
case 'VACATION': return 'purple-10';
|
||||
case 'HOLIDAY': return 'purple-10';
|
||||
case 'SICK': return 'grey-8';
|
||||
default : return 'transparent';
|
||||
}
|
||||
};
|
||||
|
||||
const getTextColor = (type: string): string => {
|
||||
switch(type) {
|
||||
case 'REGULAR': return 'grey-8';
|
||||
case '': return 'transparent';
|
||||
default: return 'white';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-card-section
|
||||
horizontal
|
||||
class="q-pa-none text-uppercase text-center items-center cursor-pointer rounded-10"
|
||||
style="line-height: 1;"
|
||||
>
|
||||
<!-- punch-in timestamps -->
|
||||
<q-card-section class="q-pa-none col">
|
||||
<q-item-label
|
||||
class="text-weight-bolder q-pa-xs rounded-5"
|
||||
:class="'bg-' + getShiftColor(props.shift.type) + ' text-' + getTextColor(props.shift.type)"
|
||||
style="font-size: 1.5em; line-height: 80% !important;"
|
||||
>
|
||||
{{ props.shift.start_time }}
|
||||
</q-item-label>
|
||||
</q-card-section>
|
||||
|
||||
<!-- arrows pointing to punch-out timestamps -->
|
||||
<q-card-section
|
||||
horizontal
|
||||
class="items-center justify-center q-mx-sm col"
|
||||
>
|
||||
<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="props.shift.type !== ''"
|
||||
name="double_arrow"
|
||||
:color="icon_data.color"
|
||||
size="24px"
|
||||
:style="icon_data.transform"
|
||||
/>
|
||||
</div>
|
||||
</q-card-section>
|
||||
|
||||
<!-- punch-out timestamps -->
|
||||
<q-card-section class="q-pa-none col">
|
||||
<q-item-label
|
||||
class="text-weight-bolder text-white q-pa-xs rounded-5"
|
||||
:class="'bg-' + getShiftColor(props.shift.type) + ' text-' + getTextColor(props.shift.type)"
|
||||
style="font-size: 1.5em; line-height: 80% !important;"
|
||||
>
|
||||
{{ props.shift.end_time }}
|
||||
</q-item-label>
|
||||
</q-card-section>
|
||||
|
||||
<!-- comment and expenses buttons -->
|
||||
<q-card-section
|
||||
class="col q-pa-none text-right"
|
||||
>
|
||||
<!-- chat_bubble_outline or announcement -->
|
||||
<q-btn
|
||||
v-if="props.shift.type !== ''"
|
||||
flat
|
||||
dense
|
||||
color='grey-8'
|
||||
icon="chat_bubble_outline"
|
||||
class="q-pa-none"
|
||||
/>
|
||||
|
||||
<!-- insert_drive_file or request_quote -->
|
||||
<q-btn
|
||||
v-if="props.shift.type !== ''"
|
||||
flat
|
||||
dense
|
||||
color='grey-8'
|
||||
icon="attach_money"
|
||||
class="q-pa-none q-mx-xs"
|
||||
/>
|
||||
</q-card-section>
|
||||
</q-card-section>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
<script setup lang="ts">
|
||||
import { default_shift, type Shift } from 'src/modules/timesheets/types/timesheet-shift-interface';
|
||||
import TimesheetEmployeeDetailsShiftsRowHeader from './timesheet-details-shifts-row-header.vue';
|
||||
import TimesheetEmployeeDetailsShiftsRow from './timesheet-details-shifts-row.vue';
|
||||
import type { PayPeriod } from 'src/modules/shared/types/pay-period-interface';
|
||||
import type { TimesheetPayPeriodDetailsOverview } from '../types/timesheet-pay-period-details-overview-interface';
|
||||
|
||||
const props = defineProps<{
|
||||
rawData: TimesheetPayPeriodDetailsOverview;
|
||||
currentPayPeriod: PayPeriod;
|
||||
}>();
|
||||
|
||||
const shifts_or_placeholder = (shifts: Shift[]): Shift[] => {
|
||||
return shifts.length > 0 ? shifts : [default_shift];
|
||||
};
|
||||
|
||||
const getDate = (shift_date: string): Date => {
|
||||
return new Date(props.currentPayPeriod.pay_year.toString() + '/' + shift_date);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-for="week, index in props.rawData"
|
||||
:key="index"
|
||||
class="q-px-xs q-pt-xs rounded-5 col"
|
||||
>
|
||||
<q-card
|
||||
v-for="day, day_index in week.shifts"
|
||||
:key="day_index"
|
||||
flat
|
||||
bordered
|
||||
class="row items-center rounded-10 q-mb-xs"
|
||||
>
|
||||
<q-card-section class="col-auto q-pa-xs text-white">
|
||||
<div
|
||||
class="bg-primary rounded-10 q-pa-xs text-center"
|
||||
:style="$q.screen.lt.md ? '' : 'width: 75px;'"
|
||||
>
|
||||
<q-item-label
|
||||
style="font-size: 0.7em;"
|
||||
class="text-uppercase"
|
||||
>{{ $d(getDate(day.short_date), {weekday: $q.screen.lt.md ? 'short' : 'long'}) }}</q-item-label>
|
||||
<q-item-label
|
||||
class="text-weight-bolder"
|
||||
style="font-size: 2.5em; line-height: 90% !important;"
|
||||
>{{ day.short_date.split('/')[1] }}</q-item-label>
|
||||
<q-item-label
|
||||
style="font-size: 0.7em;"
|
||||
class="text-uppercase"
|
||||
>{{ $d(getDate(day.short_date), {month: $q.screen.lt.md ? 'short' : 'long'}) }}</q-item-label>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-section class="col q-pa-none">
|
||||
<TimesheetEmployeeDetailsShiftsRowHeader />
|
||||
<TimesheetEmployeeDetailsShiftsRow
|
||||
v-for="shift, shift_index in shifts_or_placeholder(day.shifts)"
|
||||
:key="shift_index"
|
||||
:shift="shift"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section class="q-pr-xs col-auto">
|
||||
<q-btn
|
||||
push
|
||||
color="primary"
|
||||
icon="more_time"
|
||||
class="q-pa-sm"
|
||||
/>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -6,7 +6,7 @@ import { useTimesheetStore } from 'src/stores/timesheet-store';
|
|||
import TimesheetShiftComment from '../shift/timesheet-shift-comment.vue';
|
||||
import TimesheetSavePayload from './timesheet-save-payload.vue';
|
||||
import type { Shift } from '../../types/timesheet-shift-interface';
|
||||
import type { CreateShiftPayload } from '../../types/timesheet-shifts-payload-interface';
|
||||
import type{ CreateShiftPayload } from '../../types/timesheet-shifts-payload-interface';
|
||||
|
||||
const timesheet_store = useTimesheetStore();
|
||||
|
||||
|
|
@ -1,29 +1,78 @@
|
|||
<script setup lang="ts">
|
||||
/* eslint-disable */
|
||||
import { ref } from 'vue';
|
||||
import { date } from 'quasar';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { date as qdate } from 'quasar';
|
||||
import type { QDateDetails } from 'src/modules/shared/types/q-date-details';
|
||||
|
||||
const is_showing_calendar_picker = ref(false);
|
||||
const calendar_date = ref(date.formatDate( Date.now(), 'YYYY-MM-DD' ));
|
||||
|
||||
const props = defineProps<{
|
||||
isDisabled: boolean,
|
||||
isPreviousLimit: boolean,
|
||||
isDisabled?: boolean;
|
||||
minPickableDate?: string;
|
||||
currentLabel?: string;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'navigate-to', targetIso: string, meta?: { reason: 'previous' | 'next' | 'current' | 'picked' }): void;
|
||||
(e: 'date-selected', value: string, reason?: string, details?: QDateDetails): void;
|
||||
(e: 'pressed-previous-button'): void;
|
||||
(e: 'pressed-next-button'): void;
|
||||
(e: 'pressed-current-button'): void;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
'date-selected': [value: string, reason?: string, details?: QDateDetails]
|
||||
'pressed-previous-button': []
|
||||
'pressed-next-button': []
|
||||
'pressed-current-button' : []
|
||||
}>();
|
||||
const is_showing_calendar_picker = ref(false);
|
||||
|
||||
const currentStartEndDate = computed(()=> {
|
||||
const label = props.currentLabel ?? '';
|
||||
const parts = label.split('.');
|
||||
if(parts.length < 2) return null;
|
||||
return { start_iso: parts[0]!, end_iso: parts[1]! };
|
||||
});
|
||||
|
||||
const to_date = (iso?: string) => (iso ? qdate.extractDate(iso, 'YYYY-MM-DD'): null);
|
||||
const to_iso = (date: Date) => qdate.formatDate(date, 'YYYY-MM-DD');
|
||||
|
||||
const calendar_date = ref(currentStartEndDate.value?.start_iso ?? to_iso(new Date()));
|
||||
|
||||
watch(() => props.currentLabel, () => {
|
||||
if(currentStartEndDate.value?.start_iso) {
|
||||
calendar_date.value = currentStartEndDate.value.start_iso;
|
||||
}
|
||||
});
|
||||
|
||||
const is_previous_limit = computed(() => {
|
||||
const start_end = currentStartEndDate.value;
|
||||
if (!start_end || !props.minPickableDate) return false;
|
||||
const prev_anchor = qdate.addToDate(to_date(start_end.start_iso)!, { days: -1 });
|
||||
return to_iso(prev_anchor) <= props.minPickableDate;
|
||||
});
|
||||
|
||||
|
||||
const onDateSelected = (value: string, reason: string, details: QDateDetails) => {
|
||||
calendar_date.value = value;
|
||||
is_showing_calendar_picker.value = false;
|
||||
emit('date-selected', value, reason, details);
|
||||
}
|
||||
emit('navigate-to', value, { reason: 'picked' });
|
||||
};
|
||||
|
||||
const goPrevious = () => {
|
||||
emit('pressed-previous-button');
|
||||
const start_end = currentStartEndDate.value;
|
||||
if (!start_end) return;
|
||||
const prev_anchor = qdate.addToDate(to_date(start_end.start_iso)!, { days: -1 });
|
||||
emit('navigate-to', to_iso(prev_anchor), { reason: 'previous' });
|
||||
};
|
||||
|
||||
const goCurrent = () => {
|
||||
emit('pressed-current-button');
|
||||
emit('navigate-to', to_iso(new Date()), { reason: 'current' });
|
||||
};
|
||||
|
||||
const goNext = () => {
|
||||
emit('pressed-next-button');
|
||||
const start_end = currentStartEndDate.value;
|
||||
if (!start_end) return;
|
||||
const next_anchor = qdate.addToDate(to_date(start_end.end_iso)!, { days: 1 });
|
||||
emit('navigate-to', to_iso(next_anchor), { reason: 'next' });
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -33,8 +82,8 @@
|
|||
push rounded
|
||||
icon="keyboard_arrow_left"
|
||||
color="primary"
|
||||
@click="emit('pressed-previous-button')"
|
||||
:disable="props.isPreviousLimit || props.isDisabled"
|
||||
@click="goPrevious"
|
||||
:disable="is_previous_limit || props.isDisabled"
|
||||
class="q-mr-sm q-px-sm"
|
||||
>
|
||||
<q-tooltip
|
||||
|
|
@ -49,8 +98,8 @@
|
|||
push rounded
|
||||
icon="today"
|
||||
color="primary"
|
||||
@click="emit('pressed-current-button')"
|
||||
:disable="props.isDisabled"
|
||||
@click="goCurrent"
|
||||
:disable="!!props.isDisabled"
|
||||
class="q-mr-sm q-px-lg"
|
||||
>
|
||||
<q-tooltip
|
||||
|
|
@ -66,7 +115,7 @@
|
|||
icon="calendar_month"
|
||||
color="primary"
|
||||
@click="is_showing_calendar_picker = true"
|
||||
:disable="props.isDisabled"
|
||||
:disable="!!props.isDisabled"
|
||||
class="q-px-lg"
|
||||
>
|
||||
<q-tooltip
|
||||
|
|
@ -81,8 +130,8 @@
|
|||
push rounded
|
||||
icon="keyboard_arrow_right"
|
||||
color="primary"
|
||||
@click="emit('pressed-next-button')"
|
||||
:disable="props.isDisabled"
|
||||
@click="goNext"
|
||||
:disable="!!props.isDisabled"
|
||||
class="q-ml-sm q-px-sm"
|
||||
>
|
||||
<q-tooltip
|
||||
|
|
@ -95,14 +144,18 @@
|
|||
</div>
|
||||
|
||||
<!-- date picker calendar -->
|
||||
<q-dialog v-model="is_showing_calendar_picker" transition-show="jump-down" transition-hide="jump-up" position="top">
|
||||
<q-dialog
|
||||
v-model="is_showing_calendar_picker"
|
||||
transition-show="jump-down"
|
||||
transition-hide="jump-up"
|
||||
position="top">
|
||||
<q-date
|
||||
v-model="calendar_date"
|
||||
color="primary"
|
||||
class="q-mt-xl"
|
||||
today-btn
|
||||
mask="YYYY-MM-DD"
|
||||
:options="date => date > '2023-12-16'"
|
||||
:options="date => !props.minPickableDate || date > props.minPickableDate"
|
||||
@update:model-value="onDateSelected"
|
||||
/>
|
||||
</q-dialog>
|
||||
|
|
|
|||
93
src/modules/timesheets/pages/timesheet-details-overview.vue
Normal file
93
src/modules/timesheets/pages/timesheet-details-overview.vue
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<script setup lang="ts">
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
import { computed, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { date } from 'quasar';
|
||||
import TimesheetEmployeeDetailsShifts from '../components/timesheet-details-shifts.vue';
|
||||
import { useAuthStore } from 'src/stores/auth-store';
|
||||
import TimesheetNavigation from '../components/timesheet/timesheet-navigation.vue';
|
||||
import ShiftsLegend from '../components/shift/shifts-legend.vue';
|
||||
|
||||
|
||||
const { locale } = useI18n();
|
||||
const timesheet_store = useTimesheetStore();
|
||||
const auth_store = useAuthStore();
|
||||
|
||||
const date_options: Intl.DateTimeFormatOptions = {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
};
|
||||
|
||||
const pay_period_label = computed(() => {
|
||||
const label = timesheet_store.current_pay_period?.label ?? '';
|
||||
const dates = label.split('.');
|
||||
if ( dates.length < 2 ) {
|
||||
return { start_date: '—', end_date:'—' }
|
||||
}
|
||||
const start_date = new Intl.DateTimeFormat(locale.value, date_options).format(date.extractDate(dates[0] as string, 'YYYY-MM-DD'));
|
||||
const end_date = new Intl.DateTimeFormat(locale.value, date_options).format(date.extractDate(dates[1] as string, 'YYYY-MM-DD'));
|
||||
return { start_date, end_date };
|
||||
});
|
||||
|
||||
const loadByDate = async (isoDate: string) => {
|
||||
await timesheet_store.getPayPeriodByDate(isoDate);
|
||||
await timesheet_store.getTimesheetsByPayPeriodAndEmail(auth_store.user.email);
|
||||
};
|
||||
|
||||
onMounted( async () => {
|
||||
await timesheet_store.getPayPeriodByDate(date.formatDate(new Date(), 'YYYY-MM-DD'));
|
||||
await timesheet_store.getTimesheetsByPayPeriodAndEmail(auth_store.user.email);
|
||||
await loadByDate(date.formatDate(new Date(), 'YYYY-MM-DD' ));
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<q-page padding class="q-pa-md bg-secondary" >
|
||||
<div class="text-h4 row justify-center text-center q-mt-lg text-uppercase text-weight-bolder text-grey-8">
|
||||
{{ $t('pageTitles.timeSheets') }}
|
||||
</div>
|
||||
<div class="row items-center justify-center q-py-none q-my-none">
|
||||
<div
|
||||
class="text-primary text-uppercase text-weight-bold"
|
||||
:class="$q.screen.lt.md ? '' : 'text-h6'"
|
||||
>
|
||||
{{ pay_period_label.start_date }}
|
||||
</div>
|
||||
<div
|
||||
class="text-grey-8 text-uppercase q-mx-md"
|
||||
:class="$q.screen.lt.md ? 'text-weight-medium text-caption' : 'text-weight-bold'"
|
||||
>
|
||||
{{ $t('timesheet.dateRangesTo') }}
|
||||
</div>
|
||||
<div
|
||||
class="text-primary text-uppercase text-center text-weight-bold"
|
||||
:class="$q.screen.lt.md ? '' : 'text-h6'"
|
||||
>
|
||||
{{ pay_period_label.end_date }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<q-card flat class="q-mt-md bg-secondary">
|
||||
<TimesheetNavigation
|
||||
:is-disabled="timesheet_store.is_loading"
|
||||
:min-pickable-date="'2023-12-16'"
|
||||
:current-label="timesheet_store.current_pay_period?.label || ''"
|
||||
@navigate-to="(iso) => loadByDate(iso)"
|
||||
/>
|
||||
<ShiftsLegend
|
||||
:is-loading="false"
|
||||
/>
|
||||
<q-card-section horizontal>
|
||||
<TimesheetEmployeeDetailsShifts
|
||||
:raw-data="timesheet_store.pay_period_employee_details"
|
||||
:current-pay-period="timesheet_store.current_pay_period"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-inner-loading :showing="timesheet_store.is_loading" color="primary"/>
|
||||
</q-card>
|
||||
</div>
|
||||
</q-page>
|
||||
</template>
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
import { useTimesheetApi } from '../composables/use-timesheet-api';
|
||||
import { computed, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import TimesheetShiftForm from '../components/timesheet/timesheet-shift-form.vue';
|
||||
import TimesheetNavigation from '../components/timesheet/timesheet-navigation.vue';
|
||||
import { date as qdate } from 'quasar';
|
||||
import type { CreateShiftPayload } from '../types/timesheet-shifts-payload-interface';
|
||||
|
||||
const { locale } = useI18n();
|
||||
const timesheet_store = useTimesheetStore();
|
||||
const timesheet_api = useTimesheetApi();
|
||||
const { this_week, saveTimesheetShifts } = timesheet_api;
|
||||
|
||||
onMounted(async () => {
|
||||
await this_week();
|
||||
});
|
||||
|
||||
const date_options: Intl.DateTimeFormatOptions = {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
};
|
||||
|
||||
const timesheet_label = computed(() => {
|
||||
const dates = timesheet_store.current_timesheet.label.split('.');
|
||||
const start_date = new Intl.DateTimeFormat(locale.value, date_options).format(qdate.extractDate(dates[0] as string, 'YYYY-MM-DD'));
|
||||
const end_date = new Intl.DateTimeFormat(locale.value, date_options).format(qdate.extractDate(dates[1] as string, 'YYYY-MM-DD'));
|
||||
|
||||
if ( dates.length === 1) {
|
||||
return { start_date: '_', end_date: '_', }
|
||||
}
|
||||
return { start_date, end_date };
|
||||
});
|
||||
|
||||
const onSaveShifts = async (payload: CreateShiftPayload[]) => {
|
||||
await saveTimesheetShifts(payload);
|
||||
};
|
||||
|
||||
const is_calendar_limit = computed( () => {
|
||||
return timesheet_store.current_pay_period.pay_year === 2024 &&
|
||||
timesheet_store.current_pay_period.pay_period_no <= 1;
|
||||
});
|
||||
|
||||
const onDateSelected = async (date_string: string) => {
|
||||
const when = qdate.extractDate(date_string, 'YYYY-MM-DD');
|
||||
await timesheet_api.getCurrentWeekTimesheetOverview(when);
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
|
||||
<q-page padding class="q-pa-md bg-secondary" >
|
||||
<div class="text-h4 row justify-center q-mt-lg text-uppercase text-weight-bolder text-grey-8 col-auto">
|
||||
{{ $t('pageTitles.timeSheets') }}
|
||||
</div>
|
||||
<div class="row items-center justify-center q-py-none q-my-none">
|
||||
<div class="text-primary text-h6 text-uppercase">
|
||||
{{ timesheet_label.start_date }}
|
||||
</div>
|
||||
<div class="text-grey-8 text-weight-bold text-uppercase q-mx-md">
|
||||
{{ $t('timesheet.dateRangesTo') }}
|
||||
</div>
|
||||
<div class="text-primary text-h6 text-uppercase">
|
||||
{{ timesheet_label.end_date }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<TimesheetNavigation
|
||||
:is-disabled="timesheet_store.is_loading"
|
||||
:is-previous-limit="is_calendar_limit"
|
||||
@date-selected="onDateSelected"
|
||||
@pressed-previous-button="timesheet_api.previous_week()"
|
||||
@pressed-current-button="timesheet_api.getCurrentWeekTimesheetOverview()"
|
||||
@pressed-next-button="timesheet_api.next_week()"
|
||||
/>
|
||||
<q-card flat class="q-mt-md bg-secondary">
|
||||
<TimesheetShiftForm @save="onSaveShifts" class="col-12"/>
|
||||
<q-inner-loading :showing="timesheet_store.is_loading" color="primary"/>
|
||||
</q-card>
|
||||
</div>
|
||||
<!-- navigation buttons -->
|
||||
</q-page>
|
||||
</template>
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
import { api } from "src/boot/axios";
|
||||
import type {Timesheet} from "src/modules/timesheets/types/timesheet-interface";
|
||||
import type { CreateShiftPayload, CreateWeekShiftPayload } from "../types/timesheet-shifts-payload-interface";
|
||||
import type { PayPeriod } from "src/modules/shared/types/pay-period-interface";
|
||||
import type { PayPeriodEmployeeDetails } from "src/modules/timesheet-approval/types/timesheet-approval-pay-period-employee-details-interface";
|
||||
import type { PayPeriodOverview } from "src/modules/timesheet-approval/types/timesheet-approval-pay-period-overview-interface";
|
||||
import type { PayPeriodReportFilters } from "src/modules/timesheet-approval/types/timesheet-approval-pay-period-report-interface";
|
||||
|
||||
export const timesheetTempService = {
|
||||
//GET
|
||||
|
|
@ -14,5 +18,34 @@ export const timesheetTempService = {
|
|||
const payload: CreateWeekShiftPayload = { shifts };
|
||||
const response = await api.post(`/timesheets/shifts/${encodeURIComponent(email)}`, payload, { params: offset ? { offset }: undefined });
|
||||
return response.data as Timesheet;
|
||||
}
|
||||
},
|
||||
|
||||
getPayPeriodByDate: async (date_string: string): Promise<PayPeriod> => {
|
||||
const response = await api.get(`pay-periods/date/${date_string}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getPayPeriodByYearAndPeriodNumber: async (year: number, period_number: number): Promise<PayPeriod> => {
|
||||
const response = await api.get(`pay-periods/${year}/${period_number}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
|
||||
getPayPeriodEmployeeOverviews: async (year: number, period_number: number, supervisor_email: string): Promise<PayPeriodOverview> => {
|
||||
// TODO: REMOVE MOCK DATA PEFORE PUSHING TO PROD
|
||||
const response = await api.get(`pay-periods/${year}/${period_number}/${supervisor_email}`);
|
||||
console.log('pay period data: ', response.data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getTimesheetsByPayPeriodAndEmail: async (year: number, period_no: number, email: string): Promise<PayPeriodEmployeeDetails> => {
|
||||
const response = await api.get('timesheets', { params: { year, period_no, email, }});
|
||||
console.log('employee details: ', response.data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getTimesheetApprovalCSVReport: async (year: number, period_number: number, report_filters?: PayPeriodReportFilters) => {
|
||||
const response = await api.get(`csv/${year}/${period_number}`, { params: { report_filters, }});
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import { default_timesheet_details_week, type TimesheetDetailsWeek } from "./timesheet-details-interface";
|
||||
|
||||
export interface TimesheetPayPeriodDetailsOverview {
|
||||
week1: TimesheetDetailsWeek;
|
||||
week2: TimesheetDetailsWeek;
|
||||
};
|
||||
|
||||
export const default_pay_period_employee_details = {
|
||||
week1: default_timesheet_details_week(),
|
||||
week2: default_timesheet_details_week(),
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ const routes: RouteRecordRaw[] = [
|
|||
{
|
||||
path: 'timesheet-temp',
|
||||
name: RouteNames.TIMESHEET_TEMP,
|
||||
component: () => import('src/modules/timesheets/pages/timesheet-temp-page.vue')
|
||||
component: () => import('src/modules/timesheets/pages/timesheet-details-overview.vue')
|
||||
}
|
||||
],
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user