fix(timesheets): add autofill functionality to sick, vacation, holiday shifts.
This commit is contained in:
parent
6368beb24d
commit
2b1b0dbcbd
|
|
@ -5,20 +5,31 @@
|
|||
import { computed, onMounted, ref } from 'vue';
|
||||
import { QSelect, QInput } from 'quasar';
|
||||
import { useUiStore } from 'src/stores/ui-store';
|
||||
import { SHIFT_OPTIONS } from 'src/modules/timesheets/utils/shift.util';
|
||||
import type { Shift } from 'src/modules/timesheets/models/shift.models';
|
||||
import { getCurrentDailyMinutesWorked, getTimeStringFromMinutes, SHIFT_OPTIONS } from 'src/modules/timesheets/utils/shift.util';
|
||||
import type { Shift, ShiftOption, ShiftType } from 'src/modules/timesheets/models/shift.models';
|
||||
import { getHoursMinutesStringFromHoursFloat } from 'src/utils/date-and-time-utils';
|
||||
|
||||
// ========== state ========================================
|
||||
|
||||
const SHIFT_TYPES_WITH_PREDEFINED_TIMES: ShiftType[] = ['HOLIDAY', 'SICK', 'VACATION'];
|
||||
const COMMENT_LENGTH_MAX = 280;
|
||||
|
||||
const shift = defineModel<Shift>('shift', { required: true });
|
||||
|
||||
const { errorMessage = undefined, dense = false, hasShiftAfter = false, isTimesheetApproved = false } = defineProps<{
|
||||
const {
|
||||
dense = false,
|
||||
hasShiftAfter = false,
|
||||
isTimesheetApproved = false,
|
||||
errorMessage = undefined,
|
||||
expectedDailyHours = 8,
|
||||
currentShifts,
|
||||
} = defineProps<{
|
||||
dense?: boolean;
|
||||
hasShiftAfter?: boolean;
|
||||
isTimesheetApproved?: boolean;
|
||||
errorMessage?: string | undefined;
|
||||
expectedDailyHours?: number;
|
||||
currentShifts: Shift[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
|
@ -27,10 +38,13 @@
|
|||
}>();
|
||||
|
||||
const ui_store = useUiStore();
|
||||
const shift_type_selected = ref(SHIFT_OPTIONS.find(option => option.value == shift.value.type));
|
||||
const shiftTypeSelected = ref(SHIFT_OPTIONS.find(option => option.value == shift.value.type));
|
||||
const select_ref = ref<QSelect | null>(null);
|
||||
const is_showing_comment_popup = ref(false);
|
||||
const error_message = ref('');
|
||||
const isShowingPredefinedTime = ref(shift.value.type === 'HOLIDAY');
|
||||
const predefinedHoursString = ref('');
|
||||
const predefinedHoursBgColor = ref(`bg-${shiftTypeSelected.value?.icon_color ?? ''}`);
|
||||
|
||||
// ========== computed ========================================
|
||||
|
||||
|
|
@ -39,7 +53,7 @@
|
|||
// ========== methods =========================================
|
||||
|
||||
const onBlurShiftTypeSelect = () => {
|
||||
if (shift_type_selected.value === undefined) {
|
||||
if (shiftTypeSelected.value === undefined) {
|
||||
shift.value.type = 'REGULAR';
|
||||
shift.value.id = 0;
|
||||
emit('requestDelete');
|
||||
|
|
@ -63,11 +77,35 @@
|
|||
return 'negative';
|
||||
};
|
||||
|
||||
const onShiftTypeChange = (option: ShiftOption) => {
|
||||
shift.value.type = option.value;
|
||||
|
||||
if (SHIFT_TYPES_WITH_PREDEFINED_TIMES.includes(option.value)) {
|
||||
predefinedHoursBgColor.value = `bg-${option.icon_color}`;
|
||||
shift.value.start_time = '00:00';
|
||||
|
||||
if (option.value === 'SICK' || option.value === 'VACATION') {
|
||||
const workedMinutes = getCurrentDailyMinutesWorked(currentShifts);
|
||||
console.log('worked minutes: ', workedMinutes);
|
||||
const expectedWorkedMinutes = expectedDailyHours * 60;
|
||||
const leftOverMinutes = expectedWorkedMinutes - workedMinutes;
|
||||
|
||||
shift.value.end_time = getTimeStringFromMinutes(leftOverMinutes);
|
||||
isShowingPredefinedTime.value = false;
|
||||
} else {
|
||||
isShowingPredefinedTime.value = true;
|
||||
predefinedHoursString.value = getHoursMinutesStringFromHoursFloat(expectedDailyHours);
|
||||
shift.value.end_time = getTimeStringFromMinutes(expectedDailyHours * 60);
|
||||
}
|
||||
} else
|
||||
isShowingPredefinedTime.value = false;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (ui_store.focus_next_component) {
|
||||
select_ref.value?.focus();
|
||||
select_ref.value?.showPopup();
|
||||
shift_type_selected.value = undefined;
|
||||
shiftTypeSelected.value = undefined;
|
||||
ui_store.focus_next_component = false;
|
||||
}
|
||||
|
||||
|
|
@ -131,7 +169,7 @@
|
|||
<!-- shift type -->
|
||||
<q-select
|
||||
ref="select"
|
||||
v-model="shift_type_selected"
|
||||
v-model="shiftTypeSelected"
|
||||
:standout="$q.dark.isActive ? 'bg-blue-grey-3' : 'bg-blue-grey-9'"
|
||||
dense
|
||||
:borderless="(shift.is_approved && isTimesheetApproved)"
|
||||
|
|
@ -148,7 +186,7 @@
|
|||
: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"
|
||||
@update:model-value="onShiftTypeChange"
|
||||
>
|
||||
<template #selected-item="scope">
|
||||
<div
|
||||
|
|
@ -234,7 +272,25 @@
|
|||
</q-select>
|
||||
</div>
|
||||
|
||||
<div class="col row items-start text-uppercase rounded-5 q-pa-xs">
|
||||
<div
|
||||
v-if="isShowingPredefinedTime"
|
||||
class="col row items-start text-uppercase rounded-5 q-pa-xs relative-position"
|
||||
>
|
||||
<div
|
||||
class="absolute-full rounded-5 q-mx-sm q-my-xs"
|
||||
:class="predefinedHoursBgColor"
|
||||
style="opacity: 0.3;"
|
||||
></div>
|
||||
|
||||
<span class="col text-center text-uppercase text-h6 text-bold q-py-xs">
|
||||
{{ getHoursMinutesStringFromHoursFloat(expectedDailyHours) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="col row items-start text-uppercase rounded-5 q-pa-xs"
|
||||
>
|
||||
<!-- punch in field -->
|
||||
<div class="col q-pr-xs">
|
||||
<q-input
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@
|
|||
import { QSelect, QInput, useQuasar, type QSelectProps, QPopupProxy } from 'quasar';
|
||||
import { useUiStore } from 'src/stores/ui-store';
|
||||
import { useAuthStore } from 'src/stores/auth-store';
|
||||
import { SHIFT_OPTIONS } from 'src/modules/timesheets/utils/shift.util';
|
||||
import type { TotalHours } from 'src/modules/timesheets/models/timesheet.models';
|
||||
import { getCurrentDailyMinutesWorked, getShiftOptions, getTimeStringFromMinutes, SHIFT_OPTIONS } from 'src/modules/timesheets/utils/shift.util';
|
||||
import type { Shift, ShiftOption, ShiftType } from 'src/modules/timesheets/models/shift.models';
|
||||
import { getHoursMinutesStringFromHoursFloat } from 'src/utils/date-and-time-utils';
|
||||
import { getHoursMinutesStringFromHoursFloat } from 'src/utils/date-and-time-utils';
|
||||
|
||||
// ========== Constants ========================================
|
||||
|
||||
const SHIFT_TYPES_WITH_PREDEFINED_TIMES: ShiftType[] = ['HOLIDAY', 'SICK', 'VACATION'];
|
||||
const COMMENT_LENGTH_MAX = 280;
|
||||
|
||||
// ========== State ========================================
|
||||
|
||||
|
|
@ -23,11 +23,11 @@ import { getHoursMinutesStringFromHoursFloat } from 'src/utils/date-and-time-uti
|
|||
const {
|
||||
errorMessage = undefined,
|
||||
isTimesheetApproved = false,
|
||||
currentShifts,
|
||||
holiday = false,
|
||||
expectedDailyHours = 8,
|
||||
dailyHours,
|
||||
} = defineProps<{
|
||||
dailyHours: TotalHours;
|
||||
currentShifts: Shift[];
|
||||
expectedDailyHours?: number;
|
||||
isTimesheetApproved?: boolean;
|
||||
errorMessage?: string | undefined;
|
||||
|
|
@ -39,9 +39,6 @@ import { getHoursMinutesStringFromHoursFloat } from 'src/utils/date-and-time-uti
|
|||
'onTimeFieldBlur': [void];
|
||||
}>();
|
||||
|
||||
|
||||
const COMMENT_LENGTH_MAX = 280;
|
||||
|
||||
const q = useQuasar();
|
||||
const { t } = useI18n();
|
||||
const ui_store = useUiStore();
|
||||
|
|
@ -53,13 +50,15 @@ import { getHoursMinutesStringFromHoursFloat } from 'src/utils/date-and-time-uti
|
|||
const selectRef = ref<QSelect | null>(null);
|
||||
const shiftErrorMessage = ref<string | undefined>();
|
||||
const is_showing_delete_confirm = ref(false);
|
||||
const isShowingPredefinedTime = ref(false);
|
||||
const isShowingPredefinedTime = ref(shift.value.type === 'HOLIDAY');
|
||||
const popupProxyRef = ref<QPopupProxy | null>(null);
|
||||
const predefinedHours = ref(0);
|
||||
const predefinedHoursBgColor = ref('');
|
||||
const predefinedHoursString = ref('');
|
||||
const predefinedHoursBgColor = ref(`bg-${shiftTypeSelected.value?.icon_color ?? ''}`);
|
||||
|
||||
// ================== Computed ==================
|
||||
|
||||
const hasPTO = computed(() => currentShifts.some(shift => SHIFT_TYPES_WITH_PREDEFINED_TIMES.includes(shift.type)));
|
||||
|
||||
const rightClickMenuIcon = computed(() => shift.value.is_approved ? 'lock_open' : 'lock');
|
||||
|
||||
const rightClickMenuLabel = computed(() => shift.value.is_approved ?
|
||||
|
|
@ -94,7 +93,7 @@ import { getHoursMinutesStringFromHoursFloat } from 'src/utils/date-and-time-uti
|
|||
menuOffset: [0, 10],
|
||||
menuAnchor: "bottom middle",
|
||||
menuSelf: "top middle",
|
||||
options: SHIFT_OPTIONS,
|
||||
options: getShiftOptions(hasPTO.value, currentShifts.length > 1),
|
||||
class: `col rounded-5 q-mx-xs bg-dark ${!shift.value.is_approved && !isTimesheetApproved ? '' : 'inset-shadow'}`,
|
||||
popupContentClass: "text-uppercase text-weight-bold text-center rounded-5",
|
||||
style: shift.value.is_approved ? (holiday ? 'background-color: #7b1fa2 !important' : 'background-color: #0a7d32 !important;') : '',
|
||||
|
|
@ -149,21 +148,23 @@ import { getHoursMinutesStringFromHoursFloat } from 'src/utils/date-and-time-uti
|
|||
shift.value.type = option.value;
|
||||
|
||||
if (SHIFT_TYPES_WITH_PREDEFINED_TIMES.includes(option.value)) {
|
||||
isShowingPredefinedTime.value = true;
|
||||
predefinedHoursBgColor.value = `bg-${option.icon_color}`;
|
||||
shift.value.start_time = '00:00';
|
||||
|
||||
if (option.value === 'SICK') {
|
||||
const workedHours =
|
||||
dailyHours.regular +
|
||||
dailyHours.emergency +
|
||||
dailyHours.evening +
|
||||
dailyHours.holiday +
|
||||
dailyHours.sick +
|
||||
dailyHours.vacation;
|
||||
predefinedHours.value = Math.max(expectedDailyHours - workedHours, 0);
|
||||
if (option.value === 'SICK' || option.value === 'VACATION') {
|
||||
const workedMinutes = getCurrentDailyMinutesWorked(currentShifts);
|
||||
console.log('worked minutes: ', workedMinutes);
|
||||
const expectedWorkedMinutes = expectedDailyHours * 60;
|
||||
const leftOverMinutes = expectedWorkedMinutes - workedMinutes;
|
||||
|
||||
shift.value.end_time = getTimeStringFromMinutes(leftOverMinutes);
|
||||
} else {
|
||||
isShowingPredefinedTime.value = true;
|
||||
predefinedHoursString.value = getHoursMinutesStringFromHoursFloat(expectedDailyHours);
|
||||
shift.value.start_time = '00:00';
|
||||
shift.value.end_time = `${expectedDailyHours}:00`;
|
||||
}
|
||||
}
|
||||
else
|
||||
} else
|
||||
isShowingPredefinedTime.value = false;
|
||||
}
|
||||
|
||||
|
|
@ -334,21 +335,21 @@ import { getHoursMinutesStringFromHoursFloat } from 'src/utils/date-and-time-uti
|
|||
<!-- If shift type has predefined timestamps -->
|
||||
<div
|
||||
v-if="isShowingPredefinedTime"
|
||||
class="col row q-px-sm relative-position flex-center"
|
||||
class="col row q-pa-xs relative-position flex-center"
|
||||
>
|
||||
<div
|
||||
class="absolute-full rounded-5 q-mx-sm"
|
||||
class="absolute-full rounded-5 q-mx-sm q-my-xs"
|
||||
:class="predefinedHoursBgColor"
|
||||
style="opacity: 0.3;"
|
||||
></div>
|
||||
<span class="col text-center text-uppercase text-h6 text-bold">{{ getHoursMinutesStringFromHoursFloat(predefinedHours) }}</span>
|
||||
|
||||
<span class="col text-center text-uppercase text-h6 text-bold q-py-xs">
|
||||
{{ getHoursMinutesStringFromHoursFloat(expectedDailyHours) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Else show input fields for in-out timestamps -->
|
||||
<div
|
||||
v-else
|
||||
class="col row items-start text-uppercase rounded-5 q-pa-xs"
|
||||
>
|
||||
<div v-else class="col row items-start text-uppercase rounded-5 q-pa-xs">
|
||||
<q-input
|
||||
ref="start_time"
|
||||
v-model="shift.start_time"
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@
|
|||
:is-timesheet-approved="approved"
|
||||
:error-message="shift_error_message"
|
||||
:dense="dense"
|
||||
:current-shifts="day.shifts"
|
||||
:has-shift-after="shift_index < day.shifts.length - 1"
|
||||
@request-delete="deleteCurrentShift(shift)"
|
||||
@on-time-field-blur="onTimeFieldBlur()"
|
||||
|
|
@ -116,7 +117,7 @@
|
|||
v-else
|
||||
v-model:shift="day.shifts[shift_index]!"
|
||||
:holiday="holiday"
|
||||
:daily-hours="day.daily_hours"
|
||||
:current-shifts="day.shifts"
|
||||
:is-timesheet-approved="approved"
|
||||
:error-message="shift_error_message"
|
||||
@request-delete="deleteCurrentShift(shift)"
|
||||
|
|
|
|||
|
|
@ -46,4 +46,5 @@ export interface ShiftOption extends QSelectOption {
|
|||
value: ShiftType;
|
||||
icon: string;
|
||||
icon_color: string;
|
||||
disable?: boolean;
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ export const DATE_FORMAT_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|||
export interface TimesheetResponse {
|
||||
has_preset_schedule: boolean;
|
||||
employee_fullname: string;
|
||||
daily_expected_hours: number;
|
||||
timesheets: Timesheet[];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,38 +1,82 @@
|
|||
import { date } from "quasar";
|
||||
import type { SchedulePresetShift } from "src/modules/employee-list/models/schedule-presets.models";
|
||||
import type { Shift, ShiftOption } from "src/modules/timesheets/models/shift.models";
|
||||
import type { Shift, ShiftOption, ShiftType } from "src/modules/timesheets/models/shift.models";
|
||||
|
||||
export const isShiftOverlap = (shifts: Shift[] | SchedulePresetShift[]): boolean => {
|
||||
if (shifts.length < 2) return false;
|
||||
if (shifts.length < 2) return false;
|
||||
|
||||
const parsed_shifts = shifts.map(shift => ({
|
||||
start: date.extractDate(`2000-01-01 ${shift.start_time}`, 'YYYY-MM-DD HH:mm').getTime(),
|
||||
end: date.extractDate(`2000-01-01 ${shift.end_time}`, 'YYYY-MM-DD HH:mm').getTime(),
|
||||
}));
|
||||
const parsed_shifts = shifts.map(shift => ({
|
||||
start: date.extractDate(`2000-01-01 ${shift.start_time}`, 'YYYY-MM-DD HH:mm').getTime(),
|
||||
end: date.extractDate(`2000-01-01 ${shift.end_time}`, 'YYYY-MM-DD HH:mm').getTime(),
|
||||
}));
|
||||
|
||||
for (let i = 0; i < parsed_shifts.length; i++) {
|
||||
for (let j = i + 1; j < parsed_shifts.length; j++) {
|
||||
const parsed_shift_a = parsed_shifts[i];
|
||||
const parsed_shift_b = parsed_shifts[j];
|
||||
for (let i = 0; i < parsed_shifts.length; i++) {
|
||||
for (let j = i + 1; j < parsed_shifts.length; j++) {
|
||||
const parsed_shift_a = parsed_shifts[i];
|
||||
const parsed_shift_b = parsed_shifts[j];
|
||||
|
||||
if (parsed_shift_a === undefined || parsed_shift_b === undefined) continue;
|
||||
if (parsed_shift_a === undefined || parsed_shift_b === undefined) continue;
|
||||
|
||||
if (Math.max(parsed_shift_a.start, parsed_shift_b.start) < Math.min(parsed_shift_a.end, parsed_shift_b.end)) {
|
||||
return true; // overlap found
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Math.max(parsed_shift_a.start, parsed_shift_b.start) < Math.min(parsed_shift_a.end, parsed_shift_b.end)) {
|
||||
return true; // overlap found
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return false;
|
||||
};
|
||||
|
||||
export const getCurrentDailyMinutesWorked = (shifts: Shift[]): number => {
|
||||
const shiftTypesToIgnore: ShiftType[] = ['HOLIDAY', 'SICK', 'VACATION'];
|
||||
let minutes = 0;
|
||||
|
||||
shifts.forEach(shift => {
|
||||
if (shiftTypesToIgnore.includes(shift.type)) return;
|
||||
|
||||
const startTime = new Date(`1970-01-01T${shift.start_time}:00`);
|
||||
const endTime = new Date(`1970-01-01T${shift.end_time}:00`);
|
||||
|
||||
const diff = date.getDateDiff(endTime, startTime, 'minutes');
|
||||
minutes += diff;
|
||||
});
|
||||
|
||||
return minutes;
|
||||
}
|
||||
|
||||
export const getTimeStringFromMinutes = (minutes: number): string => {
|
||||
const h = Math.floor(minutes / 60);
|
||||
const m = minutes % 60;
|
||||
|
||||
if (h < 10 ) {
|
||||
if (m < 10)
|
||||
return `0${h}:0${m}`;
|
||||
|
||||
return `0${h}:${m}`;
|
||||
}
|
||||
|
||||
return `${h}:${m}`;
|
||||
}
|
||||
|
||||
export const SHIFT_OPTIONS: ShiftOption[] = [
|
||||
{ label: 'timesheet.shift.types.REGULAR', value: 'REGULAR', icon: 'wb_sunny', icon_color: 'accent' },
|
||||
{ label: 'timesheet.shift.types.EVENING', value: 'EVENING', icon: 'bedtime', icon_color: 'orange-5' },
|
||||
{ label: 'timesheet.shift.types.EMERGENCY', value: 'EMERGENCY', icon: 'ring_volume', icon_color: 'red-5' },
|
||||
{ label: 'timesheet.shift.types.VACATION', value: 'VACATION', icon: 'beach_access', icon_color: 'deep-orange-5' },
|
||||
{ label: 'timesheet.shift.types.HOLIDAY', value: 'HOLIDAY', icon: 'event', icon_color: 'purple-5' },
|
||||
{ label: 'timesheet.shift.types.SICK', value: 'SICK', icon: 'medication_liquid', icon_color: 'light-blue-6' },
|
||||
// { label: 'timesheet.shift.types.BANKING', value: 'BANKING', icon: 'savings', icon_color: 'pink-3' },
|
||||
{ label: 'timesheet.shift.types.WITHDRAW_BANKED', value: 'WITHDRAW_BANKED', icon: 'attach_money', icon_color: 'yellow-4' },
|
||||
];
|
||||
{ label: 'timesheet.shift.types.REGULAR', value: 'REGULAR', icon: 'wb_sunny', icon_color: 'accent' },
|
||||
{ label: 'timesheet.shift.types.EVENING', value: 'EVENING', icon: 'bedtime', icon_color: 'orange-5' },
|
||||
{ label: 'timesheet.shift.types.EMERGENCY', value: 'EMERGENCY', icon: 'ring_volume', icon_color: 'red-5' },
|
||||
{ label: 'timesheet.shift.types.VACATION', value: 'VACATION', icon: 'beach_access', icon_color: 'deep-orange-5' },
|
||||
{ label: 'timesheet.shift.types.HOLIDAY', value: 'HOLIDAY', icon: 'event', icon_color: 'purple-5' },
|
||||
{ label: 'timesheet.shift.types.SICK', value: 'SICK', icon: 'medication_liquid', icon_color: 'light-blue-6' },
|
||||
// { label: 'timesheet.shift.types.BANKING', value: 'BANKING', icon: 'savings', icon_color: 'pink-3' },
|
||||
{ label: 'timesheet.shift.types.WITHDRAW_BANKED', value: 'WITHDRAW_BANKED', icon: 'attach_money', icon_color: 'yellow-4' },
|
||||
];
|
||||
|
||||
export const getShiftOptions = (disablePTO: boolean, isNotUnique: boolean): ShiftOption[] => {
|
||||
return [
|
||||
{ label: 'timesheet.shift.types.REGULAR', value: 'REGULAR', icon: 'wb_sunny', icon_color: 'accent' },
|
||||
{ label: 'timesheet.shift.types.EVENING', value: 'EVENING', icon: 'bedtime', icon_color: 'orange-5' },
|
||||
{ label: 'timesheet.shift.types.EMERGENCY', value: 'EMERGENCY', icon: 'ring_volume', icon_color: 'red-5' },
|
||||
{ label: 'timesheet.shift.types.VACATION', value: 'VACATION', icon: 'beach_access', icon_color: 'deep-orange-5', disable: disablePTO },
|
||||
{ label: 'timesheet.shift.types.HOLIDAY', value: 'HOLIDAY', icon: 'event', icon_color: 'purple-5', disable: isNotUnique || disablePTO },
|
||||
{ label: 'timesheet.shift.types.SICK', value: 'SICK', icon: 'medication_liquid', icon_color: 'light-blue-6', disable: disablePTO },
|
||||
// { label: 'timesheet.shift.types.BANKING', value: 'BANKING', icon: 'savings', icon_color: 'pink-3' },
|
||||
// { label: 'timesheet.shift.types.WITHDRAW_BANKED', value: 'WITHDRAW_BANKED', icon: 'attach_money', icon_color: 'yellow-4' },
|
||||
];
|
||||
}
|
||||
|
|
@ -20,14 +20,27 @@ export const getMinutes = (hours: number) => {
|
|||
return minutes > 1 ? minutes.toString() : '0';
|
||||
}
|
||||
|
||||
export const getHoursMinutesStringFromHoursFloat = (hours: number): string => {
|
||||
let flat_hours = Math.floor(hours);
|
||||
let minutes = Math.round((hours - flat_hours) * 60);
|
||||
export const getHoursMinutesStringFromHoursFloat = (hours: number, minutes?: number): string => {
|
||||
let flatHours = Math.floor(hours);
|
||||
let flatMinutes = minutes ?? Math.round((hours - flatHours) * 60);
|
||||
|
||||
if (minutes === 60) {
|
||||
flat_hours += 1;
|
||||
minutes = 0;
|
||||
if (flatMinutes === 60) {
|
||||
flatHours += 1;
|
||||
flatMinutes = 0;
|
||||
}
|
||||
|
||||
return `${flat_hours}h${minutes > 1 ? ' ' + minutes : ''}`
|
||||
return `${flatHours}h${flatMinutes > 1 ? ' ' + flatMinutes : ''}`
|
||||
}
|
||||
|
||||
export const getHoursMinutesBetweenTwoHHmm = (startTime: string, endTime: string): {
|
||||
hours: number,
|
||||
minutes: number,
|
||||
} => {
|
||||
const [startHours, startMinutes] = startTime.split(':');
|
||||
const [endHours, endMinutes] = endTime.split(':');
|
||||
|
||||
return {
|
||||
hours: Number(endHours) - Number(startHours),
|
||||
minutes: Number(endMinutes) - Number(startMinutes),
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user