Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/components/employee-beta/notes/EmployeeNotes.vue
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ const emitter = inject('emitter');

// import note pages
import GeneralNotes from './pages/GeneralNotes.vue';
import TrainingNotes from './pages/TrainingNotes.vue';
import PersonalNotes from './pages/PersonalNotes.vue';
import CareerNotes from './pages/CareerNotes.vue';
import KudoNotes from './pages/KudoNotes.vue';
Expand Down Expand Up @@ -181,6 +182,9 @@ const notesTemplate = {
},
kudos: {
custom: []
},
training: {
expenses: {}
}
}
};
Expand All @@ -196,6 +200,11 @@ const menuItems = ref([
key: 'general',
component: shallowRef(GeneralNotes)
},
{
name: 'Training',
key: 'training',
component: shallowRef(TrainingNotes)
},
{
name: 'Career',
key: 'career',
Expand Down
178 changes: 178 additions & 0 deletions src/components/employee-beta/notes/pages/TrainingNotes.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
<template>
<v-col>
<v-progress-linear v-if="!filteredTrainings" indeterminate />
<div v-else>
<div class="mb-4">
<strong>Training Hour Conversions:</strong> ${{ trainingHours.pending.toFixed(2) }} pending, ${{ trainingHours.reimbursed.toFixed(2) }} reimbursed
</div>
<v-autocomplete
:items="categories"
v-model="filters.categories"
label="Filter by category"
variant="outlined"
density="compact"
multiple
chips
closable-chips
/>
<div>
<v-row>
<v-col cols="3" class="font-weight-bold">Purchase Date</v-col>
<v-col cols="3" class="font-weight-bold">Category</v-col>
<v-col cols="6" class="font-weight-bold">Description</v-col>
</v-row>
<p v-if="filteredTrainings.length === 0" class="mt-4">No trainings found.</p>
<v-row v-else v-for="training in filteredTrainings" :key="training.id">
<v-col cols="3">{{ format(training.purchaseDate, null, 'MMM DD, YYYY') }}</v-col>
<v-col cols="3">{{ training.category ?? 'None' }}</v-col>
<v-col cols="6" v-if="editing[training.id]" class="py-1">
<v-textarea
v-model="notes.expenses[training.id]"
variant="outlined"
density="compact"
class="pa-0"
rows="1"
append-inner-icon="mdi-content-save"
@click:append-inner="editing[training.id] = false"
@blur="editing[training.id] = false"
@keydown.enter="editing[training.id] = false"
hide-details
auto-grow
autofocus
/>
</v-col>
<v-col cols="6" v-else @click="editDesc(training)" :class="(getDesc(training)?.modified ? 'font-italic' : '') + ' editable-desc'">
{{ getDesc(training)?.text }}
</v-col>
</v-row>
</div>
</div>
</v-col>
</template>

<script setup>
/**
* TODO:
* - [x] make for employee, not user (oops)
* - [x] category filter with auto to what they likely want
* - [x] show training hour conversions
* - [x] summary of training hours conversions
* - [ ] notes section defaulting to desc but editable (do not edit expense, just notes)
*/

/*

{
"id": "aa22c281-f87f-4e93-ab62-83274f38b08a",
"employeeId": "21769931-4379-4902-ac13-b1f52069cf43",
"createdAt": "2025-11-14",
"expenseTypeId": "00670837-e7b9-4198-8ed1-f6146d821bfc",
"description": "bjnk",
"purchaseDate": "2025-11-05T05:00:00.000Z",
"showOnFeed": true,
"state": "CREATED",
"cost": 9,
"category": "Certifications,",
"receipt": [ "Portal_Report_(Ragnarok_Technologies).xlsx" ]
}

*/

import { ref, computed, onMounted } from 'vue';
import { useStore } from 'vuex';
import { updateStoreUser } from '@/utils/storeUtils';
import { format, difference } from '@/shared/dateUtils';
import api from '@/shared/api';
import { EXPENSE_STATES } from '@/shared/expenseUtils';

const props = defineProps(['modelValue', 'user']);
const notes = ref(props.modelValue);
const categories = ref([]);
const filters = ref({
categories: ['Certifications', 'Certifications,', 'Training', 'Exchange for Training Hours', 'Conferences']
})
const store = useStore();
let trainings = ref(null);
const trainingHours = ref({ pending: 0, reimbursed: 0 })
const editing = ref({});

onMounted(async () => {
// fetch all data
let { id } = props.user;
let [expenses, expenseTypes] = await Promise.all([api.getAllEmployeeExpenses(id), api.getEmployeeExpenseTypes(id)]);
// get all training expense type IDs
let trainingIds = new Set();
let allCategory = new Set();
for (let et of expenseTypes) {
if (et.budgetName.toLowerCase().includes('training')) {
trainingIds.add(et.id);
for (let c of et.categories || []) {
// add to category list
allCategory.add(c.name);
}
}
}

// Remove non-existant categories from auto-filled categories
for (let i = 0; i < filters.value.categories.length; i++)
if (!allCategory.has(filters.value.categories[i]))
filters.value.categories.splice(i--, 1);

// get exchanges for training hours
for (let e of expenses) {
if (e.category?.toLowerCase() === 'exchange for training hours') {
if (e.state === EXPENSE_STATES.REIMBURSED) trainingHours.value.reimbursed += e.cost;
else trainingHours.value.pending += e.cost
}
}

// get categories
categories.value = Array.from(allCategory);
// get all expenses with a training ET ID
trainings.value = expenses.filter((e) => trainingIds.has(e.expenseTypeId));
trainings.value = trainings.value.sort((a, b) => difference(b.purchaseDate, a.purchaseDate, 'day'));
});

function editDesc(training) {
notes.value.expenses[training.id] ??= training.description;
editing.value[training.id] = true;
}

function getDesc(training) {
let note = notes.value.expenses[training.id];
if (note && note !== '') return { text: note, modified: true };
if (!training.description || training.description === '') return { text: 'No description', modified: true };
return { text: training.description, modifed: false };
}

const filteredTrainings = computed(() => {
if (!trainings.value) return null;

let filtered = [];
let add = (item) => filtered.push(item);
for (let t of trainings.value) {
// categories filter
if (!filters.value.categories?.length) add(t);
for (let c of filters.value.categories || []) {
if (t.category === c) {
add(t);
continue;
}
}
}

return filtered;
});
</script>

<style scoped>
.editable-desc:hover {
cursor: pointer;
background-color: #eee;
border-radius: 0.7em;
}
input[type="text"].v-field__input {
color: red !important;
padding-left: 0;
}
</style>