fix(all): More changes to UI:
Timesheet: fix UI spaces with scrolling, change ui to not show preset apply if no preset set to employee. Layout Drawer: fix display of options according to user permissions, fix highlight of menu item to match current route name. Employee list: add functionality to prevent users without user management permissions to see or edit user info and prevent seeing inactive users, add remote to shifts for preset editor, add hover effect to employee items when management mode to visually hint at clickable item.
This commit is contained in:
parent
20fcc0206c
commit
f738a5872a
|
|
@ -13,7 +13,7 @@
|
|||
const DRAWER_BUTTONS: { i18n_key: string, icon: string, route: RouteNames, required_module?: UserModuleAccess }[] = [
|
||||
{ i18n_key: 'nav_bar.home', icon: "home", route: RouteNames.DASHBOARD, required_module: ModuleNames.DASHBOARD },
|
||||
{ i18n_key: 'nav_bar.timesheet_approvals', icon: "event_available", route: RouteNames.TIMESHEET_APPROVALS, required_module: ModuleNames.TIMESHEETS_APPROVAL },
|
||||
{ i18n_key: 'nav_bar.employee_list', icon: "groups", route: RouteNames.EMPLOYEE_LIST },
|
||||
{ i18n_key: 'nav_bar.employee_list', icon: "groups", route: RouteNames.EMPLOYEE_LIST, required_module: ModuleNames.EMPLOYEE_LIST },
|
||||
{ i18n_key: 'nav_bar.timesheet', icon: "punch_clock", route: RouteNames.TIMESHEET, required_module: ModuleNames.TIMESHEETS },
|
||||
{ i18n_key: 'nav_bar.profile', icon: "account_box", route: RouteNames.PROFILE, required_module: ModuleNames.PERSONAL_PROFILE },
|
||||
{ i18n_key: 'nav_bar.help', icon: "contact_support", route: RouteNames.HELP },
|
||||
|
|
@ -26,7 +26,6 @@
|
|||
const is_mini = ref(true);
|
||||
|
||||
const onClickDrawerPage = (page_name: RouteNames) => {
|
||||
ui_store.current_page = page_name;
|
||||
is_mini.value = true;
|
||||
|
||||
router.push({ name: page_name }).catch(error => {
|
||||
|
|
@ -43,7 +42,7 @@
|
|||
};
|
||||
|
||||
onMounted(() => {
|
||||
if(q.platform.is.mobile) {
|
||||
if (q.platform.is.mobile) {
|
||||
ui_store.is_left_drawer_open = false;
|
||||
}
|
||||
})
|
||||
|
|
@ -66,22 +65,26 @@
|
|||
v-for="button, index in DRAWER_BUTTONS"
|
||||
:key="index"
|
||||
v-show="button.required_module ?? true"
|
||||
class="row items-center full-width q-py-sm cursor-pointer"
|
||||
:class="ui_store.current_page === button.route ? ($q.dark.isActive ? 'bg-green-10' : 'bg-green-2') : ''"
|
||||
@click="onClickDrawerPage(button.route)"
|
||||
>
|
||||
<q-icon
|
||||
:name="button.icon"
|
||||
color="accent"
|
||||
size="lg"
|
||||
class="col-auto q-pl-sm"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="col text-uppercase text-weight-bold text-h6 q-pl-sm"
|
||||
:class="$q.platform.is.mobile ? '' : 'q-mini-drawer-hide'"
|
||||
v-if="button.required_module ? auth_store.user?.user_module_access.includes(button.required_module) : true"
|
||||
class="row items-center full-width q-py-sm cursor-pointer"
|
||||
:class="$router.currentRoute.value.name === button.route ? ($q.dark.isActive ? 'bg-green-10' : 'bg-green-2') : ''"
|
||||
>
|
||||
{{ $t(button.i18n_key) }}
|
||||
<q-icon
|
||||
:name="button.icon"
|
||||
color="accent"
|
||||
size="lg"
|
||||
class="col-auto q-pl-sm"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="col text-uppercase text-weight-bold text-h6 q-pl-sm"
|
||||
:class="$q.platform.is.mobile ? '' : 'q-mini-drawer-hide'"
|
||||
>
|
||||
{{ $t(button.i18n_key) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +1,31 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useAuthApi } from 'src/modules/auth/composables/use-auth-api';
|
||||
import LoginRockPaperScissor from 'src/modules/auth/components/login-rock-paper-scissor.vue';
|
||||
<script
|
||||
setup
|
||||
lang="ts"
|
||||
>
|
||||
import { computed } from 'vue';
|
||||
import { useAuthApi } from 'src/modules/auth/composables/use-auth-api';
|
||||
import LoginRockPaperScissor from 'src/modules/auth/components/login-rock-paper-scissor.vue';
|
||||
|
||||
const auth_api = useAuthApi();
|
||||
const auth_api = useAuthApi();
|
||||
|
||||
const email = defineModel<string>('email', { default: '', });
|
||||
// const is_remembered = ref<boolean>(false);
|
||||
const is_employee_email = computed(() => email.value.includes('@targ'));
|
||||
const is_game_time = computed(() => email.value.includes('allumette'));
|
||||
const email = defineModel<string>('email', { default: '', });
|
||||
// const is_remembered = ref<boolean>(false);
|
||||
const is_employee_email = computed(() => email.value.includes('@targ'));
|
||||
const is_game_time = computed(() => email.value.includes('allumette'));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-card class="rounded-15 shadow-10 full-width">
|
||||
<q-card-section class="text-center bg-primary q-pa-lg">
|
||||
<q-card
|
||||
bordered
|
||||
class="rounded-15 shadow-10 full-width"
|
||||
>
|
||||
<div class="text-center bg-primary q-pa-lg">
|
||||
<q-img
|
||||
src="/src/assets/logo-targo-white.svg"
|
||||
ratio="4.6"
|
||||
fit="contain"
|
||||
/>
|
||||
</q-card-section>
|
||||
</div>
|
||||
|
||||
<div class="q-pt-sm q-px-xl q-pb-lg ">
|
||||
<q-card-section class="text-center text-uppercase">
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { ref } from 'vue';
|
|||
arrows
|
||||
:autoplay="9001"
|
||||
control-color="accent"
|
||||
control-type="outline"
|
||||
class="bg-dark full-width rounded-15 shadow-18"
|
||||
>
|
||||
<!-- welcome slide -->
|
||||
|
|
|
|||
|
|
@ -2,20 +2,30 @@
|
|||
setup
|
||||
lang="ts"
|
||||
>
|
||||
import { useQuasar } from 'quasar';
|
||||
import type { EmployeeProfile } from 'src/modules/employee-list/models/employee-profile.models';
|
||||
import { ref } from 'vue';
|
||||
|
||||
// const getEmployeeAvatar = (first_name: string, last_name: string) => {
|
||||
// // add logic here to see if user has an avatar image and return that instead of initials
|
||||
// return first_name.charAt(0) + last_name.charAt(0);
|
||||
// };
|
||||
const q = useQuasar();
|
||||
const is_mouseover = ref(false);
|
||||
|
||||
const { row, index = -1 } = defineProps<{
|
||||
const { row, index = -1, isManagement = false } = defineProps<{
|
||||
row: EmployeeProfile
|
||||
index?: number
|
||||
isManagement?: boolean;
|
||||
}>()
|
||||
const emit = defineEmits<{
|
||||
|
||||
defineEmits<{
|
||||
onProfileClick: [email: string]
|
||||
}>();
|
||||
|
||||
const getItemStyle = (): string => {
|
||||
const active_style = row.last_work_day === null ? '' : 'opacity: 0.6;';
|
||||
const dark_style = q.dark.isActive ? 'border: 2px solid var(--q-accent);' : '';
|
||||
const hover_style = isManagement ? (is_mouseover.value ? `transform: scale(1.1); z-index: 2;` :'transform: scale(1) skew(0)') : '';
|
||||
|
||||
return `${active_style} ${dark_style} ${hover_style}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -24,10 +34,13 @@
|
|||
:style="`animation-delay: ${index / 25}s;`"
|
||||
>
|
||||
<div
|
||||
class="column col no-wrap cursor-pointer bg-dark rounded-15 shadow-12"
|
||||
class="column col no-wrap bg-dark rounded-15 shadow-12"
|
||||
:class="isManagement ? 'cursor-pointer item-mouse-hover' : ''"
|
||||
style="max-width: 230px; height: 275px;"
|
||||
:style="(row.last_work_day === null ? ' ' : 'opacity: 0.6; ') + ($q.dark.isActive ? ' border: 2px solid var(--q-accent)' : '')"
|
||||
@click="emit('onProfileClick', row.email)"
|
||||
:style="getItemStyle()"
|
||||
@click="$emit('onProfileClick', row.email)"
|
||||
@mouseenter="is_mouseover = true"
|
||||
@mouseleave="is_mouseover = false"
|
||||
>
|
||||
<div class="col-auto column flex-center q-pt-md">
|
||||
<q-avatar
|
||||
|
|
@ -66,4 +79,10 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.item-mouse-hover {
|
||||
transition: all 0.2s ease-out;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -7,13 +7,18 @@
|
|||
import { onMounted, ref } from 'vue';
|
||||
import { date, type QTableColumn } from 'quasar';
|
||||
import { useUiStore } from 'src/stores/ui-store';
|
||||
import { useAuthStore } from 'src/stores/auth-store';
|
||||
import { useEmployeeStore } from 'src/stores/employee-store';
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
import { employee_list_columns, type EmployeeProfile, type EmployeeListFilters } from 'src/modules/employee-list/models/employee-profile.models';
|
||||
|
||||
const ui_store = useUiStore();
|
||||
const auth_store = useAuthStore();
|
||||
const employee_store = useEmployeeStore();
|
||||
const timesheet_store = useTimesheetStore();
|
||||
const ui_store = useUiStore();
|
||||
|
||||
const is_management = auth_store.user?.user_module_access.includes('employee_management') ?? false;
|
||||
|
||||
const visible_columns = ref<(keyof EmployeeProfile)[]>(['first_name', 'email', 'job_title', 'last_work_day']);
|
||||
|
||||
const table_grid_container = ref<HTMLElement | null>(null);
|
||||
|
|
@ -88,6 +93,7 @@
|
|||
<template #top>
|
||||
<div class="row flex-center full-width q-mb-sm">
|
||||
<q-btn
|
||||
v-if="is_management"
|
||||
rounded
|
||||
color="accent"
|
||||
icon="las la-user-edit"
|
||||
|
|
@ -97,15 +103,15 @@
|
|||
/>
|
||||
|
||||
<q-checkbox
|
||||
v-if="is_management"
|
||||
v-model="filters.hide_inactive_users"
|
||||
color="accent"
|
||||
:label="$t('employee_management.filter.hide_terminated')"
|
||||
class="text-uppercase q-ml-md text-weight-medium q-px-sm"
|
||||
:class="filters.hide_inactive_users ? 'rounded-25 bg-accent' : ''"
|
||||
>
|
||||
<q-icon
|
||||
name="las la-user-times"
|
||||
:color="filters.hide_inactive_users ? 'white' : 'negative'"
|
||||
color="negative"
|
||||
size="sm"
|
||||
class="q-px-sm"
|
||||
/>
|
||||
|
|
@ -177,7 +183,8 @@
|
|||
:key="props.rowIndex"
|
||||
:row="props.row"
|
||||
:index="props.rowIndex"
|
||||
@on-profile-click="employee_store.openAddModifyDialog"
|
||||
:is-management="is_management"
|
||||
@on-profile-click="is_management ? employee_store.openAddModifyDialog : ''"
|
||||
/>
|
||||
</transition>
|
||||
</template>
|
||||
|
|
@ -185,7 +192,7 @@
|
|||
<template #body-cell="scope">
|
||||
<q-td
|
||||
:props="scope"
|
||||
@click="employee_store.openAddModifyDialog(scope.row.email)"
|
||||
@click="is_management ? employee_store.openAddModifyDialog(scope.row.email) : ''"
|
||||
>
|
||||
<transition
|
||||
appear
|
||||
|
|
|
|||
|
|
@ -66,6 +66,28 @@
|
|||
>{{ scope.opt.label }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #after>
|
||||
<q-toggle
|
||||
v-model="shift.is_remote"
|
||||
dense
|
||||
keep-color
|
||||
size="3em"
|
||||
color="accent"
|
||||
icon="las la-building"
|
||||
checked-icon="las la-laptop"
|
||||
>
|
||||
<q-tooltip
|
||||
anchor="top middle"
|
||||
self="bottom middle"
|
||||
:offset="[0, 10]"
|
||||
class="text-uppercase text-weight-medium text-white bg-accent"
|
||||
>
|
||||
{{ shift.is_remote ? $t('timesheet.shift.types.REMOTE') :
|
||||
$t('timesheet.shift.types.OFFICE') }}
|
||||
</q-tooltip>
|
||||
</q-toggle>
|
||||
</template>
|
||||
</q-select>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -40,14 +40,13 @@
|
|||
}>();
|
||||
|
||||
const is_showing_filters = ref(false);
|
||||
const search_string = ref('');
|
||||
|
||||
const overview_rows = computed(() => timesheet_store.pay_period_overviews.filter(overview => overview));
|
||||
const overview_filters = ref<PayPeriodOverviewFilters>({
|
||||
is_showing_inactive: false,
|
||||
is_showing_team_only: false,
|
||||
supervisors: [],
|
||||
name_search_string: search_string.value,
|
||||
name_search_string: '',
|
||||
});
|
||||
|
||||
const onClickedDetails = async (row: TimesheetApprovalOverview) => {
|
||||
|
|
@ -106,7 +105,7 @@
|
|||
: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="`max-height: ${maxHeight}px;`"
|
||||
: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>
|
||||
|
|
@ -158,7 +157,7 @@
|
|||
/>
|
||||
|
||||
<QTableFilters
|
||||
v-model:search="search_string"
|
||||
v-model:search="overview_filters.name_search_string"
|
||||
class="col-auto q-mb-sm"
|
||||
/>
|
||||
|
||||
|
|
@ -279,7 +278,7 @@
|
|||
|
||||
<!-- Template for custome failed-to-load state -->
|
||||
<template #no-data="{ message, filter }">
|
||||
<div class="full-width column items-center text-accent q-gutter-sm">
|
||||
<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'"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="$q.platform.is.mobile && $q.screen.width < $q.screen.height"
|
||||
class="col-auto row items-start q-px-sm q-pt-sm full-width"
|
||||
class="row items-start q-px-sm q-pt-sm full-width"
|
||||
>
|
||||
<!-- per timesheet -->
|
||||
<div
|
||||
|
|
@ -6,14 +6,16 @@
|
|||
import ShiftListDayRowMobile from 'src/modules/timesheets/components/mobile/shift-list-day-row-mobile.vue';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
import { useShiftApi } from 'src/modules/timesheets/composables/use-shift-api';
|
||||
import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api';
|
||||
import type { Shift } from 'src/modules/timesheets/models/shift.models';
|
||||
import type { TimesheetDay } from 'src/modules/timesheets/models/timesheet.models';
|
||||
import { isShiftOverlap } from 'src/modules/timesheets/utils/shift.util';
|
||||
import { isShiftOverlap } from 'src/modules/timesheets/utils/shift.util';
|
||||
|
||||
const shift_api = useShiftApi();
|
||||
const timesheet_api = useTimesheetApi();
|
||||
const timesheet_store = useTimesheetStore();
|
||||
const shift_error_message = ref<string | undefined>();
|
||||
|
||||
const { day, dense = false, approved = false } = defineProps<{
|
||||
|
|
@ -63,7 +65,7 @@ import { isShiftOverlap } from 'src/modules/timesheets/utils/shift.util';
|
|||
leave-active-class="animated zoomOut fast"
|
||||
>
|
||||
<q-btn
|
||||
v-if="!$q.platform.is.mobile && day.shifts.length < 1 && preset_mouseover"
|
||||
v-if="!$q.platform.is.mobile && day.shifts.length < 1 && preset_mouseover && timesheet_store.has_timesheet_preset"
|
||||
:disable="day.shifts.length > 0"
|
||||
flat
|
||||
dense
|
||||
|
|
@ -86,7 +88,7 @@ import { isShiftOverlap } from 'src/modules/timesheets/utils/shift.util';
|
|||
:key="shift_index"
|
||||
class="col-auto"
|
||||
>
|
||||
<ShiftListDayRowMobile
|
||||
<ShiftListDayRowMobile
|
||||
v-if="$q.platform.is.mobile"
|
||||
v-model:shift="day.shifts[shift_index]!"
|
||||
:is-timesheet-approved="approved"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
import ShiftListDay from 'src/modules/timesheets/components/shift-list-day.vue';
|
||||
import ShiftListDateWidget from 'src/modules/timesheets/components/shift-list-date-widget.vue';
|
||||
|
||||
import { date } from 'quasar';
|
||||
import { date, useQuasar } from 'quasar';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useUiStore } from 'src/stores/ui-store';
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
const CURRENT_DATE_STRING = new Date().toISOString().slice(0, 10);
|
||||
|
||||
const { extractDate } = date;
|
||||
const q = useQuasar();
|
||||
|
||||
const ui_store = useUiStore();
|
||||
const timesheet_store = useTimesheetStore();
|
||||
|
|
@ -68,7 +69,7 @@
|
|||
};
|
||||
|
||||
watch(currentDayComponentWatcher, () => {
|
||||
if (currentDayComponent.value && timesheet_page.value) {
|
||||
if (currentDayComponent.value && timesheet_page.value && q.platform.is.mobile) {
|
||||
console.log('setting scroll position to offsetTop of currentDayComponent: ', currentDayComponent.value[0]!.offsetTop);
|
||||
timesheet_page.value.setScrollPosition('vertical', currentDayComponent.value[0]!.offsetTop, 800);
|
||||
return;
|
||||
|
|
@ -78,7 +79,7 @@
|
|||
|
||||
<template>
|
||||
<div
|
||||
class="col column fit relative-position"
|
||||
class="column fit relative-position"
|
||||
:style="$q.platform.is.mobile && $q.screen.width < $q.screen.height ? 'margin-bottom: 40px' : ''"
|
||||
v-touch-swipe="value => handleSwipe(value.direction, value.distance ?? { x: 0, y: 0 })"
|
||||
>
|
||||
|
|
@ -91,7 +92,7 @@
|
|||
>
|
||||
<!-- Show if no timesheets found (further than one month from present) -->
|
||||
<div
|
||||
v-if="timesheet_store.timesheets.length < 1"
|
||||
v-if="timesheet_store.timesheets.length < 1 && !timesheet_store.is_loading"
|
||||
class="col-auto column flex-center fit q-py-lg"
|
||||
style="min-height: 20vh;"
|
||||
>
|
||||
|
|
@ -123,7 +124,7 @@
|
|||
leave-active-class="animated fadeOutUp"
|
||||
>
|
||||
<q-btn
|
||||
v-if="!$q.platform.is.mobile && timesheet.days.every(day => day.shifts.length < 1)"
|
||||
v-if="!$q.platform.is.mobile && timesheet.days.every(day => day.shifts.length < 1) && timesheet_store.has_timesheet_preset"
|
||||
:disable="!timesheet.days.every(day => day.shifts.length < 1)"
|
||||
flat
|
||||
dense
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
import PayPeriodNavigator from 'src/modules/shared/components/pay-period-navigator.vue';
|
||||
import TimesheetErrorWidget from 'src/modules/timesheets/components/timesheet-error-widget.vue';
|
||||
import LoadingOverlay from 'src/modules/shared/components/loading-overlay.vue';
|
||||
import ShiftListWeeklyOverview from 'src/modules/timesheets/components/mobile/shift-list-weekly-overview.vue';
|
||||
import ShiftListWeeklyOverviewMobile from 'src/modules/timesheets/components/mobile/shift-list-weekly-overview-mobile.vue';
|
||||
|
||||
import { computed, onMounted } from 'vue';
|
||||
import { useShiftApi } from 'src/modules/timesheets/composables/use-shift-api';
|
||||
|
|
@ -67,7 +67,10 @@
|
|||
/>
|
||||
|
||||
<!-- label for approval mode to delimit that this is the timesheet -->
|
||||
<span v-if="mode === 'approval'" class="col-auto text-uppercase text-bold text-h5"> {{ $t('timesheet.page_header') }}</span>
|
||||
<span
|
||||
v-if="mode === 'approval'"
|
||||
class="col-auto text-uppercase text-bold text-h5"
|
||||
> {{ $t('timesheet.page_header') }}</span>
|
||||
|
||||
<q-space v-if="$q.screen.width > $q.screen.height" />
|
||||
|
||||
|
|
@ -100,9 +103,12 @@
|
|||
<TimesheetErrorWidget class="col-auto" />
|
||||
|
||||
<!-- mobile weekly overview widget -->
|
||||
<ShiftListWeeklyOverview />
|
||||
<ShiftListWeeklyOverviewMobile class="col-auto" />
|
||||
|
||||
<ShiftList :mode="mode" />
|
||||
<ShiftList
|
||||
:mode="mode"
|
||||
class="col"
|
||||
/>
|
||||
|
||||
<q-btn
|
||||
v-if="$q.platform.is.mobile && $q.screen.width < $q.screen.height"
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ export const useShiftApi = () => {
|
|||
const saveShiftChanges = async () => {
|
||||
timesheet_store.is_loading = true;
|
||||
|
||||
const create_success = await shift_store.createNewShifts();
|
||||
const update_success = await shift_store.updateShifts();
|
||||
const create_success = await shift_store.createNewShifts();
|
||||
|
||||
if (create_success || update_success){
|
||||
await timesheet_store.getTimesheetsByOptionalEmployeeEmail(auth_store.user?.email ?? '');
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export const useTimesheetApi = () => {
|
|||
const timesheet_store = useTimesheetStore();
|
||||
|
||||
const getTimesheetsByDate = async (date_string: string, employee_email?: string) => {
|
||||
timesheet_store.timesheets = [];
|
||||
timesheet_store.is_loading = true;
|
||||
const success = await timesheet_store.getPayPeriodByDateOrYearAndNumber(date_string);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export const TIME_FORMAT_PATTERN = /^(\d{2}:\d{2})?$/;
|
|||
export const DATE_FORMAT_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
||||
|
||||
export interface TimesheetResponse {
|
||||
has_preset_schedule: boolean;
|
||||
employee_fullname: string;
|
||||
timesheets: Timesheet[];
|
||||
}
|
||||
|
|
@ -41,79 +42,4 @@ export interface TotalExpenses {
|
|||
per_diem: number;
|
||||
on_call: number;
|
||||
mileage: number;
|
||||
}
|
||||
|
||||
// export const test_timesheets: Timesheet[] = [
|
||||
// {
|
||||
// timehsid: 1,
|
||||
// is_approved: false,
|
||||
// weekly_hours: { regular: 8, evening: 0, emergency: 0, overtime: 0, vacation: 0, holiday: 0, sick: 0, absent: 0 },
|
||||
// weekly_expenses: { expenses: 15.5, mileage: 0 },
|
||||
// days: [
|
||||
// {
|
||||
// date: '2025-10-18',
|
||||
// daily_hours: { regular: 8, evening: 0, emergency: 0, overtime: 0, vacation: 0, holiday: 0, sick: 0, absent: 0 },
|
||||
// daily_expenses: { expenses: 15.5, mileage: 0 },
|
||||
// shifts: [
|
||||
// { id: 101, date: '2025-01-06', type: 'REGULAR', start_time: '08:00', end_time: '12:00', comment: 'blah', is_approved: false, is_remote: false, },
|
||||
// { id: 102, date: '2025-01-06', type: 'REGULAR', start_time: '13:00', end_time: '17:00', comment: undefined, is_approved: false, is_remote: false, },
|
||||
// ],
|
||||
// expenses: [
|
||||
// { id: 201, date: '2025-01-06', type: 'EXPENSES', amount: 15.5, comment: 'Lunch receipt', is_approved: false, },
|
||||
// ],
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// id: 2,
|
||||
// is_approved: true,
|
||||
// weekly_hours: {
|
||||
// regular: 0,
|
||||
// evening: 0,
|
||||
// emergency: 0,
|
||||
// overtime: 8,
|
||||
// vacation: 0,
|
||||
// holiday: 0,
|
||||
// sick: 0,
|
||||
// absent: 0,
|
||||
// },
|
||||
// weekly_expenses: {
|
||||
// expenses: 0,
|
||||
// mileage: 32.4,
|
||||
// },
|
||||
// days: [
|
||||
// {
|
||||
// date: '2025-10-27',
|
||||
// daily_hours: {
|
||||
// regular: 0,
|
||||
// evening: 0,
|
||||
// emergency: 0,
|
||||
// overtime: 8,
|
||||
// vacation: 0,
|
||||
// holiday: 0,
|
||||
// sick: 0,
|
||||
// absent: 0,
|
||||
// },
|
||||
// daily_expenses: {
|
||||
// expenses: 0,
|
||||
// mileage: 32.4,
|
||||
// },
|
||||
// shifts: [
|
||||
// { id: 101, date: '2025-10-27', type: 'REGULAR', start_time: '08:00', end_time: '12:00', comment: undefined, is_approved: false, is_remote: false, },
|
||||
// { id: 102, date: '2025-10-27', type: 'REGULAR', start_time: '13:00', end_time: '17:00', comment: undefined, is_approved: false, is_remote: false, },
|
||||
// ],
|
||||
// expenses: [
|
||||
// {
|
||||
// id: 202,
|
||||
// date: '2025-10-27',
|
||||
// type: 'MILEAGE',
|
||||
// amount: 0,
|
||||
// mileage: 32.4,
|
||||
// comment: 'Travel to client site',
|
||||
// is_approved: true,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// ];
|
||||
}
|
||||
|
|
@ -62,7 +62,7 @@
|
|||
<iframe
|
||||
title="Environment Canada Weather"
|
||||
height="400px"
|
||||
src="https://weather.gc.ca/wxlink/wxlink.html?coords=45.159%2C-73.676&lang=e"
|
||||
src="https://weather.gc.ca/wxlink/wxlink.html?coords=45.159%2C-73.676&lang=f"
|
||||
allowtransparency="true"
|
||||
style="border: 0;"
|
||||
class="col-auto"
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<q-page class="column flex-center bg-secondary">
|
||||
<q-page class="column items-center bg-secondary">
|
||||
<AddModifyDialog />
|
||||
|
||||
<PageHeaderTemplate title="employee_list.page_header" />
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
<template>
|
||||
<q-layout view="hHh lpR fFf">
|
||||
<q-page-container class="bg-dark">
|
||||
<q-page-container class="bg-blue-grey-10">
|
||||
<q-page class="row">
|
||||
<q-img src="src/assets/village.png" fit="cover" :class="$q.screen.lt.md ? 'absolute-bottom' : 'absolute-right'" />
|
||||
<transition appear slow enter-active-class="animated zoomIn" leave-active-class="animated zoomOut" class="col-xs-10 absolute-center">
|
||||
|
|
|
|||
|
|
@ -20,14 +20,14 @@
|
|||
const headerComponent = ref<HTMLElement | null>(null);
|
||||
|
||||
const table_max_height = computed(() => {
|
||||
const height = page_height.value - (headerComponent.value?.clientHeight ?? 0) - 20;
|
||||
console.log('offset height of header: ', headerComponent.value?.clientHeight);
|
||||
console.log('height calculated: ', height);
|
||||
const height = page_height.value - (headerComponent.value?.offsetHeight ?? 0);
|
||||
return height;
|
||||
});
|
||||
|
||||
const tableStyleFunction = (offset: number, height: number) => {
|
||||
page_height.value = height - offset;
|
||||
|
||||
return { minHeight: height - offset + 'px' };
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
|
|
@ -49,7 +49,7 @@
|
|||
<OverviewReport />
|
||||
|
||||
<div
|
||||
class="column items-center scroll q-pa-sm"
|
||||
class="column items-center scroll q-px-sm full-width"
|
||||
style="min-height: inherit;"
|
||||
>
|
||||
<div
|
||||
|
|
@ -63,7 +63,7 @@
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="col-grow full-width">
|
||||
<OverviewList :max-height="table_max_height" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
|
||||
<template>
|
||||
<q-page
|
||||
padding
|
||||
class="column bg-secondary items-center"
|
||||
>
|
||||
<PageHeaderTemplate
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export const useTimesheetStore = defineStore('timesheet', () => {
|
|||
|
||||
const is_details_dialog_open = ref(false);
|
||||
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();
|
||||
|
|
@ -98,6 +99,7 @@ export const useTimesheetStore = defineStore('timesheet', () => {
|
|||
}
|
||||
|
||||
if (response.success && response.data) {
|
||||
has_timesheet_preset.value = response.data.has_preset_schedule;
|
||||
selected_employee_name.value = response.data.employee_fullname;
|
||||
timesheets.value = response.data.timesheets;
|
||||
initial_timesheets.value = unwrapAndClone(timesheets.value);
|
||||
|
|
@ -174,6 +176,7 @@ export const useTimesheetStore = defineStore('timesheet', () => {
|
|||
current_pay_period_overview,
|
||||
pay_period_infos,
|
||||
selected_employee_name,
|
||||
has_timesheet_preset,
|
||||
timesheets,
|
||||
all_current_shifts,
|
||||
initial_timesheets,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user