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",
|
||||
unverified: "pending",
|
||||
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_team: "show my team only",
|
||||
filter_columns: "Information displayed",
|
||||
},
|
||||
tooltip: {
|
||||
button_detailed_view: "detailed view",
|
||||
|
|
|
|||
|
|
@ -354,8 +354,20 @@ export default {
|
|||
verified: "approuvé",
|
||||
unverified: "à vérifier",
|
||||
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_team: "montrer mon équipe seulement",
|
||||
filter_columns: "informations affichés",
|
||||
},
|
||||
tooltip: {
|
||||
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>
|
||||
<q-footer elevated class="bg-primary text-white">
|
||||
<q-toolbar>
|
||||
<q-toolbar-title>© 2025 Targo Communications inc.</q-toolbar-title>
|
||||
</q-toolbar>
|
||||
</q-footer>
|
||||
<q-footer
|
||||
elevated
|
||||
class="bg-primary text-white"
|
||||
>
|
||||
<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>
|
||||
|
|
@ -76,7 +76,7 @@
|
|||
rounded
|
||||
disabled
|
||||
type="submit"
|
||||
color="accent"
|
||||
color="grey-5"
|
||||
:label="$t('login.button.connect')"
|
||||
class="full-width q-mt-lg"
|
||||
/>
|
||||
|
|
@ -108,7 +108,7 @@
|
|||
rounded
|
||||
push
|
||||
disabled
|
||||
color="fb-blue"
|
||||
color="blue-grey-7"
|
||||
icon="img:src/assets/Facebook-f_Logo-White-Logo.wine.svg"
|
||||
:label="$t('login.button.facebook')"
|
||||
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[] = [
|
||||
{ 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.search', path: search_bar, description: search_bar_desc, icon: 'search' },
|
||||
];
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
setup
|
||||
lang="ts"
|
||||
>
|
||||
/* eslint-disable */
|
||||
import { date, useQuasar } from 'quasar';
|
||||
import { computed } from 'vue';
|
||||
import { computed, onMounted, onUpdated, ref } from 'vue';
|
||||
|
||||
const { title, startDate = "", endDate = "" } = defineProps<{
|
||||
title: string;
|
||||
|
|
@ -13,17 +14,36 @@
|
|||
|
||||
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', });
|
||||
|
||||
onUpdated(() => {
|
||||
if (selfRef.value) {
|
||||
emit('onGetComponentHeight', selfRef.value.offsetHeight);
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (selfRef.value) {
|
||||
emit('onGetComponentHeight', selfRef.value.offsetHeight);
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="column text-uppercase text-center text-weight-bolder text-h4">
|
||||
<span
|
||||
<div
|
||||
ref="selfRef"
|
||||
class="column text-uppercase text-center text-weight-bolder text-h4 q-pt-md"
|
||||
>
|
||||
<!-- <span
|
||||
v-if="!$q.platform.is.mobile"
|
||||
class="col q-pt-lg"
|
||||
>
|
||||
{{ $t(title) }}
|
||||
</span>
|
||||
</span> -->
|
||||
|
||||
<transition
|
||||
enter-active-class="animated fadeInDown"
|
||||
|
|
|
|||
|
|
@ -5,17 +5,17 @@
|
|||
<template>
|
||||
<q-input
|
||||
v-model="search_model"
|
||||
outlined
|
||||
:dark="false"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
debounce="300"
|
||||
:label="$t('shared.label.search')"
|
||||
color="accent"
|
||||
bg-color="white"
|
||||
label-color="accent"
|
||||
class="text-primary"
|
||||
bg-color="white"
|
||||
label-color="accent"
|
||||
>
|
||||
<template #prepend>
|
||||
<template #append>
|
||||
<q-icon
|
||||
name="search"
|
||||
color="accent"
|
||||
|
|
|
|||
|
|
@ -2,34 +2,62 @@
|
|||
setup
|
||||
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>
|
||||
|
||||
<template>
|
||||
<div class="column bg-primary text-uppercase">
|
||||
<div class="column bg-primary text-uppercase q-px-sm text-white">
|
||||
<div class="col row">
|
||||
<q-checkbox
|
||||
v-model="filters.is_showing_inactive"
|
||||
keep-color
|
||||
size="lg"
|
||||
color="accent"
|
||||
: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'"
|
||||
/>
|
||||
|
||||
|
||||
<q-checkbox
|
||||
v-model="filters.is_showing_team_only"
|
||||
keep-color
|
||||
size="lg"
|
||||
val="team"
|
||||
color="accent"
|
||||
: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'"
|
||||
/>
|
||||
</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>
|
||||
</template>
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
lang="ts"
|
||||
>
|
||||
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>();
|
||||
|
||||
|
|
@ -82,7 +82,7 @@
|
|||
> {{
|
||||
$t('shared.shift_type.regular') }} </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' : ''"
|
||||
> {{ getHoursMinutesStringFromHoursFloat(row.regular_hours) }} </span>
|
||||
<q-separator class="q-mr-sm" />
|
||||
|
|
@ -94,7 +94,7 @@
|
|||
v-for="hour_type, index in row.other_hours"
|
||||
:key="index"
|
||||
class="col-4 column ellipsis"
|
||||
:class="hour_type === 0 ? 'invisible' : ''"
|
||||
:class="hour_type === 0 ? 'invisible order-last' : 'order-first'"
|
||||
>
|
||||
<span
|
||||
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', '')}`) }}
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="text-weight-bolder q-pa-none q-mb-xs"
|
||||
style="font-size: 1.2em; line-height: 1em;"
|
||||
> {{ getHoursMinutesStringFromHoursFloat(hour_type) }} </span>
|
||||
>
|
||||
{{ getHoursMinutesStringFromHoursFloat(hour_type) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -144,20 +147,47 @@
|
|||
|
||||
<!-- Validate Pay Period section -->
|
||||
<q-card-section
|
||||
horizontal
|
||||
class="justify-between items-center text-weight-bold q-pa-none"
|
||||
class="justify-between items-center q-pa-none"
|
||||
: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
|
||||
v-if="row.is_active"
|
||||
class="col row full-width"
|
||||
>
|
||||
<div class="col text-uppercase">
|
||||
<span class="text-h6 q-ml-sm text-weight-bolder">{{ 'Total : ' + Math.floor(row.total_hours)
|
||||
}}</span>
|
||||
<span class="text-uppercase text-weight-medium text-caption">H</span>
|
||||
<span class="text-h6 q-ml-sm text-weight-bolder">{{ getMinutes(row.total_hours) }}</span>
|
||||
<span class="text-uppercase text-weight-medium text-caption">M</span>
|
||||
<span class="text-h6 q-ml-sm text-weight-bolder">Total</span>
|
||||
<span
|
||||
class="q-ml-sm text-h6"
|
||||
:class="row.total_hours > 80 ? 'bg-negative q-px-sm rounded-5 text-white' : 'text-weight-light'"
|
||||
>
|
||||
{{ getHoursMinutesStringFromHoursFloat(row.total_hours) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="col-auto q-py-xs q-px-md">
|
||||
|
|
|
|||
|
|
@ -13,15 +13,19 @@
|
|||
import { useAuthStore } from 'src/stores/auth-store';
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
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 { 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 timesheet_store = useTimesheetStore();
|
||||
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[]>([
|
||||
'employee_first_name',
|
||||
'REGULAR',
|
||||
|
|
@ -32,6 +36,9 @@
|
|||
'VACATION',
|
||||
'expenses',
|
||||
'mileage',
|
||||
'weekly_hours_1',
|
||||
'weekly_hours_2',
|
||||
'total_hours',
|
||||
'is_approved',
|
||||
]);
|
||||
|
||||
|
|
@ -74,229 +81,276 @@
|
|||
if (terms.name_search_string.length > 0) {
|
||||
const search_words = terms.name_search_string.trim().split(' ');
|
||||
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;
|
||||
};
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
<div class="full-width">
|
||||
<LoadingOverlay v-model="timesheet_store.is_loading" />
|
||||
<q-table
|
||||
dense
|
||||
row-key="email"
|
||||
color="accent"
|
||||
hide-pagination
|
||||
:rows="overview_rows"
|
||||
:columns="pay_period_overview_columns"
|
||||
:visible-columns="VISIBLE_COLUMNS"
|
||||
:grid="timesheet_store.is_approval_grid_mode"
|
||||
:pagination="{ sortBy: 'is_active' }"
|
||||
:filter="overview_filters"
|
||||
:filter-method="filterEmployeeRows"
|
||||
:rows-per-page-options="[0]"
|
||||
class="bg-transparent"
|
||||
:class="timesheet_store.is_approval_grid_mode ? '' : 'sticky-header-table no-shadow'"
|
||||
card-container-class="justify-center"
|
||||
table-class="q-pa-none q-mx-md rounded-10 bg-dark shadow-15 hide-scrollbar"
|
||||
:no-data-label="$t('shared.error.no_data_found')"
|
||||
:no-results-label="$t('shared.error.no_search_results')"
|
||||
:loading-label="$t('shared.label.loading')"
|
||||
:style="overview_rows.length > 0 ? `max-height: ${maxHeight - (timesheet_store.is_approval_grid_mode ? 0 : 20)}px;` : ''"
|
||||
@row-click="(_evt, row: TimesheetApprovalOverview) => onClickedDetails(row)"
|
||||
>
|
||||
<template #top>
|
||||
<div class="column full-width">
|
||||
<q-table
|
||||
dense
|
||||
row-key="email"
|
||||
color="accent"
|
||||
hide-pagination
|
||||
:rows="overview_rows"
|
||||
:columns="pay_period_overview_columns"
|
||||
:table-colspan="pay_period_overview_columns.length"
|
||||
:visible-columns="VISIBLE_COLUMNS"
|
||||
:grid="ui_store.user_preferences.is_timesheet_approval_grid"
|
||||
:pagination="{ sortBy: 'is_active' }"
|
||||
:filter="overview_filters"
|
||||
:filter-method="filterEmployeeRows"
|
||||
:rows-per-page-options="[0]"
|
||||
class="bg-transparent"
|
||||
:class="ui_store.user_preferences.is_timesheet_approval_grid ? '' : 'sticky-header-table no-shadow'"
|
||||
card-container-class="justify-center"
|
||||
table-class="q-pa-none q-mx-md rounded-10 bg-dark shadow-15 hide-scrollbar"
|
||||
:no-data-label="$t('shared.error.no_data_found')"
|
||||
:no-results-label="$t('shared.error.no_search_results')"
|
||||
:loading-label="$t('shared.label.loading')"
|
||||
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;` : ''"
|
||||
:table-style="{ tableLayout: 'fixed'}"
|
||||
@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
|
||||
class="col-auto row items-start full-width q-px-lg"
|
||||
:class="$q.platform.is.mobile ? 'column flex-center' : 'row q-mt-md'"
|
||||
class="col-auto row no-wrap items-start"
|
||||
:class="$q.platform.is.mobile ? 'q-mb-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' : ''"
|
||||
<q-btn-toggle
|
||||
v-model="ui_store.user_preferences.is_timesheet_approval_grid"
|
||||
push
|
||||
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-space />
|
||||
|
||||
<div
|
||||
class="col-auto row no-wrap items-start"
|
||||
:class="$q.platform.is.mobile ? 'q-mb-md' : ''"
|
||||
>
|
||||
<q-btn-toggle
|
||||
v-model="timesheet_store.is_approval_grid_mode"
|
||||
push
|
||||
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-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"
|
||||
/>
|
||||
</q-slide-transition>
|
||||
|
||||
<q-separator
|
||||
color="primary"
|
||||
size="5px"
|
||||
class="q-mx-lg q-my-none q-pa-none"
|
||||
/>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<template #header="props">
|
||||
<q-tr
|
||||
:props="props"
|
||||
class="bg-primary"
|
||||
>
|
||||
<q-th
|
||||
v-for="col in props.cols"
|
||||
:key="col.name"
|
||||
:props="props"
|
||||
>
|
||||
<span class="text-uppercase text-weight-bolder text-white">
|
||||
{{ $t(col.label) }}
|
||||
</span>
|
||||
</q-th>
|
||||
</q-tr>
|
||||
</template>
|
||||
<q-slide-transition>
|
||||
<OverviewListFilters
|
||||
v-if="is_showing_filters"
|
||||
v-model:filters="overview_filters"
|
||||
v-model:visible-columns="VISIBLE_COLUMNS"
|
||||
class="q-mx-lg col-auto"
|
||||
/>
|
||||
</q-slide-transition>
|
||||
|
||||
<template #body-cell="props">
|
||||
<q-td
|
||||
<q-separator
|
||||
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"
|
||||
class="text-weight-medium"
|
||||
>
|
||||
<transition
|
||||
appear
|
||||
enter-active-class="animated fadeInUp slow"
|
||||
leave-active-class="animated fadeOutDown"
|
||||
mode="out-in"
|
||||
<span class="text-uppercase text-weight-bolder text-white">
|
||||
{{ $t(col.label) }}
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-if="col.name.includes('weekly_hours')"
|
||||
class="q-ml-sm text-uppercase text-weight-bolder text-white"
|
||||
>
|
||||
<div
|
||||
:key="props.rowIndex + (timesheet_store.pay_period?.pay_period_no ?? 0)"
|
||||
class="rounded-5"
|
||||
style="font-size: 1.2em;"
|
||||
:style="`animation-delay: ${props.rowIndex / 15}s; opacity: ${props.row.is_active ? '1' : '0.5'};`"
|
||||
{{ col.name.slice(-1,) }}
|
||||
</span>
|
||||
</q-th>
|
||||
</q-tr>
|
||||
</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
|
||||
v-if="props.col.name === 'is_approved'"
|
||||
enter-active-class="animated swing"
|
||||
mode="out-in"
|
||||
>
|
||||
<q-btn
|
||||
:key="props.row.is_approved"
|
||||
flat
|
||||
dense
|
||||
:icon="props.value ? 'lock' : 'lock_open'"
|
||||
:color="props.value ? 'white' : 'grey-5'"
|
||||
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>
|
||||
<q-btn
|
||||
:key="props.row.is_approved"
|
||||
flat
|
||||
dense
|
||||
:icon="props.value ? 'lock' : 'lock_open'"
|
||||
:color="props.value ? 'white' : 'grey-5'"
|
||||
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>
|
||||
|
||||
<!-- display full employee name with large first name and smaller last name -->
|
||||
<div v-else-if="props.col.name === 'employee_first_name'">
|
||||
<span
|
||||
v-else
|
||||
:class="props.col.name === overview_column_names.REGULAR && props.row.overtime > 0 ? 'text-negative text-weight-bolder' : 'text-weight-regular'"
|
||||
class="text-h5 text-uppercase q-mr-xs"
|
||||
:class="props.row.is_active ? 'text-accent' : 'text-negative'"
|
||||
>
|
||||
{{ TIME_COLUMNS.includes(props.col.name) ?
|
||||
getHoursMinutesStringFromHoursFloat(props.value) : 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>
|
||||
</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)"
|
||||
<!-- display total worked hours (regular, vacation, holiday, emergency, evening) -->
|
||||
<div
|
||||
v-else-if="props.col.name === 'total_hours'"
|
||||
class="q-px-xs"
|
||||
:class="props.value > 80 ? 'bg-negative rounded-5 text-white' : ''"
|
||||
>
|
||||
<span>{{ getHoursMinutesStringFromHoursFloat(props.value) }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 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 -->
|
||||
<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'"
|
||||
/>
|
||||
|
||||
<span class="text-h6">
|
||||
{{ message }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</q-table>
|
||||
<span class="text-h6">
|
||||
{{ message }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</q-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="sass">
|
||||
<style lang="sass" scoped>
|
||||
.sticky-header-table
|
||||
thead tr:first-child th
|
||||
background-color: var(--q-primary)
|
||||
background-color: var(--q-accent)
|
||||
margin-top: none
|
||||
|
||||
thead tr th
|
||||
|
|
@ -313,4 +367,7 @@
|
|||
|
||||
.q-table__grid-content
|
||||
overflow: auto
|
||||
|
||||
td
|
||||
min-width: 80px
|
||||
</style>
|
||||
|
|
@ -19,6 +19,7 @@ export class TimesheetApprovalOverview {
|
|||
holiday_hours: number;
|
||||
vacation_hours: number;
|
||||
};
|
||||
weekly_hours: number[];
|
||||
total_hours: number;
|
||||
expenses: number;
|
||||
mileage: number;
|
||||
|
|
@ -39,6 +40,7 @@ export class TimesheetApprovalOverview {
|
|||
holiday_hours: 0,
|
||||
vacation_hours: 0,
|
||||
}
|
||||
this.weekly_hours = [0];
|
||||
this.total_hours = 0;
|
||||
this.expenses = 0;
|
||||
this.mileage = 0;
|
||||
|
|
@ -63,7 +65,7 @@ export interface PayPeriodOverviewFilters {
|
|||
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 = {
|
||||
FIRST_NAME: 'employee_first_name',
|
||||
|
|
@ -78,6 +80,9 @@ export const overview_column_names = {
|
|||
OVERTIME: 'OVERTIME',
|
||||
EXPENSES: 'expenses',
|
||||
MILEAGE: 'mileage',
|
||||
WEEKLY_HOURS_1: 'weekly_hours_1',
|
||||
WEEKLY_HOURS_2: 'weekly_hours_2',
|
||||
TOTAL_HOURS: 'total_hours',
|
||||
IS_APPROVED: 'is_approved',
|
||||
IS_ACTIVE: 'is_active',
|
||||
}
|
||||
|
|
@ -89,13 +94,14 @@ export const pay_period_overview_columns: QTableColumn[] = [
|
|||
align: 'left',
|
||||
field: overview_column_names.FIRST_NAME,
|
||||
sortable: true,
|
||||
required: true,
|
||||
required: true,
|
||||
style: 'width: 10vw;'
|
||||
},
|
||||
{
|
||||
name: overview_column_names.LAST_NAME,
|
||||
label: 'timesheet_approvals.table.full_name',
|
||||
align: 'left',
|
||||
field: 'employee_last_name',
|
||||
field: '',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
|
|
@ -104,27 +110,28 @@ export const pay_period_overview_columns: QTableColumn[] = [
|
|||
align: 'left',
|
||||
field: overview_column_names.EMAIL,
|
||||
sortable: true,
|
||||
style: 'width: 10vw;'
|
||||
},
|
||||
{
|
||||
name: overview_column_names.REGULAR,
|
||||
label: 'shared.shift_type.regular',
|
||||
align: 'left',
|
||||
field: 'regular_hours',
|
||||
sortable: true,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: overview_column_names.EVENING,
|
||||
label: 'shared.shift_type.evening',
|
||||
align: 'left',
|
||||
field: row => row.other_hours.evening_hours,
|
||||
sortable: true,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: overview_column_names.EMERGENCY,
|
||||
label: 'shared.shift_type.emergency',
|
||||
align: 'left',
|
||||
field: row => row.other_hours.emergency_hours,
|
||||
sortable: true,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: overview_column_names.SICK,
|
||||
|
|
@ -168,6 +175,27 @@ export const pay_period_overview_columns: QTableColumn[] = [
|
|||
field: 'mileage',
|
||||
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,
|
||||
label: 'timesheet_approvals.table.is_approved',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
>
|
||||
import { date } from 'quasar';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useUiStore } from 'src/stores/ui-store';
|
||||
import { useExpensesStore } from 'src/stores/expense-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') },
|
||||
]
|
||||
|
||||
const expense_selected = ref(expense_options.find(expense => expense.value == expenses_store.current_expense.type));
|
||||
const expense_selected = ref<ExpenseOption | undefined>();
|
||||
|
||||
const openDatePicker = () => {
|
||||
is_navigator_open.value = true;
|
||||
|
|
@ -61,6 +61,10 @@
|
|||
expenses_store.mode = 'create';
|
||||
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>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -73,10 +73,19 @@
|
|||
<div class="col-auto">
|
||||
<q-icon
|
||||
: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"
|
||||
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>
|
||||
|
||||
<!-- amount or mileage section -->
|
||||
|
|
@ -118,18 +127,27 @@
|
|||
</div>
|
||||
|
||||
<!-- 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">
|
||||
{{ $t('timesheet.expense.employee_comment') }}
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="col"
|
||||
class="col ellipsis"
|
||||
:class="expense.is_approved ? ' bg-accent text-white' : ''"
|
||||
style="font-size: 1.3em;"
|
||||
style="font-size: 1em;"
|
||||
>
|
||||
{{ expense.comment }}
|
||||
</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>
|
||||
|
||||
<!-- supervisor comment section -->
|
||||
|
|
@ -167,7 +185,8 @@
|
|||
:offset="[0, 20]"
|
||||
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-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) => {
|
||||
if (comment_length < 200) return 'primary';
|
||||
if (comment_length < 250) return 'warning';
|
||||
|
|
@ -66,7 +74,7 @@
|
|||
>
|
||||
<!-- shift type -->
|
||||
<q-select
|
||||
ref="select"
|
||||
ref="select_ref"
|
||||
v-model="shift_type_selected"
|
||||
:standout="$q.dark.isActive ? 'bg-blue-grey-3' : 'bg-blue-grey-9'"
|
||||
dense
|
||||
|
|
@ -83,6 +91,7 @@
|
|||
popup-content-class="text-uppercase text-weight-bold text-center rounded-5"
|
||||
:style="shift.is_approved ? 'background-color: #0a7d32 !important;' : ''"
|
||||
popup-content-style="border: 2px solid var(--q-accent)"
|
||||
@blur="onBlurShiftTypeSelect"
|
||||
@update:model-value="option => shift.type = option.value"
|
||||
>
|
||||
<template #selected-item="scope">
|
||||
|
|
|
|||
|
|
@ -4,13 +4,10 @@
|
|||
>
|
||||
import ShiftList from 'src/modules/timesheets/components/shift-list.vue';
|
||||
|
||||
import { useQuasar } from 'quasar';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api';
|
||||
import type { QScrollArea, TouchSwipeValue } from 'quasar';
|
||||
|
||||
const q = useQuasar();
|
||||
|
||||
const timesheet_store = useTimesheetStore();
|
||||
const timesheet_api = useTimesheetApi();
|
||||
|
|
@ -22,8 +19,6 @@
|
|||
const mobile_animation_direction = ref('fadeInLeft');
|
||||
|
||||
const timesheet_page = ref<QScrollArea | null>(null);
|
||||
const currentDayComponent = ref<HTMLElement[] | null>(null);
|
||||
const currentDayComponentWatcher = ref(currentDayComponent);
|
||||
|
||||
const scroll_y = computed(() => timesheet_page.value?.getScrollPosition().top ?? 0);
|
||||
|
||||
|
|
@ -34,12 +29,10 @@
|
|||
}
|
||||
};
|
||||
|
||||
watch(currentDayComponentWatcher, () => {
|
||||
if (currentDayComponent.value && timesheet_page.value && q.platform.is.mobile) {
|
||||
timesheet_page.value.setScrollPosition('vertical', currentDayComponent.value[0]!.offsetTop, 800);
|
||||
return;
|
||||
}
|
||||
})
|
||||
const onTodayComponentFound = (today_component: HTMLElement | undefined) => {
|
||||
if (timesheet_page.value && today_component)
|
||||
timesheet_page.value.setScrollPosition('vertical', today_component.offsetTop, 800);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -73,7 +66,7 @@
|
|||
</div>
|
||||
|
||||
<!-- Else show timesheets if found -->
|
||||
<ShiftList />
|
||||
<ShiftList @on-current-day-component-found="onTodayComponentFound" />
|
||||
</q-scroll-area>
|
||||
|
||||
<q-page-sticky
|
||||
|
|
|
|||
|
|
@ -5,27 +5,34 @@
|
|||
import ShiftListDay from 'src/modules/timesheets/components/shift-list-day.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 { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
import { Shift } from 'src/modules/timesheets/models/shift.models';
|
||||
import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api';
|
||||
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 { extractDate } = date;
|
||||
const q = useQuasar();
|
||||
|
||||
const ui_store = useUiStore();
|
||||
const timesheet_store = useTimesheetStore();
|
||||
const timesheet_api = useTimesheetApi();
|
||||
|
||||
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 emit = defineEmits<{
|
||||
'onCurrentDayComponentFound': [component: HTMLElement | undefined];
|
||||
}>();
|
||||
|
||||
const addNewShift = (day_shifts: Shift[], date: string, timesheet_id: number) => {
|
||||
ui_store.focus_next_component = true;
|
||||
const new_shift = new Shift;
|
||||
|
|
@ -50,6 +57,12 @@
|
|||
const getMobileDayRef = (iso_date_string: string): string => {
|
||||
return iso_date_string === CURRENT_DATE_STRING ? 'currentDayComponent' : '';
|
||||
};
|
||||
|
||||
watch(currentDayComponentWatcher, () => {
|
||||
if (currentDayComponent.value && q.platform.is.mobile) {
|
||||
emit('onCurrentDayComponentFound', currentDayComponent.value[0])
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -3,11 +3,27 @@
|
|||
lang="ts"
|
||||
>
|
||||
import { useShiftStore } from 'src/stores/shift-store';
|
||||
import { computed } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
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>
|
||||
|
||||
<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 rounded-20 q-pa-xs bg-accent" :class="$q.platform.is.mobile ? 'col-5' : 'col-4'">
|
||||
<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="q-pr-md text-white text-h2 text-weight-bolder">404</div>
|
||||
<div class="q-pr-md text-white text-h5 text-weight-bold">{{
|
||||
|
|
@ -22,7 +22,7 @@ import { useRouter } from 'vue-router';
|
|||
</q-img>
|
||||
</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>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -17,12 +17,8 @@
|
|||
const timesheet_store = useTimesheetStore();
|
||||
|
||||
const page_height = ref(0);
|
||||
const headerComponent = ref<HTMLElement | null>(null);
|
||||
|
||||
const table_max_height = computed(() => {
|
||||
const height = page_height.value - Math.min(headerComponent.value?.clientHeight ?? 0, headerComponent.value?.offsetHeight ?? 0);
|
||||
return height;
|
||||
});
|
||||
const header_height = ref(0);
|
||||
const table_max_height = computed(() => page_height.value - header_height.value);
|
||||
|
||||
const tableStyleFunction = (offset: number, height: number) => {
|
||||
page_height.value = height - offset;
|
||||
|
|
@ -60,6 +56,7 @@
|
|||
title="timesheet_approvals.page_title"
|
||||
:start-date="timesheet_store.pay_period?.period_start ?? ''"
|
||||
:end-date="timesheet_store.pay_period?.period_end ?? ''"
|
||||
@on-get-component-height="value => header_height = value"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ export const useTimesheetStore = defineStore('timesheet', () => {
|
|||
const selected_employee_name = ref<string>();
|
||||
const has_timesheet_preset = ref(false);
|
||||
const current_pay_period_overview = ref<TimesheetApprovalOverview>();
|
||||
const is_approval_grid_mode = ref<boolean>(true);
|
||||
const pay_period_report = ref();
|
||||
|
||||
const getNextOrPreviousPayPeriod = (direction: number) => {
|
||||
|
|
@ -169,7 +168,6 @@ export const useTimesheetStore = defineStore('timesheet', () => {
|
|||
return {
|
||||
is_loading,
|
||||
is_report_dialog_open,
|
||||
is_approval_grid_mode,
|
||||
is_details_dialog_open,
|
||||
pay_period,
|
||||
pay_period_overviews,
|
||||
|
|
|
|||
|
|
@ -23,5 +23,5 @@ export const getMinutes = (hours: number) => {
|
|||
export const getHoursMinutesStringFromHoursFloat = (hours: number): string => {
|
||||
const flat_hours = Math.floor(hours);
|
||||
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