diff --git a/quasar.config.ts b/quasar.config.ts index 3821349..13781d0 100644 --- a/quasar.config.ts +++ b/quasar.config.ts @@ -106,7 +106,7 @@ export default defineConfig((ctx) => { color: 'primary', avatar: 'https://cdn.quasar.dev/img/boy-avatar.png', }, - dark: "auto", + dark: false, }, // iconSet: 'material-icons', // Quasar icon set @@ -127,15 +127,7 @@ export default defineConfig((ctx) => { // animations: 'all', // --- includes all animations // https://v2.quasar.dev/options/animations - animations: [ - 'fadeIn', - 'fadeOut', - 'fadeInUp', - 'zoomIn', - 'zoomOut', - 'flipInX', - 'flipOutX', - ], + animations: 'all', // https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#sourcefiles // sourceFiles: { diff --git a/src/i18n/en-ca/index.ts b/src/i18n/en-ca/index.ts index 6c8f371..922125b 100644 --- a/src/i18n/en-ca/index.ts +++ b/src/i18n/en-ca/index.ts @@ -83,6 +83,7 @@ export default { cancel: "cancel", update: "update", modify: "modify", + close: "close", }, misc: { or: "or", diff --git a/src/i18n/fr-ca/index.ts b/src/i18n/fr-ca/index.ts index 907802f..213d174 100644 --- a/src/i18n/fr-ca/index.ts +++ b/src/i18n/fr-ca/index.ts @@ -83,6 +83,7 @@ export default { cancel: "annuler", update: "mettre à jour", modify: "modifier", + close: "fermer", }, misc: { or: "ou", diff --git a/src/layouts/main-layout.vue b/src/layouts/main-layout.vue index 3ea5017..90b6276 100644 --- a/src/layouts/main-layout.vue +++ b/src/layouts/main-layout.vue @@ -1,14 +1,14 @@ - + diff --git a/src/modules/auth/types/auth-interface.ts b/src/modules/auth/models/auth.models.ts similarity index 56% rename from src/modules/auth/types/auth-interface.ts rename to src/modules/auth/models/auth.models.ts index 6027520..8a1042c 100644 --- a/src/modules/auth/types/auth-interface.ts +++ b/src/modules/auth/models/auth.models.ts @@ -1,4 +1,4 @@ -import type { User } from "src/modules/shared/types/user-interface"; +import type { User } from "src/modules/shared/models/user.models"; export interface AuthState { token: string; diff --git a/src/modules/shared/components/pay-period-navigator.vue b/src/modules/shared/components/pay-period-navigator.vue index d440b8b..4485c1e 100644 --- a/src/modules/shared/components/pay-period-navigator.vue +++ b/src/modules/shared/components/pay-period-navigator.vue @@ -27,7 +27,7 @@ - + @@ -16,6 +17,7 @@ v-model="search_model" outlined dense + rounded debounce="300" class="right-rounded" :label="$t('shared.label.search')" @@ -23,21 +25,11 @@ bg-color="white" color="primary" > - + - - - \ No newline at end of file + \ No newline at end of file diff --git a/src/modules/timesheet-approval/components/details-crud-dialog-chart-expenses.vue b/src/modules/timesheet-approval/components/details-crud-dialog-chart-expenses.vue index 18c1fe5..8854179 100644 --- a/src/modules/timesheet-approval/components/details-crud-dialog-chart-expenses.vue +++ b/src/modules/timesheet-approval/components/details-crud-dialog-chart-expenses.vue @@ -1,11 +1,13 @@ - - | undefined; - plugins?: Plugin<"bar">[] | undefined; - }>(); + const { pay_period_details } = useTimesheetStore(); const hours_worked_labels = ref([]); const hours_worked_dataset = ref[]>([]); const getHoursWorkedData = (): ChartData<'bar'> => { - if (props.rawData) { - const all_weeks = [props.rawData.week1, props.rawData.week2]; - const all_days = all_weeks.flatMap( week => Object.values(week.shifts)); + + const all_days = pay_period_details.weeks.flatMap( week => Object.values(week.shifts)); const datasetConfig = [ { key: 'regular_hours', @@ -58,7 +53,7 @@ })); hours_worked_labels.value = all_days.map(day => day.short_date); - } + return { labels: hours_worked_labels.value, diff --git a/src/modules/timesheet-approval/components/details-crud-dialog-chart-shift-types.vue b/src/modules/timesheet-approval/components/details-crud-dialog-chart-shift-types.vue index 45060a8..14bd9c9 100644 --- a/src/modules/timesheet-approval/components/details-crud-dialog-chart-shift-types.vue +++ b/src/modules/timesheet-approval/components/details-crud-dialog-chart-shift-types.vue @@ -1,51 +1,49 @@ - - - + + + + {{ $t('shared.loading') }} + + + + - - - - - {{ $t('shared.loading') }} - - - - {{ payPeriodDetails.employee_full_name }} + {{ timesheet_store.pay_period_details.employee_full_name }} - - - - - - - - - - - + - + - - - + - - + + - - - + diff --git a/src/modules/timesheet-approval/components/overview-list-item.vue b/src/modules/timesheet-approval/components/overview-list-item.vue index 2702c30..852fa91 100644 --- a/src/modules/timesheet-approval/components/overview-list-item.vue +++ b/src/modules/timesheet-approval/components/overview-list-item.vue @@ -1,9 +1,11 @@ @@ -25,7 +27,7 @@ class="col-auto q-pa-none q-ma-none" color="primary" icon="work_history" - @click="emit('clickDetails')" + @click="emit('clickDetails', row)" > - import { ref } from 'vue'; + - - - + + - + - + onClickedDetails(props.row.email, overview)" /> - + + + {{ message }} - diff --git a/src/modules/timesheet-approval/components/overview-report.vue b/src/modules/timesheet-approval/components/overview-report.vue index cd353ac..417f2b4 100644 --- a/src/modules/timesheet-approval/components/overview-report.vue +++ b/src/modules/timesheet-approval/components/overview-report.vue @@ -1,9 +1,12 @@ - - - + - {{$t('timesheet_approvals.print_report.company')}} + {{ $t('timesheet_approvals.print_report.company') }} - - + @@ -59,13 +68,17 @@ - - {{$t('timesheet_approvals.print_report.type')}} - + + {{ $t('timesheet_approvals.print_report.type') }} + - import { onMounted } from 'vue'; - import { date } from 'quasar'; - import { useTimesheetApprovalApi } from 'src/modules/timesheet-approval/composables/use-timesheet-approval-api'; - import { useTimesheetStore } from 'src/stores/timesheet-store'; - import PageHeaderTemplate from 'src/modules/shared/components/page-header-template.vue'; - import EmployeeOverviewList from 'src/modules/timesheet-approval/components/employee-overview/overview-list.vue'; - import DetailedDialog from 'src/modules/timesheet-approval/components/detailed-dialog.vue'; - - const timesheet_approval_api = useTimesheetApprovalApi(); - const timesheet_store = useTimesheetStore(); - - onMounted( async () => { - await timesheet_approval_api.getPayPeriodOverviewByDate(date.formatDate( new Date(), 'YYYY-MM-DD')); - }); - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/timesheet-approval/services/timesheet-approval-service.ts b/src/modules/timesheet-approval/services/timesheet-approval-service.ts index d156929..53e3e9b 100644 --- a/src/modules/timesheet-approval/services/timesheet-approval-service.ts +++ b/src/modules/timesheet-approval/services/timesheet-approval-service.ts @@ -1,9 +1,9 @@ import { api } from "src/boot/axios"; import type { TimesheetApprovalCSVReportFilters } from "src/modules/timesheet-approval/models/timesheet-approval-csv-report.models"; -import type { PayPeriodOverview } from "src/modules/timesheet-approval/models/pay-period-overview.models"; +import type { PayPeriodOverviewResponse } from "src/modules/timesheet-approval/models/pay-period-overview.models"; export const timesheetApprovalService = { - getPayPeriodOverviewsBySupervisorEmail: async (year: number, period_number: number, supervisor_email: string): Promise => { + getPayPeriodOverviewsBySupervisorEmail: async (year: number, period_number: number, supervisor_email: string): Promise => { const response = await api.get(`pay-periods/${year}/${period_number}/${supervisor_email}`); return response.data; }, diff --git a/src/modules/timesheets/components/expenses/expense-crud-dialog-form.vue b/src/modules/timesheets/components/expense-crud-dialog-form.vue similarity index 65% rename from src/modules/timesheets/components/expenses/expense-crud-dialog-form.vue rename to src/modules/timesheets/components/expense-crud-dialog-form.vue index 8ffeb3a..38773bf 100644 --- a/src/modules/timesheets/components/expenses/expense-crud-dialog-form.vue +++ b/src/modules/timesheets/components/expense-crud-dialog-form.vue @@ -2,50 +2,53 @@ setup lang="ts" > - import { ref } from 'vue'; + import { inject, ref } from 'vue'; + import { useI18n } from 'vue-i18n'; import { useExpensesStore } from 'src/stores/expense-store'; - import type { ExpenseType, Expense } from 'src/modules/timesheets/models/expense.models'; + import { default_expense, EXPENSE_TYPE, TYPES_WITH_AMOUNT_ONLY } from 'src/modules/timesheets/models/expense.models'; + import { makeExpenseRules } from 'src/modules/timesheets/utils/expense.util'; + import { useExpensesApi } from 'src/modules/timesheets/composables/api/use-expense-api'; - const expense_store = useExpensesStore(); + const { t } = useI18n(); + + const expenses_store = useExpensesStore(); + const expenses_api = useExpensesApi(); const files = defineModel('files'); const is_navigator_open = ref(false); - //------------------ props ------------------ - defineProps<{ - type_options: { label: string; value: ExpenseType }[]; - show_amount: boolean; - is_readonly: boolean; - rules: { - typeRequired: (val: unknown) => true | string; - amountRequired: (val: unknown) => true | string; - mileageRequired: (val: unknown) => true | string; - commentRequired: (val: unknown) => true | string; - commentTooLong: (val: unknown) => true | string; - }; - comment_max_length: number; - setType: (val: ExpenseType) => void; - }>(); + const COMMENT_MAX_LENGTH = 280; + const employee_email = inject('employeeEmail'); + const rules = makeExpenseRules(t); - //------------------ emits ------------------ - const emit = defineEmits<{ - 'submit': [void]; - }>(); + const cancelUpdateMode = () => { + expenses_store.current_expense = default_expense; + expenses_store.initial_expense = default_expense; + expenses_store.mode = 'create'; + } + + const requestExpenseCreationOrUpdate = async () => { + if (expenses_store.mode === 'create') await expenses_api.createExpenseByEmployeeEmail(employee_email ?? '', expenses_store.current_expense.date); + else await expenses_api.updateExpenseByEmployeeEmail(employee_email ?? '', expenses_store.current_expense.date); + } {{ $t('timesheet.expense.add_expense') }} - + @@ -74,8 +77,8 @@ setType(val as ExpenseType)" /> - + @@ -171,6 +173,15 @@ + + + import { useExpensesStore } from 'src/stores/expense-store'; + + const expense_store = useExpensesStore(); + + + + + + {{ $t('timesheet.expense.title') }} + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/timesheets/components/expense-crud-dialog-list.vue b/src/modules/timesheets/components/expense-crud-dialog-list.vue new file mode 100644 index 0000000..eef7b96 --- /dev/null +++ b/src/modules/timesheets/components/expense-crud-dialog-list.vue @@ -0,0 +1,151 @@ + + + + + + + {{ $t('timesheet.expense.empty_list') }} + + + + + + + + + + + + {{ expense.mileage?.toFixed(1) }} km + + + {{ expense.amount.toFixed(2) }} $ + + + + {{ expense.amount.toFixed(2) }} $ + + + + + {{ $d(new Date(expense.date), { year: 'numeric', month: 'short', day: 'numeric', weekday: 'short' }) + }} + + + + + + + + + + + + {{ $t('timesheet.expense.employee_comment') }} + + + {{ expense.comment }} + + + + + + + {{ $t('timesheet.expense.supervisor_comment') }} + + + {{ expense.supervisor_comment }} + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/timesheets/components/expenses/expense-crud-dialog-supervisor-comment.vue b/src/modules/timesheets/components/expense-crud-dialog-supervisor-comment.vue similarity index 100% rename from src/modules/timesheets/components/expenses/expense-crud-dialog-supervisor-comment.vue rename to src/modules/timesheets/components/expense-crud-dialog-supervisor-comment.vue diff --git a/src/modules/timesheets/components/expense-crud-dialog.vue b/src/modules/timesheets/components/expense-crud-dialog.vue new file mode 100644 index 0000000..ed9d3ed --- /dev/null +++ b/src/modules/timesheets/components/expense-crud-dialog.vue @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/timesheets/components/expenses/expense-crud-dialog-list.vue b/src/modules/timesheets/components/expenses/expense-crud-dialog-list.vue deleted file mode 100644 index 9e5f12b..0000000 --- a/src/modules/timesheets/components/expenses/expense-crud-dialog-list.vue +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - {{ $t('timesheet.expense.empty_list') }} - - - - - - - - - - - - {{ expense.mileage?.toFixed(1) }} km - - - {{ expense.amount?.toFixed(2) }} $ - - - - {{ expense.amount?.toFixed(2) }} $ - - - - - {{ $d(new Date(expense.date + 'T00:00:00'), { year:'numeric', month:'short', day: 'numeric', weekday: 'short'}) }} - - - - - - - - - - - - {{ $t('timesheet.expense.employee_comment') }} - - - {{ expense.comment }} - - - - - - - {{ $t('timesheet.expense.supervisor_comment') }} - - - {{ expense.supervisor_comment }} - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/timesheets/components/expenses/expense-crud-dialog.vue b/src/modules/timesheets/components/expenses/expense-crud-dialog.vue deleted file mode 100644 index 7c78582..0000000 --- a/src/modules/timesheets/components/expenses/expense-crud-dialog.vue +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - - - - - - - - - - - {{ $t('timesheet.expense.title') }} - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/timesheets/components/expenses/timesheet-details-expenses.vue b/src/modules/timesheets/components/expenses/timesheet-details-expenses.vue deleted file mode 100644 index 40ba68a..0000000 --- a/src/modules/timesheets/components/expenses/timesheet-details-expenses.vue +++ /dev/null @@ -1,267 +0,0 @@ - - - - - - - - {{ $t('timesheet.expense.title') }} - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/timesheets/components/shift/shift-crud-dialog.vue b/src/modules/timesheets/components/shift-crud-dialog.vue similarity index 100% rename from src/modules/timesheets/components/shift/shift-crud-dialog.vue rename to src/modules/timesheets/components/shift-crud-dialog.vue diff --git a/src/modules/timesheets/components/shift/shift-list-header.vue b/src/modules/timesheets/components/shift-list-header.vue similarity index 91% rename from src/modules/timesheets/components/shift/shift-list-header.vue rename to src/modules/timesheets/components/shift-list-header.vue index 37d2af7..780e833 100644 --- a/src/modules/timesheets/components/shift/shift-list-header.vue +++ b/src/modules/timesheets/components/shift-list-header.vue @@ -9,7 +9,7 @@ - {{ $t('shiftColumns.labelIn') }} + {{ $t('shared.misc.in') }} @@ -20,7 +20,7 @@ - {{ $t('shiftColumns.labelOut') }} + {{ $t('shared.misc.out') }} diff --git a/src/modules/timesheets/components/shift-list-legend.vue b/src/modules/timesheets/components/shift-list-legend.vue new file mode 100644 index 0000000..f8327fe --- /dev/null +++ b/src/modules/timesheets/components/shift-list-legend.vue @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/timesheets/components/shift/shift-list-row.vue b/src/modules/timesheets/components/shift-list-row.vue similarity index 92% rename from src/modules/timesheets/components/shift/shift-list-row.vue rename to src/modules/timesheets/components/shift-list-row.vue index 3e18540..b978473 100644 --- a/src/modules/timesheets/components/shift/shift-list-row.vue +++ b/src/modules/timesheets/components/shift-list-row.vue @@ -3,8 +3,9 @@ import type { Shift } from 'src/modules/timesheets/models/shift.models'; - const { shift } = defineProps<{ + const { shift, dense = false } = defineProps<{ shift: Shift; + dense?: boolean; }>(); const emit = defineEmits<{ @@ -14,11 +15,12 @@ }>(); const has_comment = computed(() => { - const comment = (shift as any).description ?? (shift as any).comment ?? ''; + const comment = shift.comment ?? ''; return typeof comment === 'string' && comment.trim().length > 0; }) const comment_icon = computed(() => (has_comment.value ? 'announcement' : 'chat_bubble_outline')); const comment_color = computed(() => (has_comment.value ? 'primary' : 'grey-8')); + const hour_font_size = computed(() => dense ? '0.9em' : '1.5em' ) const get_shift_color = (type: string): string => { switch (type) { @@ -27,7 +29,7 @@ case 'EMERGENCY': return 'amber-10'; case 'OVERTIME': return 'negative'; case 'VACATION': return 'purple-10'; - case 'HOLIDAY': return 'purple-10'; + case 'HOLIDAY': return 'purple-5'; case 'SICK': return 'grey-8'; default: return 'transparent'; } @@ -61,7 +63,7 @@ {{ shift.start_time }} diff --git a/src/modules/timesheets/components/shift/shift-list.vue b/src/modules/timesheets/components/shift-list.vue similarity index 56% rename from src/modules/timesheets/components/shift/shift-list.vue rename to src/modules/timesheets/components/shift-list.vue index 6b6a4ef..fc43565 100644 --- a/src/modules/timesheets/components/shift/shift-list.vue +++ b/src/modules/timesheets/components/shift-list.vue @@ -3,26 +3,23 @@ lang="ts" > import { date } from 'quasar'; - import ShiftListHeader from 'src/modules/timesheets/components/shift/shift-list-header.vue'; - import ShiftListRow from 'src/modules/timesheets/components/shift/shift-list-row.vue'; - import ShiftListLegend from 'src/modules/timesheets/components/shift/shift-list-legend.vue'; - import PayPeriodNavigator from 'src/modules/shared/components/pay-period-navigator.vue'; + import ShiftListHeader from 'src/modules/timesheets/components/shift-list-header.vue'; + import ShiftListRow from 'src/modules/timesheets/components/shift-list-row.vue'; import { useShiftStore } from 'src/stores/shift-store'; - import { useTimesheetApi } from 'src/modules/timesheets/composables/api/use-timesheet-api'; + import { useTimesheetStore } from 'src/stores/timesheet-store'; import { type Shift, default_shift } from 'src/modules/timesheets/models/shift.models'; - import type { PayPeriodDetails } from 'src/modules/timesheets/models/pay-period-details.models'; - import type { PayPeriod } from 'src/modules/shared/types/pay-period-interface'; +import { computed } from 'vue'; - const props = defineProps<{ - rawData: PayPeriodDetails; - currentPayPeriod: PayPeriod; + const timesheet_store = useTimesheetStore(); + const { openCreate, openDelete, openUpdate } = useShiftStore(); + const { dense = false } = defineProps<{ + dense?: boolean; }>(); - const timesheet_api = useTimesheetApi(); - const { openCreate, openDelete, openUpdate } = useShiftStore(); + const font_size = computed(() => dense ? '1.5em' : '2.5em') const get_date_from_short = (short_date: string): Date => { - return new Date(props.currentPayPeriod.pay_year.toString() + '/' + short_date); + return new Date(timesheet_store.pay_period.pay_year.toString() + '/' + short_date); }; const to_iso_date = (short_date: string): string => { @@ -34,37 +31,35 @@ }; const getDate = (shift_date: string): Date => { - return new Date(props.currentPayPeriod.pay_year.toString() + '/' + shift_date); + return new Date(timesheet_store.pay_period.pay_year.toString() + '/' + shift_date); }; - - - - - + + {{ $d(getDate(day.short_date), { weekday: $q.screen.lt.md ? 'short' : 'long' }) }} {{ day.short_date.split('/')[1] }} - - openUpdate(to_iso_date(day.short_date), value)" - @request-delete="value => openDelete(to_iso_date(day.short_date), value)" - /> + + + openUpdate(to_iso_date(day.short_date), value)" + @request-delete="value => openDelete(to_iso_date(day.short_date), value)" + /> + diff --git a/src/modules/timesheets/components/shift/shift-list-legend.vue b/src/modules/timesheets/components/shift/shift-list-legend.vue deleted file mode 100644 index 82f5f7a..0000000 --- a/src/modules/timesheets/components/shift/shift-list-legend.vue +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/src/modules/timesheets/components/timesheet-wrapper.vue b/src/modules/timesheets/components/timesheet-wrapper.vue new file mode 100644 index 0000000..31bd9a8 --- /dev/null +++ b/src/modules/timesheets/components/timesheet-wrapper.vue @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/timesheets/components/timesheet/timesheet-wrapper.vue b/src/modules/timesheets/components/timesheet/timesheet-wrapper.vue deleted file mode 100644 index fa4f330..0000000 --- a/src/modules/timesheets/components/timesheet/timesheet-wrapper.vue +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/timesheets/composables/api/use-expense-api.ts b/src/modules/timesheets/composables/api/use-expense-api.ts index 4bcc01c..ee9a3fd 100644 --- a/src/modules/timesheets/composables/api/use-expense-api.ts +++ b/src/modules/timesheets/composables/api/use-expense-api.ts @@ -1,6 +1,6 @@ import { normalizeObject } from "src/utils/normalize-object"; import { useExpensesStore } from "src/stores/expense-store"; -import { expense_validation_schema, type ExpensesApiError } from "src/modules/timesheets/models/expense.validation"; +import { expense_validation_schema } from "src/modules/timesheets/models/expense.validation"; import type { Expense, UpsertExpense } from "src/modules/timesheets/models/expense.models"; export const useExpensesApi = () => { @@ -11,26 +11,26 @@ export const useExpensesApi = () => { new_expense?: Expense; }) => obj as UpsertExpense; - const createExpenseByEmployeeEmail = async (employee_email: string): Promise => { + const createExpenseByEmployeeEmail = async (employee_email: string, date: string): Promise => { const upsert_expense = toUpsertExpense({ new_expense: normalizeObject(expenses_store.current_expense, expense_validation_schema), }); - await expenses_store.upsertOrDeleteExpensesByEmployeeEmail(employee_email, upsert_expense); + await expenses_store.upsertOrDeleteExpensesByEmployeeEmail(employee_email, date, upsert_expense); }; - const updateExpenseByEmployeeEmail = async (employee_email: string): Promise => { + const updateExpenseByEmployeeEmail = async (employee_email: string, date: string): Promise => { const upsert_expense = toUpsertExpense({ old_expense: normalizeObject(expenses_store.initial_expense, expense_validation_schema), new_expense: normalizeObject(expenses_store.current_expense, expense_validation_schema), }); - await expenses_store.upsertOrDeleteExpensesByEmployeeEmail(employee_email, upsert_expense); + await expenses_store.upsertOrDeleteExpensesByEmployeeEmail(employee_email, date, upsert_expense); }; - const deleteExpenseByEmployeeEmail = async (employee_email: string): Promise => { + const deleteExpenseByEmployeeEmail = async (employee_email: string, date: string): Promise => { const upsert_expense = toUpsertExpense({ old_expense: normalizeObject(expenses_store.initial_expense, expense_validation_schema), }); - await expenses_store.upsertOrDeleteExpensesByEmployeeEmail(employee_email, upsert_expense); + await expenses_store.upsertOrDeleteExpensesByEmployeeEmail(employee_email, date, upsert_expense); }; return { diff --git a/src/modules/timesheets/composables/api/use-shift-api.ts b/src/modules/timesheets/composables/api/use-shift-api.ts index a7f043c..f32461d 100644 --- a/src/modules/timesheets/composables/api/use-shift-api.ts +++ b/src/modules/timesheets/composables/api/use-shift-api.ts @@ -1,5 +1,5 @@ import { unwrapAndClone } from "src/utils/unwrap-and-clone"; -import { DATE_FORMAT_PATTERN, TIME_FORMAT_PATTERN } from "src/modules/timesheets/constants/shift.constants"; +import { TIME_FORMAT_PATTERN } from "src/modules/timesheets/constants/shift.constants"; import { GenericApiError } from "src/modules/timesheets/models/expense.validation"; import { useShiftStore } from "src/stores/shift-store"; import { default_shift, type Shift, type UpsertShift } from "src/modules/timesheets/models/shift.models"; diff --git a/src/modules/timesheets/composables/use-expense-draft.ts b/src/modules/timesheets/composables/use-expense-draft.ts index 182a5d3..8b7afa2 100644 --- a/src/modules/timesheets/composables/use-expense-draft.ts +++ b/src/modules/timesheets/composables/use-expense-draft.ts @@ -1,11 +1,11 @@ import { ref, computed } from "vue"; -import type { TimesheetExpense } from "../types/expense.interfaces"; -import type { ExpenseType } from "../types/expense.types"; +import type { Expense } from "src/modules/timesheets/models/expense.models"; +import type { ExpenseType } from "src/modules/timesheets/models/expense.models"; -export const useExpenseDraft = (initial?: Partial) => { +export const useExpenseDraft = (initial?: Partial) => { const DEFAULT_TYPE: ExpenseType = 'EXPENSES'; - const draft = ref>({ + const draft = ref>({ date: '', type: DEFAULT_TYPE, comment: '', diff --git a/src/modules/timesheets/composables/use-expense-items.ts b/src/modules/timesheets/composables/use-expense-items.ts index 46d4e8e..aeeae5a 100644 --- a/src/modules/timesheets/composables/use-expense-items.ts +++ b/src/modules/timesheets/composables/use-expense-items.ts @@ -1,58 +1,59 @@ -import { ref, type Ref } from "vue"; -import { normalizeExpense, validateExpenseUI } from "../utils/expenses-validators"; -import { normExpenseType } from "../utils/expense.util"; -import type { Expense, PayPeriodExpenses } from "src/modules/timesheets/models/expense.models"; -import { useExpensesStore } from "src/stores/expense-store"; -import { unwrapAndClone } from "src/utils/unwrap-and-clone"; +// import { ref, type Ref } from "vue"; +// import { normalizeObject } from "src/utils/normalize-object"; +// import { normExpenseType } from "../utils/expense.util"; +// import type { Expense, PayPeriodExpenses } from "src/modules/timesheets/models/expense.models"; +// import { useExpensesStore } from "src/stores/expense-store"; +// import { unwrapAndClone } from "src/utils/unwrap-and-clone"; +// import { expense_validation_schema } from "src/modules/timesheets/models/expense.validation"; -const expenses_store = useExpensesStore(); +// const expenses_store = useExpensesStore(); -export const useExpenseItems = () => { - let expenses = unwrapAndClone(expenses_store.pay_period_expenses.expenses.map(normalizeExpense)); +// export const useExpenseItems = () => { +// let expenses = unwrapAndClone(expenses_store.pay_period_expenses.expenses.map(normalizeExpense)); - const normalizePayload = (expense: Expense): Expense => { - const exp = normalizeExpense(expense); - const out: Expense = { - date: exp.date, - type: exp.type as ExpenseType, - comment: exp.comment || '', - }; - if(typeof exp.amount === 'number') out.amount = exp.amount; - if(typeof exp.mileage === 'number') out.mileage = exp.mileage; - return out; -} +// const normalizePayload = (expense: Expense): Expense => { +// const exp = normalizeObject(expense, expense_validation_schema); +// const out: Expense = { +// date: exp.date, +// type: exp.type as ExpenseType, +// comment: exp.comment || '', +// }; +// if(typeof exp.amount === 'number') out.amount = exp.amount; +// if(typeof exp.mileage === 'number') out.mileage = exp.mileage; +// return out; +// } - const addFromDraft = () => { - const candidate: Expense = normalizeExpense({ - date: draft.date, - type: normExpenseType(draft.type), - ...(typeof draft.amount === 'number' ? { amount: draft.amount }: {}), - ...(typeof draft.mileage === 'number' ? { mileage: draft.mileage }: {}), - comment: String(draft.comment ?? '').trim(), - } as Expense); +// const addFromDraft = () => { +// const candidate: Expense = normalizeExpense({ +// date: draft.date, +// type: normExpenseType(draft.type), +// ...(typeof draft.amount === 'number' ? { amount: draft.amount }: {}), +// ...(typeof draft.mileage === 'number' ? { mileage: draft.mileage }: {}), +// comment: String(draft.comment ?? '').trim(), +// } as Expense); - validateExpenseUI(candidate, 'expense_draft'); - expenses = [ ...expenses, candidate]; - }; +// validateExpenseUI(candidate, 'expense_draft'); +// expenses = [ ...expenses, candidate]; +// }; - const removeAt = (index: number) => { - if(index < 0 || index >= expenses.length) return; - expenses = expenses.filter((_,i)=> i !== index); - }; +// const removeAt = (index: number) => { +// if(index < 0 || index >= expenses.length) return; +// expenses = expenses.filter((_,i)=> i !== index); +// }; - const validateAll = () => { - for (const expense of expenses) { - validateExpenseUI(expense, 'expense_item'); - } - }; +// const validateAll = () => { +// for (const expense of expenses) { +// validateExpenseUI(expense, 'expense_item'); +// } +// }; - const payload = () => expenses.map(normalizeExpense); +// const payload = () => expenses.map(normalizeExpense); - return { - expenses, - addFromDraft, - removeAt, - validateAll, - payload, - }; -}; +// return { +// expenses, +// addFromDraft, +// removeAt, +// validateAll, +// payload, +// }; +// }; diff --git a/src/modules/timesheets/constants/expense.constants.ts b/src/modules/timesheets/constants/expense.constants.ts index 14ad95b..ce6d207 100644 --- a/src/modules/timesheets/constants/expense.constants.ts +++ b/src/modules/timesheets/constants/expense.constants.ts @@ -1,3 +1 @@ -export const COMMENT_MAX_LENGTH = 280; - export const DATE_FORMAT_PATTERN = /^\d{4}-\d{2}-\d{2}$/; \ No newline at end of file diff --git a/src/modules/timesheets/models/expense.models.ts b/src/modules/timesheets/models/expense.models.ts index 12d8c08..dbc32a5 100644 --- a/src/modules/timesheets/models/expense.models.ts +++ b/src/modules/timesheets/models/expense.models.ts @@ -7,7 +7,7 @@ export const TYPES_WITH_AMOUNT_ONLY: Readonly = ['PER_DIEM', 'EXP export interface Expense { date: string; type: ExpenseType; - amount?: number; + amount: number; mileage?: number; comment: string; supervisor_comment?: string; @@ -26,10 +26,9 @@ export interface PayPeriodExpenses { totals?: ExpenseTotals; } -export interface TimesheetDetailsWeekDayExpenses { - cash: Expense[]; - km: Expense[]; - [otherType: string]: Expense[]; +export interface UpsertExpense { + old_expense: Expense; + new_expense: Expense; } export const default_expense: Expense = { diff --git a/src/modules/timesheets/models/expense.validation.ts b/src/modules/timesheets/models/expense.validation.ts index 5fbe65a..9a9700a 100644 --- a/src/modules/timesheets/models/expense.validation.ts +++ b/src/modules/timesheets/models/expense.validation.ts @@ -1,5 +1,5 @@ -import { Expense, EXPENSE_TYPE, ExpenseType } from "src/modules/timesheets/models/expense.models"; -import { Normalizer } from "src/utils/normalize-object"; +import { type Expense, EXPENSE_TYPE, type ExpenseType } from "src/modules/timesheets/models/expense.models"; +import type { Normalizer } from "src/utils/normalize-object"; export interface ApiErrorPayload { status_code: number; @@ -43,12 +43,12 @@ export class ExpensesApiError extends ApiError { }; export const expense_validation_schema: Normalizer = { - date: v => String(v ?? "1970-01-01").trim(), - type: v => EXPENSE_TYPE.includes(v) ? v as ExpenseType : "EXPENSES", - amount: v => typeof v === "number" ? v : undefined, + date: v => typeof v === 'string' ? v.trim() : '1970-01-01', + type: v => EXPENSE_TYPE.includes(v as ExpenseType) ? v as ExpenseType : "EXPENSES", + amount: v => typeof v === "number" ? v : -1, mileage: v => typeof v === "number" ? v : undefined, - comment: v => String(v ?? "").trim(), - supervisor_comment: v => String(v ?? "").trim(), + comment: v => typeof v === 'string' ? v.trim() : '', + supervisor_comment: v => typeof v === 'string' ? v.trim() : '', is_approved: v => !!v, }; diff --git a/src/modules/timesheets/models/pay-period-details.models.ts b/src/modules/timesheets/models/pay-period-details.models.ts index c19a69f..4ec846c 100644 --- a/src/modules/timesheets/models/pay-period-details.models.ts +++ b/src/modules/timesheets/models/pay-period-details.models.ts @@ -1,5 +1,5 @@ import type { Shift } from "./shift.models"; -import type { Expense } from "src/modules/timesheets/models/expense.models"; +import { default_expense, type Expense } from "src/modules/timesheets/models/expense.models"; export type Week = { sun: T; @@ -34,9 +34,9 @@ export interface PayPeriodDetailsWeekDayShifts { } export interface PayPeriodDetailsWeekDayExpenses { - cash: Expense[]; - km: Expense[]; - [otherType: string]: Expense[]; + expenses: Expense[]; + total_expenses: number; + total_mileage: number; } const makeWeek = (factory: ()=> T): Week => ({ @@ -61,8 +61,9 @@ const emptyDailySchedule = (): PayPeriodDetailsWeekDayShifts => ({ }); const emptyDailyExpenses = (): PayPeriodDetailsWeekDayExpenses => ({ - cash: [], - km: [], + expenses: [default_expense,], + total_expenses: -1, + total_mileage: -1, }); export const defaultPayPeriodDetailsWeek = (): PayPeriodDetailsWeek => ({ diff --git a/src/modules/timesheets/pages/timesheet-page.vue b/src/modules/timesheets/pages/timesheet-page.vue deleted file mode 100644 index 7be7fd6..0000000 --- a/src/modules/timesheets/pages/timesheet-page.vue +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/src/modules/timesheets/services/timesheet-service.ts b/src/modules/timesheets/services/timesheet-service.ts index d52d38c..0f4fdda 100644 --- a/src/modules/timesheets/services/timesheet-service.ts +++ b/src/modules/timesheets/services/timesheet-service.ts @@ -3,7 +3,7 @@ import type { UpsertShift } from "src/modules/timesheets/models/shift.models"; import type { PayPeriod } from "src/modules/shared/models/pay-period.models"; import type { PayPeriodDetails } from "src/modules/timesheets/models/pay-period-details.models"; import type { PayPeriodOverview } from "src/modules/timesheet-approval/models/pay-period-overview.models"; -import type { PayPeriodExpenses } from "src/modules/timesheets/models/expense.models"; +import type { Expense, PayPeriodExpenses, UpsertExpense } from "src/modules/timesheets/models/expense.models"; export const timesheetService = { getPayPeriodDetailsByEmployeeEmail: async (email: string): Promise => { @@ -31,8 +31,20 @@ export const timesheetService = { return response.data; }, - upsertOrDeletePayPeriodDetailsByDateAndEmployeeEmail: async (email: string, payload: UpsertShift[] | PayPeriodExpenses, pay_period: PayPeriod, date?: string): Promise => { - if (date) return (await api.put(`/shifts/upsert/${email}/${date}`, payload)).data; - else return (await api.put(`/expenses/${email}/${pay_period.pay_year}/${pay_period.pay_period_no}`, payload, { headers: {'Content-Type': 'application/json'}})).data; + getExpensesByPayPeriodAndEmployeeEmail: async (email: string, year: string, period_number: string): Promise => { + const response = await api.get(`/expenses/${email}/${year}/${period_number}`); + return response.data; }, + + upsertOrDeleteShiftsByDateAndEmployeeEmail: async (email: string, payload: UpsertShift[], date: string): Promise => { + const response = await api.put(`/shifts/upsert/${email}/${date}`, payload); + return response.data; + }, + + upsertOrDeleteExpensesByPayPeriodAndEmployeeEmail: async (email: string, date: string, payload: UpsertExpense): Promise => { + const headers = { 'Content-Type': 'application/json' } + + const response = await api.put(`/expenses/upsert/${email}/${date}`, payload, { headers }); + return response.data; + } }; \ No newline at end of file diff --git a/src/modules/timesheets/utils/expense.util.ts b/src/modules/timesheets/utils/expense.util.ts index b81918b..36e0bb3 100644 --- a/src/modules/timesheets/utils/expense.util.ts +++ b/src/modules/timesheets/utils/expense.util.ts @@ -1,8 +1,8 @@ -import type { Expense, ExpenseTotals, ExpenseType, PayPeriodExpenses } from "src/modules/timesheets/models/expense.models"; +import type { Expense, ExpenseTotals } from "src/modules/timesheets/models/expense.models"; //------------------ normalization / icons ------------------ export const normExpenseType = (type: unknown): string => - String(type ?? '').trim().toUpperCase(); + typeof type === 'string' ? type.trim().toUpperCase() : ''; const icon_map: Record = { MILEAGE: 'time_to_leave', @@ -31,7 +31,7 @@ export const computeExpenseTotals = (items: readonly Expense[]): ExpenseTotals = ); //------------------ Quasar :rules=[] ------------------ -export const makeExpenseRules = (t: (key: string) => string, max_comment_char: number) => { +export const makeExpenseRules = (t: (_key: string) => string) => { const isPresent = (val: unknown) => val !== undefined && val !== null && val !== ''; const typeRequired = (val: unknown) => (!!val) || t('timesheet.expense.errors.type_required'); @@ -40,15 +40,12 @@ export const makeExpenseRules = (t: (key: string) => string, max_comment_char: n const mileageRequired = (val: unknown) => (isPresent(val)) || t('timesheet.expense.errors.mileage_required_for_type'); - const commentRequired = (val: unknown) => (String(val ?? '').trim().length > 0) || t('timesheet.expense.errors.comment_required'); - - const commentTooLong = (val: unknown) => (String(val ?? '').trim().length <= max_comment_char) || t('timesheet.expense.errors.comment_too_long'); + const commentRequired = (val: unknown) => (typeof val === 'string' ? val.trim().length > 0 : false ) || t('timesheet.expense.errors.comment_required'); return { typeRequired, amountRequired, mileageRequired, commentRequired, - commentTooLong, }; }; \ No newline at end of file diff --git a/src/modules/timesheets/utils/timesheet-format.util.ts b/src/modules/timesheets/utils/timesheet-format.util.ts index 5c4775a..c2ecb12 100644 --- a/src/modules/timesheets/utils/timesheet-format.util.ts +++ b/src/modules/timesheets/utils/timesheet-format.util.ts @@ -1,4 +1,4 @@ -import type { PayPeriodLabel } from "../types/ui.types"; +import type { PayPeriodLabel } from "src/modules/timesheets/models/ui.models"; export const formatPayPeriodLabel = ( raw_label: string | undefined, diff --git a/src/modules/profile/pages/profile-container.vue b/src/pages/profile-page.vue similarity index 100% rename from src/modules/profile/pages/profile-container.vue rename to src/pages/profile-page.vue diff --git a/src/modules/employee-list/pages/supervisor-crew-page.vue b/src/pages/supervisor-crew-page.vue similarity index 100% rename from src/modules/employee-list/pages/supervisor-crew-page.vue rename to src/pages/supervisor-crew-page.vue diff --git a/src/pages/timesheet-approval-page.vue b/src/pages/timesheet-approval-page.vue new file mode 100644 index 0000000..cb972db --- /dev/null +++ b/src/pages/timesheet-approval-page.vue @@ -0,0 +1,46 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/pages/timesheet-page.vue b/src/pages/timesheet-page.vue new file mode 100644 index 0000000..639d8db --- /dev/null +++ b/src/pages/timesheet-page.vue @@ -0,0 +1,37 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/router/routes.ts b/src/router/routes.ts index a0c8225..7a21d96 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -15,22 +15,22 @@ const routes: RouteRecordRaw[] = [ { path: 'timesheet-approvals', name: RouteNames.TIMESHEET_APPROVALS, - component: () => import('src/modules/timesheet-approval/pages/timesheet-approval.vue'), + component: () => import('src/pages/timesheet-approval-page.vue'), }, { path: 'employees', name: RouteNames.EMPLOYEE_LIST, - component: () => import('src/modules/employee-list/pages/supervisor-crew-page.vue'), + component: () => import('src/pages/supervisor-crew-page.vue'), }, { path: 'timesheet-temp', name: RouteNames.TIMESHEET_TEMP, - component: () => import('src/modules/timesheets/pages/timesheet-details-overview.vue') + component: () => import('src/pages/timesheet-page.vue') }, { path: 'user/profile', name: RouteNames.PROFILE, - component: () => import('src/modules/profile/pages/profile-container.vue'), + component: () => import('src/pages/profile-page.vue'), }, ], }, diff --git a/src/stores/auth-store.ts b/src/stores/auth-store.ts index d7ba1f5..603876f 100644 --- a/src/stores/auth-store.ts +++ b/src/stores/auth-store.ts @@ -1,20 +1,20 @@ import { computed, ref } from "vue"; import { defineStore } from "pinia"; import { AuthService } from "../modules/auth/services/services-auth"; -import type { User } from "src/modules/shared/types/user-interface"; +import type { User } from "src/modules/shared/models/user.models"; export type CompanyRole = 'guest' | 'supervisor' | 'accounting' | 'human_resources' | 'employee'; const TestUsers: Record = { guest: { firstName: 'Unknown', lastName: 'Unknown', email: 'guest@guest.com', role: 'guest' }, - supervisor: { firstName: 'Robin', lastName: 'Clark', email: 'user5@example.test', role: 'supervisor' }, + supervisor: { firstName: 'User', lastName: 'Test', email: 'user@targointernet.com', role: 'supervisor' }, accounting: { firstName: 'Robin', lastName: 'Clark', email: 'user5@example.test', role: 'supervisor' }, human_resources: { firstName: 'Robin', lastName: 'Clark', email: 'user5@example.test', role: 'supervisor' }, employee: { firstName: 'Robin', lastName: 'Clark', email: 'user5@example.test', role: 'supervisor' }, } export const useAuthStore = defineStore('auth', () => { - const user = ref(TestUsers.guest); + const user = ref(TestUsers.guest); const authError = ref(""); const isAuthorizedUser = computed(() => user.value.role !== 'guest'); diff --git a/src/stores/expense-store.ts b/src/stores/expense-store.ts index 307ccf8..b9bbfbd 100644 --- a/src/stores/expense-store.ts +++ b/src/stores/expense-store.ts @@ -1,86 +1,103 @@ -import { ref } from "vue"; +import { computed, ref } from "vue"; import { defineStore } from "pinia"; import { useTimesheetStore } from "src/stores/timesheet-store"; -import { default_expense, default_pay_period_expenses, type Expense, type PayPeriodExpenses } from "src/modules/timesheets/models/expense.models"; -import { unwrapAndClone } from "src/utils/unwrap-and-clone"; +import { default_expense, default_pay_period_expenses, type UpsertExpense, type Expense, type PayPeriodExpenses } from "src/modules/timesheets/models/expense.models"; import { timesheetService } from "src/modules/timesheets/services/timesheet-service"; -import { ExpensesApiError } from "src/modules/timesheets/models/expense.validation"; +import { ExpensesApiError, type GenericApiError } from "src/modules/timesheets/models/expense.validation"; +import { computeExpenseTotals } from "src/modules/timesheets/utils/expense.util"; +import type { UpsertAction } from "src/modules/timesheets/models/shift.models"; -const { pay_period } = useTimesheetStore(); -const encodeData = ( email: string, year: number, period_number: number ) => { - return { email: encodeURIComponent(email), year: encodeURIComponent(year), period_number: encodeURIComponent(period_number)}; -} export const useExpensesStore = defineStore('expenses', () => { + const timesheet_store = useTimesheetStore(); const is_open = ref(false); const is_loading = ref(false); + const mode = ref('create'); const pay_period_expenses = ref(default_pay_period_expenses); + const pay_period_expenses_totals = computed(() => computeExpenseTotals(pay_period_expenses.value.expenses)) const current_expense = ref(default_expense); const initial_expense = ref(default_expense); const error = ref(null); - const setErrorFrom = (err: unknown) => { - const e = err as any; - error.value = e?.message || 'Unknown error'; - }; + // const setErrorFrom = (err: unknown) => { + // const e = err as any; + // error.value = e?.message || 'Unknown error'; + // }; const open = async (employee_email: string): Promise => { is_open.value = true; is_loading.value = true; error.value = null; + current_expense.value = default_expense; + initial_expense.value = default_expense; await getPayPeriodExpensesByEmployeeEmail(employee_email); is_loading.value = false; } - const getPayPeriodExpensesByEmployeeEmail = async (employee_email: string): Promise => { - const encoded_data = encodeData(employee_email, pay_period.pay_year, pay_period.pay_period_no); - - try { - const expenses = await timesheetService.getExpensesByPayPeriodAndEmployeeEmail(encoded_data.email, encoded_data.year, encoded_data.period_number); - pay_period_expenses.value = expenses; - } catch(err:any) { - const status_code: number = err?.response?.status ?? 500; - const data = err?.response?.data ?? {}; - error.value = data.message || data.error || err.message; - - throw new ExpensesApiError({ - status_code, - error_code: data.error_code, - message: data.message || data.error || err.message, - context: data.context, - }); - } - }; - - const upsertOrDeleteExpensesByEmployeeEmail = async (employee_email: string, expenses: Expense[]): Promise => { - is_loading.value = true; - error.value = null; - - try { - const encoded_data = encodeData(employee_email, pay_period.pay_year, pay_period.pay_period_no); - const payload = { is_approved: false, expenses }; - - const updated_expenses = await timesheetService.upsertOrDeleteExpensesByPayPeriodAndEmployeeEmail(encoded_data.email, encoded_data.year, encoded_data.period_number, payload); - pay_period_expenses.value.expenses = updated_expenses; - is_open.value = false; - } catch (err) { - setErrorFrom(err); - } finally { - is_loading.value = false; - } - }; - const close = () => { error.value = null; is_open.value = false; }; + const getPayPeriodExpensesByEmployeeEmail = async (employee_email: string): Promise => { + is_loading.value = true; + error.value = null; + + try { + const expenses = await timesheetService.getExpensesByPayPeriodAndEmployeeEmail( + encodeURIComponent(employee_email), + encodeURIComponent(timesheet_store.pay_period.pay_year), + encodeURIComponent(timesheet_store.pay_period.pay_period_no), + ); + pay_period_expenses.value = expenses; + } catch (err: unknown) { + if (typeof err === 'object') { + const error = err as GenericApiError; + const status_code: number = error.status_code ?? 500; + // const data = error.context ?? ''; + // error.value = data.message || data.error || err.message; + + throw new ExpensesApiError({ + status_code, + // error_code: data.error_code, + // message: data.message || data.error || err.message, + // context: data.context, + }); + } + + } finally { + is_loading.value = false; + } + }; + + const upsertOrDeleteExpensesByEmployeeEmail = async (employee_email: string, date: string, expense: UpsertExpense): Promise => { + is_loading.value = true; + error.value = null; + + try { + const updated_expenses = await timesheetService.upsertOrDeleteExpensesByPayPeriodAndEmployeeEmail( + encodeURIComponent(employee_email), + encodeURIComponent(date), + expense, + ); + console.log('updated expenses received: ', updated_expenses) + pay_period_expenses.value.expenses = updated_expenses; + } catch (err) { + // setErrorFrom(err); + console.log('error doing some expense thing: ', err) + } finally { + is_loading.value = false; + } + }; + return { is_open, is_loading, + mode, pay_period_expenses, + pay_period_expenses_totals, current_expense, initial_expense, error, diff --git a/src/stores/shift-store.ts b/src/stores/shift-store.ts index c5b33c3..1dac9be 100644 --- a/src/stores/shift-store.ts +++ b/src/stores/shift-store.ts @@ -3,8 +3,7 @@ import { defineStore } from "pinia"; import { unwrapAndClone } from "src/utils/unwrap-and-clone"; import { timesheetService } from "src/modules/timesheets/services/timesheet-service"; import { useTimesheetStore } from "src/stores/timesheet-store"; -import { default_shift, type UpsertAction, type Shift, UpsertShift } from "src/modules/timesheets/models/shift.models"; -import { GenericApiError } from "src/modules/timesheets/models/expense.validation"; +import { default_shift, type UpsertAction, type Shift, type UpsertShift } from "src/modules/timesheets/models/shift.models"; export const useShiftStore = defineStore('shift', () => { const is_open = ref(false); @@ -31,7 +30,7 @@ export const useShiftStore = defineStore('shift', () => { open('update', date, shift, unwrapAndClone(shift)); }; - const openDelete = (date: string, shift: any) => { + const openDelete = (date: string, shift: Shift) => { open('delete', date, default_shift, shift); } @@ -48,17 +47,18 @@ export const useShiftStore = defineStore('shift', () => { const encoded_date = encodeURIComponent(current_shift.value.date); try { - const result = await timesheetService.upsertOrDeletePayPeriodShifts(encoded_email, encoded_date, [ upsert_shift, ]); + const result = await timesheetService.upsertOrDeleteShiftsByDateAndEmployeeEmail(encoded_email, [ upsert_shift, ], encoded_date); timesheet_store.pay_period_details = result; - } catch (err: any) { - const status_code: number = err?.response?.status ?? 500; - const data = err?.response?.data ?? {}; - throw new GenericApiError({ - status_code, - error_code: data.error_code, - message: data.message || data.error || err.message, - context: data.context, - }); + } catch (err) { + console.log('error doing thing: ', err) + // const status_code: number = err?.response?.status ?? 500; + // const data = err?.response?.data ?? {}; + // throw new GenericApiError({ + // status_code, + // error_code: data.error_code, + // message: data.message || data.error || err.message, + // context: data.context, + // }); } finally { close(); } diff --git a/src/stores/timesheet-store.ts b/src/stores/timesheet-store.ts index 00c09ed..b93a33d 100644 --- a/src/stores/timesheet-store.ts +++ b/src/stores/timesheet-store.ts @@ -4,93 +4,91 @@ import { withLoading } from 'src/utils/store-helpers'; import { timesheetApprovalService } from 'src/modules/timesheet-approval/services/timesheet-approval-service'; import { timesheetService } from 'src/modules/timesheets/services/timesheet-service'; import { default_pay_period_overview, type PayPeriodOverview } from "src/modules/timesheet-approval/models/pay-period-overview.models"; -import { default_pay_period, type PayPeriod } from 'src/modules/shared/types/pay-period-interface'; +import { default_pay_period, type PayPeriod } from 'src/modules/shared/models/pay-period.models'; import { default_pay_period_details, type PayPeriodDetails } from 'src/modules/timesheets/models/pay-period-details.models'; -import { TimesheetApprovalCSVReportFilters } from 'src/modules/timesheet-approval/models/timesheet-approval-csv-report.models'; +import type { TimesheetApprovalCSVReportFilters } from 'src/modules/timesheet-approval/models/timesheet-approval-csv-report.models'; export const useTimesheetStore = defineStore('timesheet', () => { const is_loading = ref(false); const pay_period = ref(default_pay_period); - const pay_period_overviews = ref([ default_pay_period_overview, ]); + const pay_period_overviews = ref([default_pay_period_overview,]); const current_pay_period_overview = ref(default_pay_period_overview); const pay_period_details = ref(default_pay_period_details); const pay_period_report = ref(); - const is_calendar_limit = computed( ()=> + const is_calendar_limit = computed(() => pay_period.value.pay_year === 2024 && pay_period.value.pay_period_no <= 1 ); - const getPayPeriodByDateOrYearAndNumber = (date_or_year: string | number, period_number?: number): Promise => { - return withLoading( is_loading, async () => { - try { - let response; + const getPayPeriodByDateOrYearAndNumber = async (date_or_year: string | number, period_number?: number): Promise => { + is_loading.value = true; - if (typeof date_or_year === 'string') { - response = await timesheetService.getPayPeriodByDate(date_or_year); - return true; - } - else if ( typeof date_or_year === 'number' && period_number ) { - response = await timesheetService.getPayPeriodByYearAndPeriodNumber(date_or_year, period_number); - return true; - } - else response = default_pay_period; - - pay_period.value = response; - return false; - } catch(error){ - console.error('Could not get current pay period: ', error ); - pay_period.value = default_pay_period; - pay_period_overviews.value = [ default_pay_period_overview, ]; - //TODO: More in-depth error-handling here + try { + if (typeof date_or_year === 'string') { + pay_period.value = await timesheetService.getPayPeriodByDate(date_or_year); } + else if (typeof date_or_year === 'number' && period_number) { + pay_period.value = await timesheetService.getPayPeriodByYearAndPeriodNumber(date_or_year, period_number); + } + else pay_period.value = default_pay_period; + is_loading.value = false; + + return true; + } catch (error) { + console.error('Could not get current pay period: ', error); + pay_period.value = default_pay_period; + pay_period_overviews.value = [default_pay_period_overview,]; + //TODO: More in-depth error-handling here + is_loading.value = false; return false; - }); - }; - - const getPayPeriodDetailsByEmployeeEmail = async (employee_email: string) => { - return withLoading( is_loading, async () => { - try { - const response = await timesheetService.getPayPeriodDetailsByPayPeriodAndEmployeeEmail( - pay_period.value.pay_year, - pay_period.value.pay_period_no, - employee_email - ); - pay_period_details.value = response; - - return true; - } catch (error) { - console.error('There was an error retrieving timesheet details for this employee: ', error); - // TODO: More in-depth error-handling here - } - - pay_period_details.value = default_pay_period_details; - return false; - }); + } }; const getPayPeriodOverviewsBySupervisorEmail = async (pay_year: number, period_number: number, supervisor_email: string): Promise => { - return withLoading( is_loading, async () => { - try { - const response = await timesheetApprovalService.getPayPeriodOverviewsBySupervisorEmail( pay_year, period_number, supervisor_email ); - pay_period_overviews.value = response; - return true; - } catch (error) { - console.error('There was an error retrieving Employee Pay Period overviews: ', error); - pay_period_overviews.value = [ default_pay_period_overview, ]; - // TODO: More in-depth error-handling here - } + is_loading.value = true; + try { + const response = await timesheetApprovalService.getPayPeriodOverviewsBySupervisorEmail(pay_year, period_number, supervisor_email); + pay_period_overviews.value = response.employees_overview; + is_loading.value = false; + + return true; + } catch (error) { + console.error('There was an error retrieving Employee Pay Period overviews: ', error); + pay_period_overviews.value = [default_pay_period_overview,]; + // TODO: More in-depth error-handling here + is_loading.value = false; + return false; - }); + } + }; + + const getPayPeriodDetailsByEmployeeEmail = async (employee_email: string) => { + is_loading.value = true; + try { + const response = await timesheetService.getPayPeriodDetailsByPayPeriodAndEmployeeEmail( + pay_period.value.pay_year, + pay_period.value.pay_period_no, + employee_email + ); + pay_period_details.value = response; + console.log('pay period details: ', response, pay_period_details.value.employee_full_name) + is_loading.value = false; + } catch (error) { + console.error('There was an error retrieving timesheet details for this employee: ', error); + // TODO: More in-depth error-handling here + pay_period_details.value = default_pay_period_details; + is_loading.value = false; + } }; const getPayPeriodReportByYearAndPeriodNumber = async (year: number, period_number: number, report_filters?: TimesheetApprovalCSVReportFilters) => { - return withLoading( is_loading, async () => { + return withLoading(is_loading.value, async () => { try { const response = await timesheetApprovalService.getPayPeriodReportByYearAndPeriodNumber( - year, - period_number, + year, + period_number, report_filters ); pay_period_report.value = response; @@ -105,10 +103,10 @@ export const useTimesheetStore = defineStore('timesheet', () => { }); }; - return { + return { is_loading, is_calendar_limit, - pay_period, + pay_period, pay_period_overviews, current_pay_period_overview, pay_period_details, diff --git a/src/utils/normalize-object.ts b/src/utils/normalize-object.ts index 2b1c6d4..dfb6316 100644 --- a/src/utils/normalize-object.ts +++ b/src/utils/normalize-object.ts @@ -1,8 +1,8 @@ export type Normalizer = { - [K in keyof T]: (val: unknown) => T[K]; + [K in keyof T]: (_val: unknown) => T[K]; }; -export const normalizeObject = (raw: any, schema: Normalizer): T => { +export const normalizeObject = (raw: Partial>, schema: Normalizer): T => { const result = {} as T; for (const key in schema) { result[key] = schema[key](raw[key]); diff --git a/src/utils/store-helpers.ts b/src/utils/store-helpers.ts index 99c2daf..e38831c 100644 --- a/src/utils/store-helpers.ts +++ b/src/utils/store-helpers.ts @@ -1,5 +1,6 @@ export const withLoading = async ( loading_state: boolean, fn: () => Promise ) => { loading_state = true; + try { return await fn(); } finally { diff --git a/src/utils/to-qselect-options.ts b/src/utils/to-qselect-options.ts index c12619b..15c670c 100644 --- a/src/utils/to-qselect-options.ts +++ b/src/utils/to-qselect-options.ts @@ -1,6 +1,6 @@ export const toQSelectOptions = (values: readonly T[], i18n_domain?: string): { label: string; value: T }[] => { return values.map(value => ({ label: ((i18n_domain ?? "") + value).toString(), - value: value as T + value: value })); }; \ No newline at end of file diff --git a/src/utils/unwrap-and-clone.ts b/src/utils/unwrap-and-clone.ts index 779a358..73cc45a 100644 --- a/src/utils/unwrap-and-clone.ts +++ b/src/utils/unwrap-and-clone.ts @@ -7,8 +7,8 @@ export const unwrapAndClone = (obj: T): T => { const raw = isProxy(obj) ? toRaw(obj) : obj; // Use structuredClone if available (handles Dates, Maps, Sets, circulars) - if (typeof (globalThis as any).structuredClone === "function") { - return (globalThis as any).structuredClone(raw); + if (typeof globalThis.structuredClone === "function") { + return globalThis.structuredClone(raw); } // Fallback for older environments (loses Dates, Sets, Maps)
{{$t('timesheet_approvals.print_report.type')}}
+ {{ $t('timesheet_approvals.print_report.type') }}