fix(approvals): add more functionality and ui fixes to list view, add weekly breakdown hours, ui adjustments to card view
This commit is contained in:
parent
1a707bf05b
commit
c62350fde4
Binary file not shown.
|
Before Width: | Height: | Size: 618 KiB After Width: | Height: | Size: 90 KiB |
|
|
@ -353,8 +353,20 @@ export default {
|
||||||
verified: "approved",
|
verified: "approved",
|
||||||
unverified: "pending",
|
unverified: "pending",
|
||||||
inactive: "inactive",
|
inactive: "inactive",
|
||||||
|
regular: "regular",
|
||||||
|
evening: "evening",
|
||||||
|
emergency: "emergency",
|
||||||
|
overtime: "overtime",
|
||||||
|
holiday: "holiday",
|
||||||
|
vacation: "vacation",
|
||||||
|
sick: "sick",
|
||||||
|
remote: "remote work",
|
||||||
|
weekly_hours_1: "1st week hours",
|
||||||
|
weekly_hours_2: "2nd week hours",
|
||||||
|
total_hours: "total hours",
|
||||||
filter_active: "show only active employees",
|
filter_active: "show only active employees",
|
||||||
filter_team: "show my team only",
|
filter_team: "show my team only",
|
||||||
|
filter_columns: "Information displayed",
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
button_detailed_view: "detailed view",
|
button_detailed_view: "detailed view",
|
||||||
|
|
|
||||||
|
|
@ -354,8 +354,20 @@ export default {
|
||||||
verified: "approuvé",
|
verified: "approuvé",
|
||||||
unverified: "à vérifier",
|
unverified: "à vérifier",
|
||||||
inactive: "inactif",
|
inactive: "inactif",
|
||||||
|
regular: "régulier",
|
||||||
|
evening: "soir",
|
||||||
|
emergency: "urgence",
|
||||||
|
overtime: "supplémentaire",
|
||||||
|
holiday: "férié",
|
||||||
|
vacation: "vacances",
|
||||||
|
sick: "maladie",
|
||||||
|
remote: "télétravail",
|
||||||
|
weekly_hours_1: "heures semaine 1",
|
||||||
|
weekly_hours_2: "heures semaine 2",
|
||||||
|
total_hours: "heures totales",
|
||||||
filter_active: "montrer les employés inactifs",
|
filter_active: "montrer les employés inactifs",
|
||||||
filter_team: "montrer mon équipe seulement",
|
filter_team: "montrer mon équipe seulement",
|
||||||
|
filter_columns: "informations affichés",
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
button_detailed_view: "vue détaillée",
|
button_detailed_view: "vue détaillée",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,20 @@
|
||||||
|
<script
|
||||||
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
|
const CREATE_YEAR = 2025;
|
||||||
|
const today = new Date();
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-footer elevated class="bg-primary text-white">
|
<q-footer
|
||||||
<q-toolbar>
|
elevated
|
||||||
<q-toolbar-title>© 2025 Targo Communications inc.</q-toolbar-title>
|
class="bg-primary text-white"
|
||||||
</q-toolbar>
|
>
|
||||||
</q-footer>
|
<div class="q-px-md q-py-xs full-width text-right">
|
||||||
|
<span class="text-weight-light text-caption text-uppercase">
|
||||||
|
© {{ CREATE_YEAR }} - {{ today.getFullYear() }} Targo Communications inc.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</q-footer>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -76,7 +76,7 @@
|
||||||
rounded
|
rounded
|
||||||
disabled
|
disabled
|
||||||
type="submit"
|
type="submit"
|
||||||
color="accent"
|
color="grey-5"
|
||||||
:label="$t('login.button.connect')"
|
:label="$t('login.button.connect')"
|
||||||
class="full-width q-mt-lg"
|
class="full-width q-mt-lg"
|
||||||
/>
|
/>
|
||||||
|
|
@ -108,7 +108,7 @@
|
||||||
rounded
|
rounded
|
||||||
push
|
push
|
||||||
disabled
|
disabled
|
||||||
color="fb-blue"
|
color="blue-grey-7"
|
||||||
icon="img:src/assets/Facebook-f_Logo-White-Logo.wine.svg"
|
icon="img:src/assets/Facebook-f_Logo-White-Logo.wine.svg"
|
||||||
:label="$t('login.button.facebook')"
|
:label="$t('login.button.facebook')"
|
||||||
class="full-width row q-mb-sm"
|
class="full-width row q-mb-sm"
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,6 @@ const schedule_preset_desc = "descriptions.employee_management.schedule_preset";
|
||||||
|
|
||||||
|
|
||||||
export const employee_list_options: HelpModuleOptions[] = [
|
export const employee_list_options: HelpModuleOptions[] = [
|
||||||
{ label: 'help.tutorial.employee_list.terminated_employees', path: terminated_employee_display, description: terminated_employee_desc, icon: 'work_off' },
|
|
||||||
{ label: 'help.tutorial.shared.display', path: employee_list_card, description: display_desc, icon: 'display_settings' },
|
{ label: 'help.tutorial.shared.display', path: employee_list_card, description: display_desc, icon: 'display_settings' },
|
||||||
{ label: 'help.tutorial.shared.search', path: search_bar, description: search_bar_desc, icon: 'search' },
|
{ label: 'help.tutorial.shared.search', path: search_bar, description: search_bar_desc, icon: 'search' },
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@
|
||||||
setup
|
setup
|
||||||
lang="ts"
|
lang="ts"
|
||||||
>
|
>
|
||||||
|
/* eslint-disable */
|
||||||
import { date, useQuasar } from 'quasar';
|
import { date, useQuasar } from 'quasar';
|
||||||
import { computed } from 'vue';
|
import { computed, onMounted, onUpdated, ref } from 'vue';
|
||||||
|
|
||||||
const { title, startDate = "", endDate = "" } = defineProps<{
|
const { title, startDate = "", endDate = "" } = defineProps<{
|
||||||
title: string;
|
title: string;
|
||||||
|
|
@ -13,17 +14,36 @@
|
||||||
|
|
||||||
const q = useQuasar();
|
const q = useQuasar();
|
||||||
|
|
||||||
|
const emit = defineEmits<{ 'onGetComponentHeight': [value: number] }>();
|
||||||
|
|
||||||
|
const selfRef = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
const date_format_options = computed(() => q.platform.is.mobile ? { day: 'numeric', month: 'short', year: 'numeric' } : { day: 'numeric', month: 'long', year: 'numeric', });
|
const date_format_options = computed(() => q.platform.is.mobile ? { day: 'numeric', month: 'short', year: 'numeric' } : { day: 'numeric', month: 'long', year: 'numeric', });
|
||||||
|
|
||||||
|
onUpdated(() => {
|
||||||
|
if (selfRef.value) {
|
||||||
|
emit('onGetComponentHeight', selfRef.value.offsetHeight);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (selfRef.value) {
|
||||||
|
emit('onGetComponentHeight', selfRef.value.offsetHeight);
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="column text-uppercase text-center text-weight-bolder text-h4">
|
<div
|
||||||
<span
|
ref="selfRef"
|
||||||
|
class="column text-uppercase text-center text-weight-bolder text-h4 q-pt-md"
|
||||||
|
>
|
||||||
|
<!-- <span
|
||||||
v-if="!$q.platform.is.mobile"
|
v-if="!$q.platform.is.mobile"
|
||||||
class="col q-pt-lg"
|
class="col q-pt-lg"
|
||||||
>
|
>
|
||||||
{{ $t(title) }}
|
{{ $t(title) }}
|
||||||
</span>
|
</span> -->
|
||||||
|
|
||||||
<transition
|
<transition
|
||||||
enter-active-class="animated fadeInDown"
|
enter-active-class="animated fadeInDown"
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,17 @@
|
||||||
<template>
|
<template>
|
||||||
<q-input
|
<q-input
|
||||||
v-model="search_model"
|
v-model="search_model"
|
||||||
outlined
|
:dark="false"
|
||||||
dense
|
dense
|
||||||
|
outlined
|
||||||
rounded
|
rounded
|
||||||
debounce="300"
|
debounce="300"
|
||||||
:label="$t('shared.label.search')"
|
:label="$t('shared.label.search')"
|
||||||
color="accent"
|
color="accent"
|
||||||
bg-color="white"
|
bg-color="white"
|
||||||
label-color="accent"
|
label-color="accent"
|
||||||
class="text-primary"
|
|
||||||
>
|
>
|
||||||
<template #prepend>
|
<template #append>
|
||||||
<q-icon
|
<q-icon
|
||||||
name="search"
|
name="search"
|
||||||
color="accent"
|
color="accent"
|
||||||
|
|
|
||||||
|
|
@ -2,34 +2,62 @@
|
||||||
setup
|
setup
|
||||||
lang="ts"
|
lang="ts"
|
||||||
>
|
>
|
||||||
import type { PayPeriodOverviewFilters } from 'src/modules/timesheet-approval/models/timesheet-overview.models';
|
import { overview_column_names, type OverviewColumns, type PayPeriodOverviewFilters } from 'src/modules/timesheet-approval/models/timesheet-overview.models';
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
const filters = defineModel<PayPeriodOverviewFilters>('filters', { required: true })
|
const filters = defineModel<PayPeriodOverviewFilters>('filters', { required: true });
|
||||||
|
const visible_columns = defineModel<OverviewColumns[]>('visibleColumns', {required: true});
|
||||||
|
|
||||||
|
const column_options = ref<{ label: string, value: OverviewColumns }[]>([]);
|
||||||
|
const EXCLUDED_COLUMNS: OverviewColumns[] = ['employee_last_name', 'employee_first_name', 'is_active']
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
Object.values(overview_column_names).map(column => column_options.value.push({ label: `timesheet_approvals.table.${column}`, value: column as OverviewColumns }))
|
||||||
|
column_options.value = column_options.value.filter(column => !EXCLUDED_COLUMNS.includes(column.value));
|
||||||
|
console.log('filter column values: ', column_options.value )
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="column bg-primary text-uppercase">
|
<div class="column bg-primary text-uppercase q-px-sm text-white">
|
||||||
<div class="col row">
|
<div class="col row">
|
||||||
<q-checkbox
|
<q-checkbox
|
||||||
v-model="filters.is_showing_inactive"
|
v-model="filters.is_showing_inactive"
|
||||||
keep-color
|
keep-color
|
||||||
size="lg"
|
|
||||||
color="accent"
|
color="accent"
|
||||||
:label="$t('timesheet_approvals.table.filter_active')"
|
:label="$t('timesheet_approvals.table.filter_active')"
|
||||||
class="col"
|
class="col-auto"
|
||||||
:class="filters.is_showing_inactive ? 'text-accent text-weight-bolder' : 'text-white text-weight-medium'"
|
:class="filters.is_showing_inactive ? 'text-accent text-weight-bolder' : 'text-white text-weight-medium'"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<q-checkbox
|
<q-checkbox
|
||||||
v-model="filters.is_showing_team_only"
|
v-model="filters.is_showing_team_only"
|
||||||
keep-color
|
keep-color
|
||||||
size="lg"
|
|
||||||
val="team"
|
val="team"
|
||||||
color="accent"
|
color="accent"
|
||||||
:label="$t('timesheet_approvals.table.filter_team')"
|
:label="$t('timesheet_approvals.table.filter_team')"
|
||||||
class="col"
|
class="col-auto q-px-sm"
|
||||||
:class="filters.is_showing_team_only ? 'text-accent text-weight-bolder' : 'text-white text-weight-medium'"
|
:class="filters.is_showing_team_only ? 'text-accent text-weight-bolder' : 'text-white text-weight-medium'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<span class="col-auto q-px-md q-pt-sm text-h6 text-bold">
|
||||||
|
{{ $t('timesheet_approvals.table.filter_columns') }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="col row">
|
||||||
|
<q-option-group
|
||||||
|
v-model="visible_columns"
|
||||||
|
:options="column_options"
|
||||||
|
inline
|
||||||
|
keep-color
|
||||||
|
color="accent"
|
||||||
|
type="checkbox"
|
||||||
|
>
|
||||||
|
<template #label="scope">
|
||||||
|
<span class="text-caption text-uppercase text-weight-light">{{ $t(scope.label.toLowerCase()) }}</span>
|
||||||
|
</template>
|
||||||
|
</q-option-group>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
lang="ts"
|
lang="ts"
|
||||||
>
|
>
|
||||||
import type { TimesheetApprovalOverview } from 'src/modules/timesheet-approval/models/timesheet-overview.models';
|
import type { TimesheetApprovalOverview } from 'src/modules/timesheet-approval/models/timesheet-overview.models';
|
||||||
import { getHoursMinutesStringFromHoursFloat, getMinutes } from 'src/utils/date-and-time-utils';
|
import { getHoursMinutesStringFromHoursFloat } from 'src/utils/date-and-time-utils';
|
||||||
|
|
||||||
const modelApproval = defineModel<boolean>();
|
const modelApproval = defineModel<boolean>();
|
||||||
|
|
||||||
|
|
@ -82,7 +82,7 @@
|
||||||
> {{
|
> {{
|
||||||
$t('shared.shift_type.regular') }} </span>
|
$t('shared.shift_type.regular') }} </span>
|
||||||
<span
|
<span
|
||||||
class="text-weight-bolder text-h3 q-py-none"
|
class="text-weight-bolder text-h4 q-py-none"
|
||||||
:class="row.regular_hours > 80 || !row.is_active ? 'text-negative' : ''"
|
:class="row.regular_hours > 80 || !row.is_active ? 'text-negative' : ''"
|
||||||
> {{ getHoursMinutesStringFromHoursFloat(row.regular_hours) }} </span>
|
> {{ getHoursMinutesStringFromHoursFloat(row.regular_hours) }} </span>
|
||||||
<q-separator class="q-mr-sm" />
|
<q-separator class="q-mr-sm" />
|
||||||
|
|
@ -94,7 +94,7 @@
|
||||||
v-for="hour_type, index in row.other_hours"
|
v-for="hour_type, index in row.other_hours"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="col-4 column ellipsis"
|
class="col-4 column ellipsis"
|
||||||
:class="hour_type === 0 ? 'invisible' : ''"
|
:class="hour_type === 0 ? 'invisible order-last' : 'order-first'"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="text-weight-bold text-accent text-uppercase q-pa-none q-my-none"
|
class="text-weight-bold text-accent text-uppercase q-pa-none q-my-none"
|
||||||
|
|
@ -102,10 +102,13 @@
|
||||||
>
|
>
|
||||||
{{ $t(`shared.shift_type.${index.replace('_hours', '')}`) }}
|
{{ $t(`shared.shift_type.${index.replace('_hours', '')}`) }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
class="text-weight-bolder q-pa-none q-mb-xs"
|
class="text-weight-bolder q-pa-none q-mb-xs"
|
||||||
style="font-size: 1.2em; line-height: 1em;"
|
style="font-size: 1.2em; line-height: 1em;"
|
||||||
> {{ getHoursMinutesStringFromHoursFloat(hour_type) }} </span>
|
>
|
||||||
|
{{ getHoursMinutesStringFromHoursFloat(hour_type) }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -144,20 +147,47 @@
|
||||||
|
|
||||||
<!-- Validate Pay Period section -->
|
<!-- Validate Pay Period section -->
|
||||||
<q-card-section
|
<q-card-section
|
||||||
horizontal
|
class="justify-between items-center q-pa-none"
|
||||||
class="justify-between items-center text-weight-bold q-pa-none"
|
|
||||||
:class="row.is_active ? (row.is_approved ? 'text-white bg-accent' : 'bg-dark') : 'bg-transparent'"
|
:class="row.is_active ? (row.is_approved ? 'text-white bg-accent' : 'bg-dark') : 'bg-transparent'"
|
||||||
>
|
>
|
||||||
|
<!-- weekly totals -->
|
||||||
|
<q-separator />
|
||||||
|
<div
|
||||||
|
v-if="row.is_active"
|
||||||
|
class="col row full-width justify-between"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="weekly_hours, index in row.weekly_hours"
|
||||||
|
:key="index"
|
||||||
|
class="col q-px-sm text-uppercase"
|
||||||
|
>
|
||||||
|
<span class="text-weight-bold">
|
||||||
|
{{ $t('timesheet.week') }} {{ index + 1 }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="q-ml-sm"
|
||||||
|
:class="row.total_hours > 80 ? 'bg-negative q-px-sm rounded-5 text-white' : ''"
|
||||||
|
>
|
||||||
|
{{ getHoursMinutesStringFromHoursFloat(weekly_hours) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-separator />
|
||||||
|
|
||||||
|
<!-- overall totals and approve all button -->
|
||||||
<div
|
<div
|
||||||
v-if="row.is_active"
|
v-if="row.is_active"
|
||||||
class="col row full-width"
|
class="col row full-width"
|
||||||
>
|
>
|
||||||
<div class="col text-uppercase">
|
<div class="col text-uppercase">
|
||||||
<span class="text-h6 q-ml-sm text-weight-bolder">{{ 'Total : ' + Math.floor(row.total_hours)
|
<span class="text-h6 q-ml-sm text-weight-bolder">Total</span>
|
||||||
}}</span>
|
<span
|
||||||
<span class="text-uppercase text-weight-medium text-caption">H</span>
|
class="q-ml-sm text-h6"
|
||||||
<span class="text-h6 q-ml-sm text-weight-bolder">{{ getMinutes(row.total_hours) }}</span>
|
:class="row.total_hours > 80 ? 'bg-negative q-px-sm rounded-5 text-white' : 'text-weight-light'"
|
||||||
<span class="text-uppercase text-weight-medium text-caption">M</span>
|
>
|
||||||
|
{{ getHoursMinutesStringFromHoursFloat(row.total_hours) }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-auto q-py-xs q-px-md">
|
<div class="col-auto q-py-xs q-px-md">
|
||||||
|
|
|
||||||
|
|
@ -13,15 +13,19 @@
|
||||||
import { useAuthStore } from 'src/stores/auth-store';
|
import { useAuthStore } from 'src/stores/auth-store';
|
||||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||||
import { useTimesheetApprovalApi } from 'src/modules/timesheet-approval/composables/use-timesheet-approval-api';
|
import { useTimesheetApprovalApi } from 'src/modules/timesheet-approval/composables/use-timesheet-approval-api';
|
||||||
import { overview_column_names, OverviewColumns, pay_period_overview_columns, PayPeriodOverviewFilters, type TimesheetApprovalOverview } from 'src/modules/timesheet-approval/models/timesheet-overview.models';
|
import { type OverviewColumns, pay_period_overview_columns, PayPeriodOverviewFilters, type TimesheetApprovalOverview } from 'src/modules/timesheet-approval/models/timesheet-overview.models';
|
||||||
import { getHoursMinutesStringFromHoursFloat } from 'src/utils/date-and-time-utils';
|
import { getHoursMinutesStringFromHoursFloat } from 'src/utils/date-and-time-utils';
|
||||||
|
import { useUiStore } from 'src/stores/ui-store';
|
||||||
|
|
||||||
|
const WARNING_COLUMNS: OverviewColumns[] = ['EMERGENCY', 'EVENING', 'HOLIDAY', 'VACATION', 'SICK']
|
||||||
|
const NEGATIVE_COLUMNS: OverviewColumns[] = ['OVERTIME',]
|
||||||
|
|
||||||
|
const ui_store = useUiStore();
|
||||||
const auth_store = useAuthStore();
|
const auth_store = useAuthStore();
|
||||||
const timesheet_store = useTimesheetStore();
|
const timesheet_store = useTimesheetStore();
|
||||||
const timesheet_approval_api = useTimesheetApprovalApi();
|
const timesheet_approval_api = useTimesheetApprovalApi();
|
||||||
|
|
||||||
const TIME_COLUMNS: OverviewColumns[] = ['REGULAR', 'EVENING', 'EMERGENCY', 'OVERTIME', 'HOLIDAY', 'VACATION'];
|
const TIME_COLUMNS: OverviewColumns[] = ['REGULAR', 'EVENING', 'EMERGENCY', 'OVERTIME', 'HOLIDAY', 'VACATION', 'SICK'];
|
||||||
const VISIBLE_COLUMNS = ref<OverviewColumns[]>([
|
const VISIBLE_COLUMNS = ref<OverviewColumns[]>([
|
||||||
'employee_first_name',
|
'employee_first_name',
|
||||||
'REGULAR',
|
'REGULAR',
|
||||||
|
|
@ -32,6 +36,9 @@
|
||||||
'VACATION',
|
'VACATION',
|
||||||
'expenses',
|
'expenses',
|
||||||
'mileage',
|
'mileage',
|
||||||
|
'weekly_hours_1',
|
||||||
|
'weekly_hours_2',
|
||||||
|
'total_hours',
|
||||||
'is_approved',
|
'is_approved',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -74,229 +81,276 @@
|
||||||
if (terms.name_search_string.length > 0) {
|
if (terms.name_search_string.length > 0) {
|
||||||
const search_words = terms.name_search_string.trim().split(' ');
|
const search_words = terms.name_search_string.trim().split(' ');
|
||||||
search_words.map(word => result = result.filter(row =>
|
search_words.map(word => result = result.filter(row =>
|
||||||
row.employee_first_name.includes(word ?? '') || row.employee_last_name.includes(word ?? '')
|
row.employee_first_name.toLowerCase().includes(word.toLowerCase() ?? '') || row.employee_last_name.toLowerCase().includes(word ?? '')
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getListViewTimeClass = (column_name: OverviewColumns, value: number) => {
|
||||||
|
if(WARNING_COLUMNS.includes(column_name) && value > 0)
|
||||||
|
return 'bg-warning text-white rounded-5';
|
||||||
|
|
||||||
|
if(NEGATIVE_COLUMNS.includes(column_name) && value > 0)
|
||||||
|
return 'bg-negative text-white text-bold rounded-5';
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="full-width">
|
<div class="full-width">
|
||||||
<LoadingOverlay v-model="timesheet_store.is_loading" />
|
<LoadingOverlay v-model="timesheet_store.is_loading" />
|
||||||
<q-table
|
<q-table
|
||||||
dense
|
dense
|
||||||
row-key="email"
|
row-key="email"
|
||||||
color="accent"
|
color="accent"
|
||||||
hide-pagination
|
hide-pagination
|
||||||
:rows="overview_rows"
|
:rows="overview_rows"
|
||||||
:columns="pay_period_overview_columns"
|
:columns="pay_period_overview_columns"
|
||||||
:visible-columns="VISIBLE_COLUMNS"
|
:table-colspan="pay_period_overview_columns.length"
|
||||||
:grid="timesheet_store.is_approval_grid_mode"
|
:visible-columns="VISIBLE_COLUMNS"
|
||||||
:pagination="{ sortBy: 'is_active' }"
|
:grid="ui_store.user_preferences.is_timesheet_approval_grid"
|
||||||
:filter="overview_filters"
|
:pagination="{ sortBy: 'is_active' }"
|
||||||
:filter-method="filterEmployeeRows"
|
:filter="overview_filters"
|
||||||
:rows-per-page-options="[0]"
|
:filter-method="filterEmployeeRows"
|
||||||
class="bg-transparent"
|
:rows-per-page-options="[0]"
|
||||||
:class="timesheet_store.is_approval_grid_mode ? '' : 'sticky-header-table no-shadow'"
|
class="bg-transparent"
|
||||||
card-container-class="justify-center"
|
:class="ui_store.user_preferences.is_timesheet_approval_grid ? '' : 'sticky-header-table no-shadow'"
|
||||||
table-class="q-pa-none q-mx-md rounded-10 bg-dark shadow-15 hide-scrollbar"
|
card-container-class="justify-center"
|
||||||
:no-data-label="$t('shared.error.no_data_found')"
|
table-class="q-pa-none q-mx-md rounded-10 bg-dark shadow-15 hide-scrollbar"
|
||||||
:no-results-label="$t('shared.error.no_search_results')"
|
:no-data-label="$t('shared.error.no_data_found')"
|
||||||
:loading-label="$t('shared.label.loading')"
|
:no-results-label="$t('shared.error.no_search_results')"
|
||||||
:style="overview_rows.length > 0 ? `max-height: ${maxHeight - (timesheet_store.is_approval_grid_mode ? 0 : 20)}px;` : ''"
|
:loading-label="$t('shared.label.loading')"
|
||||||
@row-click="(_evt, row: TimesheetApprovalOverview) => onClickedDetails(row)"
|
table-header-style="min-width: 80xp; max-width: 80px;"
|
||||||
>
|
:style="overview_rows.length > 0 ? `max-height: ${maxHeight - (ui_store.user_preferences.is_timesheet_approval_grid ? 0 : 20)}px;` : ''"
|
||||||
<template #top>
|
:table-style="{ tableLayout: 'fixed'}"
|
||||||
<div class="column full-width">
|
@row-click="(_evt, row: TimesheetApprovalOverview) => onClickedDetails(row)"
|
||||||
|
>
|
||||||
|
<template #top>
|
||||||
|
<div class="column full-width">
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="col-auto row items-start full-width q-px-lg"
|
||||||
|
:class="$q.platform.is.mobile ? 'column flex-center' : 'row q-mt-md'"
|
||||||
|
>
|
||||||
|
<PayPeriodNavigator
|
||||||
|
@date-selected="timesheet_approval_api.getTimesheetOverviews"
|
||||||
|
@pressed-next-button="timesheet_approval_api.getTimesheetOverviews"
|
||||||
|
@pressed-previous-button="timesheet_approval_api.getTimesheetOverviews"
|
||||||
|
:class="$q.platform.is.mobile ? 'q-mb-sm' : ''"
|
||||||
|
style="height: 40px;"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-space />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="col-auto row items-start full-width q-px-lg"
|
class="col-auto row no-wrap items-start"
|
||||||
:class="$q.platform.is.mobile ? 'column flex-center' : 'row q-mt-md'"
|
:class="$q.platform.is.mobile ? 'q-mb-md' : ''"
|
||||||
>
|
>
|
||||||
<PayPeriodNavigator
|
<q-btn-toggle
|
||||||
@date-selected="timesheet_approval_api.getTimesheetOverviews"
|
v-model="ui_store.user_preferences.is_timesheet_approval_grid"
|
||||||
@pressed-next-button="timesheet_approval_api.getTimesheetOverviews"
|
push
|
||||||
@pressed-previous-button="timesheet_approval_api.getTimesheetOverviews"
|
rounded
|
||||||
:class="$q.platform.is.mobile ? 'q-mb-sm' : ''"
|
color="white"
|
||||||
|
text-color="accent"
|
||||||
|
toggle-color="accent"
|
||||||
|
class="col-auto"
|
||||||
|
:class="$q.platform.is.mobile ? 'q-mb-sm' : 'q-mr-sm'"
|
||||||
|
:options="[
|
||||||
|
{ icon: 'grid_view', value: true },
|
||||||
|
{ icon: 'view_list', value: false },
|
||||||
|
]"
|
||||||
style="height: 40px;"
|
style="height: 40px;"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<q-space />
|
<q-btn
|
||||||
|
push
|
||||||
<div
|
rounded
|
||||||
class="col-auto row no-wrap items-start"
|
icon="download"
|
||||||
:class="$q.platform.is.mobile ? 'q-mb-md' : ''"
|
color="accent"
|
||||||
>
|
:label="$q.screen.lt.md ? '' : $t('shared.label.download')"
|
||||||
<q-btn-toggle
|
class="col-auto q-mr-sm"
|
||||||
v-model="timesheet_store.is_approval_grid_mode"
|
style="height: 40px;"
|
||||||
push
|
@click="timesheet_store.is_report_dialog_open = true"
|
||||||
rounded
|
|
||||||
color="white"
|
|
||||||
text-color="accent"
|
|
||||||
toggle-color="accent"
|
|
||||||
class="col-auto"
|
|
||||||
:class="$q.platform.is.mobile ? 'q-mb-sm' : 'q-mr-sm'"
|
|
||||||
:options="[
|
|
||||||
{ icon: 'grid_view', value: true },
|
|
||||||
{ icon: 'view_list', value: false },
|
|
||||||
]"
|
|
||||||
style="height: 40px;"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<q-btn
|
|
||||||
push
|
|
||||||
rounded
|
|
||||||
icon="download"
|
|
||||||
color="accent"
|
|
||||||
:label="$q.screen.lt.md ? '' : $t('shared.label.download')"
|
|
||||||
class="col-auto q-mr-sm"
|
|
||||||
style="height: 40px;"
|
|
||||||
@click="timesheet_store.is_report_dialog_open = true"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<QTableFilters
|
|
||||||
v-model:search="overview_filters.name_search_string"
|
|
||||||
class="col-auto q-mb-sm"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<q-btn
|
|
||||||
flat
|
|
||||||
icon="filter_alt"
|
|
||||||
color="white"
|
|
||||||
:label="$q.platform.is.mobile ? '' : $t('shared.label.filter')"
|
|
||||||
class="col q-ml-sm self-stretch bg-primary"
|
|
||||||
style="border-radius: 5px 5px 0 0;"
|
|
||||||
@click="is_showing_filters = !is_showing_filters"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<q-slide-transition>
|
|
||||||
<OverviewListFilters
|
|
||||||
v-if="is_showing_filters"
|
|
||||||
v-model:filters="overview_filters"
|
|
||||||
class="q-mx-lg col-auto"
|
|
||||||
/>
|
/>
|
||||||
</q-slide-transition>
|
|
||||||
|
|
||||||
<q-separator
|
<QTableFilters
|
||||||
color="primary"
|
v-model:search="overview_filters.name_search_string"
|
||||||
size="5px"
|
class="col-auto q-mb-sm"
|
||||||
class="q-mx-lg q-my-none q-pa-none"
|
/>
|
||||||
/>
|
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
icon="filter_alt"
|
||||||
|
color="white"
|
||||||
|
:label="$q.platform.is.mobile ? '' : $t('shared.label.filter')"
|
||||||
|
class="col q-ml-sm self-stretch bg-primary"
|
||||||
|
style="border-radius: 5px 5px 0 0;"
|
||||||
|
@click="is_showing_filters = !is_showing_filters"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #header="props">
|
<q-slide-transition>
|
||||||
<q-tr
|
<OverviewListFilters
|
||||||
:props="props"
|
v-if="is_showing_filters"
|
||||||
class="bg-primary"
|
v-model:filters="overview_filters"
|
||||||
>
|
v-model:visible-columns="VISIBLE_COLUMNS"
|
||||||
<q-th
|
class="q-mx-lg col-auto"
|
||||||
v-for="col in props.cols"
|
/>
|
||||||
:key="col.name"
|
</q-slide-transition>
|
||||||
:props="props"
|
|
||||||
>
|
|
||||||
<span class="text-uppercase text-weight-bolder text-white">
|
|
||||||
{{ $t(col.label) }}
|
|
||||||
</span>
|
|
||||||
</q-th>
|
|
||||||
</q-tr>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #body-cell="props">
|
<q-separator
|
||||||
<q-td
|
color="primary"
|
||||||
|
size="5px"
|
||||||
|
class="q-mx-lg q-my-none q-pa-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #header="props">
|
||||||
|
<q-tr
|
||||||
|
:props="props"
|
||||||
|
class="bg-primary"
|
||||||
|
>
|
||||||
|
<q-th
|
||||||
|
v-for="col in props.cols"
|
||||||
|
:key="col.name"
|
||||||
:props="props"
|
:props="props"
|
||||||
class="text-weight-medium"
|
|
||||||
>
|
>
|
||||||
<transition
|
<span class="text-uppercase text-weight-bolder text-white">
|
||||||
appear
|
{{ $t(col.label) }}
|
||||||
enter-active-class="animated fadeInUp slow"
|
</span>
|
||||||
leave-active-class="animated fadeOutDown"
|
|
||||||
mode="out-in"
|
<span
|
||||||
|
v-if="col.name.includes('weekly_hours')"
|
||||||
|
class="q-ml-sm text-uppercase text-weight-bolder text-white"
|
||||||
>
|
>
|
||||||
<div
|
{{ col.name.slice(-1,) }}
|
||||||
:key="props.rowIndex + (timesheet_store.pay_period?.pay_period_no ?? 0)"
|
</span>
|
||||||
class="rounded-5"
|
</q-th>
|
||||||
style="font-size: 1.2em;"
|
</q-tr>
|
||||||
:style="`animation-delay: ${props.rowIndex / 15}s; opacity: ${props.row.is_active ? '1' : '0.5'};`"
|
</template>
|
||||||
|
|
||||||
|
<template #body-cell="props">
|
||||||
|
<q-td
|
||||||
|
:props="props"
|
||||||
|
:class="props.rowIndex % 2 === 0 ? ($q.dark.isActive ? 'bg-primary' : 'bg-secondary') : ''"
|
||||||
|
>
|
||||||
|
<transition
|
||||||
|
appear
|
||||||
|
enter-active-class="animated fadeInUp"
|
||||||
|
leave-active-class="animated fadeOutDown"
|
||||||
|
mode="out-in"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:key="props.rowIndex + (timesheet_store.pay_period?.pay_period_no ?? 0)"
|
||||||
|
class="rounded-5"
|
||||||
|
:style="`animation-delay: ${props.rowIndex / 15}s; opacity: ${props.row.is_active ? '1' : '0.5'};`"
|
||||||
|
>
|
||||||
|
<!-- button to toggle approval status -->
|
||||||
|
<transition
|
||||||
|
v-if="props.col.name === 'is_approved'"
|
||||||
|
enter-active-class="animated swing"
|
||||||
|
mode="out-in"
|
||||||
>
|
>
|
||||||
<transition
|
<q-btn
|
||||||
v-if="props.col.name === 'is_approved'"
|
:key="props.row.is_approved"
|
||||||
enter-active-class="animated swing"
|
flat
|
||||||
mode="out-in"
|
dense
|
||||||
>
|
:icon="props.value ? 'lock' : 'lock_open'"
|
||||||
<q-btn
|
:color="props.value ? 'white' : 'grey-5'"
|
||||||
:key="props.row.is_approved"
|
class="rounded-5 "
|
||||||
flat
|
:class="props.value ? (props.row.is_active ? 'bg-accent' : 'bg-negative') : ''"
|
||||||
dense
|
@click.stop="onClickApproveAll(props.row.email, !props.row.is_approved)"
|
||||||
:icon="props.value ? 'lock' : 'lock_open'"
|
/>
|
||||||
:color="props.value ? 'white' : 'grey-5'"
|
</transition>
|
||||||
class="rounded-5 "
|
|
||||||
:class="props.value ? (props.row.is_active ? 'bg-accent' : 'bg-negative') : ''"
|
|
||||||
@click.stop="onClickApproveAll(props.row.email, !props.row.is_approved)"
|
|
||||||
/>
|
|
||||||
</transition>
|
|
||||||
|
|
||||||
<div v-else-if="props.col.name === 'employee_first_name'">
|
|
||||||
<span
|
|
||||||
class="text-h5 text-uppercase q-mr-xs"
|
|
||||||
:class="props.row.is_active ? 'text-accent' : 'text-negative'"
|
|
||||||
>
|
|
||||||
{{ props.value }}
|
|
||||||
</span>
|
|
||||||
<span class="text-uppercase text-weight-light">
|
|
||||||
{{ props.row.employee_last_name }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<!-- display full employee name with large first name and smaller last name -->
|
||||||
|
<div v-else-if="props.col.name === 'employee_first_name'">
|
||||||
<span
|
<span
|
||||||
v-else
|
class="text-h5 text-uppercase q-mr-xs"
|
||||||
:class="props.col.name === overview_column_names.REGULAR && props.row.overtime > 0 ? 'text-negative text-weight-bolder' : 'text-weight-regular'"
|
:class="props.row.is_active ? 'text-accent' : 'text-negative'"
|
||||||
>
|
>
|
||||||
{{ TIME_COLUMNS.includes(props.col.name) ?
|
{{ props.value }}
|
||||||
getHoursMinutesStringFromHoursFloat(props.value) : props.value }}
|
</span>
|
||||||
|
<span class="text-uppercase text-weight-light">
|
||||||
|
{{ props.row.employee_last_name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- display weekly total hours (regular, vacation, holiday, emergency, evening) -->
|
||||||
|
<div
|
||||||
|
v-else-if="props.col.name.includes('weekly_hours')"
|
||||||
|
class="q-px-xs"
|
||||||
|
:class="props.value[Number(props.col.name.slice(-1,)) - 1] > 40 ? 'bg-negative text-white rounded-5' : ''"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
getHoursMinutesStringFromHoursFloat((props.value[Number(props.col.name.slice(-1,)) -
|
||||||
|
1]) ?? 0) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
|
||||||
</q-td>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Template for individual employee cards -->
|
<!-- display total worked hours (regular, vacation, holiday, emergency, evening) -->
|
||||||
<template #item="props: { row: TimesheetApprovalOverview, rowIndex: number }">
|
<div
|
||||||
<OverviewListItem
|
v-else-if="props.col.name === 'total_hours'"
|
||||||
v-model="props.row.is_approved"
|
class="q-px-xs"
|
||||||
:key="props.row.email + timesheet_store.pay_period?.pay_period_no"
|
:class="props.value > 80 ? 'bg-negative rounded-5 text-white' : ''"
|
||||||
:index="props.rowIndex"
|
>
|
||||||
:row="props.row"
|
<span>{{ getHoursMinutesStringFromHoursFloat(props.value) }}</span>
|
||||||
@click-details="onClickedDetails"
|
</div>
|
||||||
@click-approval-all="is_approved => onClickApproveAll(props.row.email, is_approved)"
|
|
||||||
|
<!-- any other fields, though time fields will have their own conditional class to highlight abnormalities -->
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="q-px-xs"
|
||||||
|
:class="getListViewTimeClass(props.col.name, props.value)"
|
||||||
|
>
|
||||||
|
{{ TIME_COLUMNS.includes(props.col.name) ?
|
||||||
|
getHoursMinutesStringFromHoursFloat(props.value) : props.value }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Template for individual employee cards -->
|
||||||
|
<template #item="props: { row: TimesheetApprovalOverview, rowIndex: number }">
|
||||||
|
<OverviewListItem
|
||||||
|
v-model="props.row.is_approved"
|
||||||
|
:key="props.row.email + timesheet_store.pay_period?.pay_period_no"
|
||||||
|
:index="props.rowIndex"
|
||||||
|
:row="props.row"
|
||||||
|
@click-details="onClickedDetails"
|
||||||
|
@click-approval-all="is_approved => onClickApproveAll(props.row.email, is_approved)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Template for custome failed-to-load state -->
|
||||||
|
<template #no-data="{ message, filter }">
|
||||||
|
<div
|
||||||
|
v-if="!timesheet_store.is_loading"
|
||||||
|
class="full-width column items-center text-accent"
|
||||||
|
>
|
||||||
|
<q-icon
|
||||||
|
size="4em"
|
||||||
|
:name="filter ? 'filter_alt_off' : 'error_outline'"
|
||||||
/>
|
/>
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Template for custome failed-to-load state -->
|
<span class="text-h6">
|
||||||
<template #no-data="{ message, filter }">
|
{{ message }}
|
||||||
<div v-if="!timesheet_store.is_loading" class="full-width column items-center text-accent">
|
</span>
|
||||||
<q-icon
|
</div>
|
||||||
size="4em"
|
</template>
|
||||||
:name="filter ? 'filter_alt_off' : 'error_outline'"
|
</q-table>
|
||||||
/>
|
|
||||||
|
|
||||||
<span class="text-h6">
|
|
||||||
{{ message }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</q-table>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="sass">
|
<style lang="sass" scoped>
|
||||||
.sticky-header-table
|
.sticky-header-table
|
||||||
thead tr:first-child th
|
thead tr:first-child th
|
||||||
background-color: var(--q-primary)
|
background-color: var(--q-accent)
|
||||||
margin-top: none
|
margin-top: none
|
||||||
|
|
||||||
thead tr th
|
thead tr th
|
||||||
|
|
@ -313,4 +367,7 @@
|
||||||
|
|
||||||
.q-table__grid-content
|
.q-table__grid-content
|
||||||
overflow: auto
|
overflow: auto
|
||||||
|
|
||||||
|
td
|
||||||
|
min-width: 80px
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -19,6 +19,7 @@ export class TimesheetApprovalOverview {
|
||||||
holiday_hours: number;
|
holiday_hours: number;
|
||||||
vacation_hours: number;
|
vacation_hours: number;
|
||||||
};
|
};
|
||||||
|
weekly_hours: number[];
|
||||||
total_hours: number;
|
total_hours: number;
|
||||||
expenses: number;
|
expenses: number;
|
||||||
mileage: number;
|
mileage: number;
|
||||||
|
|
@ -39,6 +40,7 @@ export class TimesheetApprovalOverview {
|
||||||
holiday_hours: 0,
|
holiday_hours: 0,
|
||||||
vacation_hours: 0,
|
vacation_hours: 0,
|
||||||
}
|
}
|
||||||
|
this.weekly_hours = [0];
|
||||||
this.total_hours = 0;
|
this.total_hours = 0;
|
||||||
this.expenses = 0;
|
this.expenses = 0;
|
||||||
this.mileage = 0;
|
this.mileage = 0;
|
||||||
|
|
@ -63,7 +65,7 @@ export interface PayPeriodOverviewFilters {
|
||||||
name_search_string: string;
|
name_search_string: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OverviewColumns = 'employee_first_name' | 'employee_last_name' | 'email' | 'REGULAR' | 'EVENING' | 'EMERGENCY' | 'SICK' | 'HOLIDAY' | 'VACATION' | 'OVERTIME' | 'expenses' | 'mileage' | 'is_approved' | 'is_active'
|
export type OverviewColumns = 'employee_first_name' | 'employee_last_name' | 'email' | 'REGULAR' | 'EVENING' | 'EMERGENCY' | 'SICK' | 'HOLIDAY' | 'VACATION' | 'OVERTIME' | 'expenses' | 'mileage' | 'total_hours' | 'weekly_hours_1' | 'weekly_hours_2' | 'is_approved' | 'is_active'
|
||||||
|
|
||||||
export const overview_column_names = {
|
export const overview_column_names = {
|
||||||
FIRST_NAME: 'employee_first_name',
|
FIRST_NAME: 'employee_first_name',
|
||||||
|
|
@ -78,6 +80,9 @@ export const overview_column_names = {
|
||||||
OVERTIME: 'OVERTIME',
|
OVERTIME: 'OVERTIME',
|
||||||
EXPENSES: 'expenses',
|
EXPENSES: 'expenses',
|
||||||
MILEAGE: 'mileage',
|
MILEAGE: 'mileage',
|
||||||
|
WEEKLY_HOURS_1: 'weekly_hours_1',
|
||||||
|
WEEKLY_HOURS_2: 'weekly_hours_2',
|
||||||
|
TOTAL_HOURS: 'total_hours',
|
||||||
IS_APPROVED: 'is_approved',
|
IS_APPROVED: 'is_approved',
|
||||||
IS_ACTIVE: 'is_active',
|
IS_ACTIVE: 'is_active',
|
||||||
}
|
}
|
||||||
|
|
@ -89,13 +94,14 @@ export const pay_period_overview_columns: QTableColumn[] = [
|
||||||
align: 'left',
|
align: 'left',
|
||||||
field: overview_column_names.FIRST_NAME,
|
field: overview_column_names.FIRST_NAME,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
required: true,
|
required: true,
|
||||||
|
style: 'width: 10vw;'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: overview_column_names.LAST_NAME,
|
name: overview_column_names.LAST_NAME,
|
||||||
label: 'timesheet_approvals.table.full_name',
|
label: 'timesheet_approvals.table.full_name',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
field: 'employee_last_name',
|
field: '',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -104,27 +110,28 @@ export const pay_period_overview_columns: QTableColumn[] = [
|
||||||
align: 'left',
|
align: 'left',
|
||||||
field: overview_column_names.EMAIL,
|
field: overview_column_names.EMAIL,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
|
style: 'width: 10vw;'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: overview_column_names.REGULAR,
|
name: overview_column_names.REGULAR,
|
||||||
label: 'shared.shift_type.regular',
|
label: 'shared.shift_type.regular',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
field: 'regular_hours',
|
field: 'regular_hours',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: overview_column_names.EVENING,
|
name: overview_column_names.EVENING,
|
||||||
label: 'shared.shift_type.evening',
|
label: 'shared.shift_type.evening',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
field: row => row.other_hours.evening_hours,
|
field: row => row.other_hours.evening_hours,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: overview_column_names.EMERGENCY,
|
name: overview_column_names.EMERGENCY,
|
||||||
label: 'shared.shift_type.emergency',
|
label: 'shared.shift_type.emergency',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
field: row => row.other_hours.emergency_hours,
|
field: row => row.other_hours.emergency_hours,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: overview_column_names.SICK,
|
name: overview_column_names.SICK,
|
||||||
|
|
@ -168,6 +175,27 @@ export const pay_period_overview_columns: QTableColumn[] = [
|
||||||
field: 'mileage',
|
field: 'mileage',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: overview_column_names.WEEKLY_HOURS_1,
|
||||||
|
label: 'timesheet.week',
|
||||||
|
align: 'left',
|
||||||
|
field: 'weekly_hours',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: overview_column_names.WEEKLY_HOURS_2,
|
||||||
|
label: 'timesheet.week',
|
||||||
|
align: 'left',
|
||||||
|
field: 'weekly_hours',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: overview_column_names.TOTAL_HOURS,
|
||||||
|
label: 'timesheet_approvals.table.total_hours',
|
||||||
|
align: 'left',
|
||||||
|
field: 'total_hours',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: overview_column_names.IS_APPROVED,
|
name: overview_column_names.IS_APPROVED,
|
||||||
label: 'timesheet_approvals.table.is_approved',
|
label: 'timesheet_approvals.table.is_approved',
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
>
|
>
|
||||||
import { date } from 'quasar';
|
import { date } from 'quasar';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, onMounted, ref } from 'vue';
|
||||||
import { useUiStore } from 'src/stores/ui-store';
|
import { useUiStore } from 'src/stores/ui-store';
|
||||||
import { useExpensesStore } from 'src/stores/expense-store';
|
import { useExpensesStore } from 'src/stores/expense-store';
|
||||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||||
|
|
@ -40,7 +40,7 @@
|
||||||
{ label: t('timesheet.expense.types.ON_CALL'), value: 'ON_CALL', icon: getExpenseIcon('ON_CALL') },
|
{ label: t('timesheet.expense.types.ON_CALL'), value: 'ON_CALL', icon: getExpenseIcon('ON_CALL') },
|
||||||
]
|
]
|
||||||
|
|
||||||
const expense_selected = ref(expense_options.find(expense => expense.value == expenses_store.current_expense.type));
|
const expense_selected = ref<ExpenseOption | undefined>();
|
||||||
|
|
||||||
const openDatePicker = () => {
|
const openDatePicker = () => {
|
||||||
is_navigator_open.value = true;
|
is_navigator_open.value = true;
|
||||||
|
|
@ -61,6 +61,10 @@
|
||||||
expenses_store.mode = 'create';
|
expenses_store.mode = 'create';
|
||||||
expenses_store.current_expense = new Expense(date.formatDate(new Date(), 'YYYY-MM-DD'));
|
expenses_store.current_expense = new Expense(date.formatDate(new Date(), 'YYYY-MM-DD'));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
expense_selected.value = expense_options.find(expense => expense.value === expenses_store.current_expense.type);
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -73,10 +73,19 @@
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<q-icon
|
<q-icon
|
||||||
:name="getExpenseIcon(expense.type)"
|
:name="getExpenseIcon(expense.type)"
|
||||||
:color="expense.is_approved ? 'white' : ($q.dark.isActive ? 'white' : 'primary')"
|
:color="expense.is_approved ? 'white' : ($q.dark.isActive ? 'white' : 'blue-grey-8')"
|
||||||
size="lg"
|
size="lg"
|
||||||
class="q-pr-md"
|
class="q-pr-md"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<q-tooltip
|
||||||
|
anchor="top middle"
|
||||||
|
self="center middle"
|
||||||
|
:offset="[0, 20]"
|
||||||
|
class="bg-accent text-uppercase text-weight-bold"
|
||||||
|
>
|
||||||
|
{{ $t(`timesheet.expense.types.${expense.type}`) }}
|
||||||
|
</q-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- amount or mileage section -->
|
<!-- amount or mileage section -->
|
||||||
|
|
@ -118,18 +127,27 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- comment section -->
|
<!-- comment section -->
|
||||||
<div class="col column">
|
<div class="col column no-wrap">
|
||||||
<span class="col-auto text-weight-bold text-accent text-uppercase text-caption">
|
<span class="col-auto text-weight-bold text-accent text-uppercase text-caption">
|
||||||
{{ $t('timesheet.expense.employee_comment') }}
|
{{ $t('timesheet.expense.employee_comment') }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
class="col"
|
class="col ellipsis"
|
||||||
:class="expense.is_approved ? ' bg-accent text-white' : ''"
|
:class="expense.is_approved ? ' bg-accent text-white' : ''"
|
||||||
style="font-size: 1.3em;"
|
style="font-size: 1em;"
|
||||||
>
|
>
|
||||||
{{ expense.comment }}
|
{{ expense.comment }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<q-tooltip
|
||||||
|
anchor="top middle"
|
||||||
|
self="center middle"
|
||||||
|
:offset="[0, 20]"
|
||||||
|
class="bg-accent text-uppercase text-weight-bold"
|
||||||
|
>
|
||||||
|
{{ expense.comment }}
|
||||||
|
</q-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- supervisor comment section -->
|
<!-- supervisor comment section -->
|
||||||
|
|
@ -167,7 +185,8 @@
|
||||||
:offset="[0, 20]"
|
:offset="[0, 20]"
|
||||||
class="bg-accent text-uppercase text-weight-bold"
|
class="bg-accent text-uppercase text-weight-bold"
|
||||||
>
|
>
|
||||||
{{ expense.is_approved ? $t('timesheet_approvals.tooltip.unapprove') : $t('timesheet_approvals.tooltip.approve') }}
|
{{ expense.is_approved ? $t('timesheet_approvals.tooltip.unapprove') :
|
||||||
|
$t('timesheet_approvals.tooltip.approve') }}
|
||||||
</q-tooltip>
|
</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onBlurShiftTypeSelect = () => {
|
||||||
|
if (shift_type_selected.value === undefined) {
|
||||||
|
shift.value.type = 'REGULAR';
|
||||||
|
shift.value.id = 0;
|
||||||
|
emit('requestDelete');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const getCommentCounterColor = (comment_length: number) => {
|
const getCommentCounterColor = (comment_length: number) => {
|
||||||
if (comment_length < 200) return 'primary';
|
if (comment_length < 200) return 'primary';
|
||||||
if (comment_length < 250) return 'warning';
|
if (comment_length < 250) return 'warning';
|
||||||
|
|
@ -66,7 +74,7 @@
|
||||||
>
|
>
|
||||||
<!-- shift type -->
|
<!-- shift type -->
|
||||||
<q-select
|
<q-select
|
||||||
ref="select"
|
ref="select_ref"
|
||||||
v-model="shift_type_selected"
|
v-model="shift_type_selected"
|
||||||
:standout="$q.dark.isActive ? 'bg-blue-grey-3' : 'bg-blue-grey-9'"
|
:standout="$q.dark.isActive ? 'bg-blue-grey-3' : 'bg-blue-grey-9'"
|
||||||
dense
|
dense
|
||||||
|
|
@ -83,6 +91,7 @@
|
||||||
popup-content-class="text-uppercase text-weight-bold text-center rounded-5"
|
popup-content-class="text-uppercase text-weight-bold text-center rounded-5"
|
||||||
:style="shift.is_approved ? 'background-color: #0a7d32 !important;' : ''"
|
:style="shift.is_approved ? 'background-color: #0a7d32 !important;' : ''"
|
||||||
popup-content-style="border: 2px solid var(--q-accent)"
|
popup-content-style="border: 2px solid var(--q-accent)"
|
||||||
|
@blur="onBlurShiftTypeSelect"
|
||||||
@update:model-value="option => shift.type = option.value"
|
@update:model-value="option => shift.type = option.value"
|
||||||
>
|
>
|
||||||
<template #selected-item="scope">
|
<template #selected-item="scope">
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,10 @@
|
||||||
>
|
>
|
||||||
import ShiftList from 'src/modules/timesheets/components/shift-list.vue';
|
import ShiftList from 'src/modules/timesheets/components/shift-list.vue';
|
||||||
|
|
||||||
import { useQuasar } from 'quasar';
|
import { computed, ref } from 'vue';
|
||||||
import { computed, ref, watch } from '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 type { QScrollArea, TouchSwipeValue } from 'quasar';
|
import type { QScrollArea, TouchSwipeValue } from 'quasar';
|
||||||
|
|
||||||
const q = useQuasar();
|
|
||||||
|
|
||||||
const timesheet_store = useTimesheetStore();
|
const timesheet_store = useTimesheetStore();
|
||||||
const timesheet_api = useTimesheetApi();
|
const timesheet_api = useTimesheetApi();
|
||||||
|
|
@ -22,8 +19,6 @@
|
||||||
const mobile_animation_direction = ref('fadeInLeft');
|
const mobile_animation_direction = ref('fadeInLeft');
|
||||||
|
|
||||||
const timesheet_page = ref<QScrollArea | null>(null);
|
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 scroll_y = computed(() => timesheet_page.value?.getScrollPosition().top ?? 0);
|
||||||
|
|
||||||
|
|
@ -34,12 +29,10 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(currentDayComponentWatcher, () => {
|
const onTodayComponentFound = (today_component: HTMLElement | undefined) => {
|
||||||
if (currentDayComponent.value && timesheet_page.value && q.platform.is.mobile) {
|
if (timesheet_page.value && today_component)
|
||||||
timesheet_page.value.setScrollPosition('vertical', currentDayComponent.value[0]!.offsetTop, 800);
|
timesheet_page.value.setScrollPosition('vertical', today_component.offsetTop, 800);
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -73,7 +66,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Else show timesheets if found -->
|
<!-- Else show timesheets if found -->
|
||||||
<ShiftList />
|
<ShiftList @on-current-day-component-found="onTodayComponentFound" />
|
||||||
</q-scroll-area>
|
</q-scroll-area>
|
||||||
|
|
||||||
<q-page-sticky
|
<q-page-sticky
|
||||||
|
|
|
||||||
|
|
@ -5,27 +5,34 @@
|
||||||
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 { ref, computed } from 'vue';
|
import { date, useQuasar } from 'quasar';
|
||||||
|
import { ref, computed, 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 { 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 mobile_animation_direction = ref('fadeInLeft');
|
||||||
|
const currentDayComponent = ref<HTMLElement[] | null>(null);
|
||||||
|
const currentDayComponentWatcher = ref(currentDayComponent);
|
||||||
|
|
||||||
const animation_style = computed(() => ui_store.is_mobile_mode ? mobile_animation_direction.value : 'fadeInDown');
|
const animation_style = computed(() => ui_store.is_mobile_mode ? mobile_animation_direction.value : 'fadeInDown');
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'onCurrentDayComponentFound': [component: HTMLElement | undefined];
|
||||||
|
}>();
|
||||||
|
|
||||||
const addNewShift = (day_shifts: Shift[], date: string, timesheet_id: number) => {
|
const addNewShift = (day_shifts: Shift[], date: string, timesheet_id: number) => {
|
||||||
ui_store.focus_next_component = true;
|
ui_store.focus_next_component = true;
|
||||||
const new_shift = new Shift;
|
const new_shift = new Shift;
|
||||||
|
|
@ -50,6 +57,12 @@
|
||||||
const getMobileDayRef = (iso_date_string: string): string => {
|
const getMobileDayRef = (iso_date_string: string): string => {
|
||||||
return iso_date_string === CURRENT_DATE_STRING ? 'currentDayComponent' : '';
|
return iso_date_string === CURRENT_DATE_STRING ? 'currentDayComponent' : '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
watch(currentDayComponentWatcher, () => {
|
||||||
|
if (currentDayComponent.value && q.platform.is.mobile) {
|
||||||
|
emit('onCurrentDayComponentFound', currentDayComponent.value[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,27 @@
|
||||||
lang="ts"
|
lang="ts"
|
||||||
>
|
>
|
||||||
import { useShiftStore } from 'src/stores/shift-store';
|
import { useShiftStore } from 'src/stores/shift-store';
|
||||||
import { computed } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
const shift_store = useShiftStore();
|
const shift_store = useShiftStore();
|
||||||
|
const is_counting_error_display_time = ref(false);
|
||||||
|
|
||||||
const is_showing_errors = computed(() => shift_store.shift_errors.length > 0);
|
const is_showing_errors = computed(() => {
|
||||||
|
if (shift_store.shift_errors.length > 0 && !is_counting_error_display_time.value) {
|
||||||
|
onShowingErrorMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return shift_store.shift_errors.length > 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const onShowingErrorMessage = () => {
|
||||||
|
is_counting_error_display_time.value = true;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
is_counting_error_display_time.value = false
|
||||||
|
shift_store.shift_errors = [];
|
||||||
|
}, 5000);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import { useRouter } from 'vue-router';
|
||||||
<div class=" column justify-center" :class="$q.platform.is.mobile ? 'col-11' : 'col-8'">
|
<div class=" column justify-center" :class="$q.platform.is.mobile ? 'col-11' : 'col-8'">
|
||||||
<div class="column rounded-20 q-pa-xs bg-accent" :class="$q.platform.is.mobile ? 'col-5' : 'col-4'">
|
<div class="column rounded-20 q-pa-xs bg-accent" :class="$q.platform.is.mobile ? 'col-5' : 'col-4'">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<q-img src="src/assets/line-truck-1.jpg" fit="cover" class="relative-position fit border-radius-inherit">
|
<q-img src="src/assets/line-truck-1.jpg" fit="cover" class="relative-position fit" style="border-radius: 18px 18px 0 0;">
|
||||||
<div class="absolute-bottom text-center column flex-center">
|
<div class="absolute-bottom text-center column flex-center">
|
||||||
<div class="q-pr-md text-white text-h2 text-weight-bolder">404</div>
|
<div class="q-pr-md text-white text-h2 text-weight-bolder">404</div>
|
||||||
<div class="q-pr-md text-white text-h5 text-weight-bold">{{
|
<div class="q-pr-md text-white text-h5 text-weight-bold">{{
|
||||||
|
|
@ -22,7 +22,7 @@ import { useRouter } from 'vue-router';
|
||||||
</q-img>
|
</q-img>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-auto text-center text-h6 text-weight-light bg-dark q-pa-md">
|
<div class="col-auto text-center text-h6 text-weight-light bg-dark q-pa-md" style="border-radius: 0 0 18px 18px;">
|
||||||
<div>{{ $t('error.not_found_description') }}</div>
|
<div>{{ $t('error.not_found_description') }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,8 @@
|
||||||
const timesheet_store = useTimesheetStore();
|
const timesheet_store = useTimesheetStore();
|
||||||
|
|
||||||
const page_height = ref(0);
|
const page_height = ref(0);
|
||||||
const headerComponent = ref<HTMLElement | null>(null);
|
const header_height = ref(0);
|
||||||
|
const table_max_height = computed(() => page_height.value - header_height.value);
|
||||||
const table_max_height = computed(() => {
|
|
||||||
const height = page_height.value - Math.min(headerComponent.value?.clientHeight ?? 0, headerComponent.value?.offsetHeight ?? 0);
|
|
||||||
return height;
|
|
||||||
});
|
|
||||||
|
|
||||||
const tableStyleFunction = (offset: number, height: number) => {
|
const tableStyleFunction = (offset: number, height: number) => {
|
||||||
page_height.value = height - offset;
|
page_height.value = height - offset;
|
||||||
|
|
@ -60,6 +56,7 @@
|
||||||
title="timesheet_approvals.page_title"
|
title="timesheet_approvals.page_title"
|
||||||
:start-date="timesheet_store.pay_period?.period_start ?? ''"
|
:start-date="timesheet_store.pay_period?.period_start ?? ''"
|
||||||
:end-date="timesheet_store.pay_period?.period_end ?? ''"
|
:end-date="timesheet_store.pay_period?.period_end ?? ''"
|
||||||
|
@on-get-component-height="value => header_height = value"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ export const useTimesheetStore = defineStore('timesheet', () => {
|
||||||
const selected_employee_name = ref<string>();
|
const selected_employee_name = ref<string>();
|
||||||
const has_timesheet_preset = ref(false);
|
const has_timesheet_preset = ref(false);
|
||||||
const current_pay_period_overview = ref<TimesheetApprovalOverview>();
|
const current_pay_period_overview = ref<TimesheetApprovalOverview>();
|
||||||
const is_approval_grid_mode = ref<boolean>(true);
|
|
||||||
const pay_period_report = ref();
|
const pay_period_report = ref();
|
||||||
|
|
||||||
const getNextOrPreviousPayPeriod = (direction: number) => {
|
const getNextOrPreviousPayPeriod = (direction: number) => {
|
||||||
|
|
@ -169,7 +168,6 @@ export const useTimesheetStore = defineStore('timesheet', () => {
|
||||||
return {
|
return {
|
||||||
is_loading,
|
is_loading,
|
||||||
is_report_dialog_open,
|
is_report_dialog_open,
|
||||||
is_approval_grid_mode,
|
|
||||||
is_details_dialog_open,
|
is_details_dialog_open,
|
||||||
pay_period,
|
pay_period,
|
||||||
pay_period_overviews,
|
pay_period_overviews,
|
||||||
|
|
|
||||||
|
|
@ -23,5 +23,5 @@ export const getMinutes = (hours: number) => {
|
||||||
export const getHoursMinutesStringFromHoursFloat = (hours: number): string => {
|
export const getHoursMinutesStringFromHoursFloat = (hours: number): string => {
|
||||||
const flat_hours = Math.floor(hours);
|
const flat_hours = Math.floor(hours);
|
||||||
const minutes = Math.round((hours - flat_hours) * 60);
|
const minutes = Math.round((hours - flat_hours) * 60);
|
||||||
return `${flat_hours}h ${minutes > 1 ? minutes : ''}`
|
return `${flat_hours}h${minutes > 1 ? ' ' + minutes : ''}`
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user