UNCLASSIFIED

Commit ad34b4ea authored by graham.smith's avatar graham.smith
Browse files

Merge branch 'LB-211' into 'master'

Lb 211

See merge request platform-one/products/bullhorn/launchboard-fe!114
parents 39d864e9 c5992656
...@@ -99,10 +99,6 @@ export default { ...@@ -99,10 +99,6 @@ export default {
} }
} }
.v-select__selections {
line-height: 1.25rem;
}
.v-image { .v-image {
&.h-flip { &.h-flip {
transform: rotate(180deg); transform: rotate(180deg);
......
...@@ -63,7 +63,6 @@ ...@@ -63,7 +63,6 @@
</v-btn> </v-btn>
<v-btn <v-btn
color="primary" color="primary"
text
href="https://jira.il2.dso.mil/servicedesk/customer/portal/1/create/137" href="https://jira.il2.dso.mil/servicedesk/customer/portal/1/create/137"
target="_blank" target="_blank"
> >
......
...@@ -3,16 +3,7 @@ ...@@ -3,16 +3,7 @@
:show-dialog="showDialog" :show-dialog="showDialog"
:dialog-title="editMode ? `Edit team` : `Add a new team`" :dialog-title="editMode ? `Edit team` : `Add a new team`"
> >
<v-form <v-form v-model="valid" id="add-team">
v-model="valid"
id="add-team"
:style="{
padding: '2em',
'margin-bottom': '2em',
paddingTop: '0',
height: '425px',
}"
>
<v-text-field <v-text-field
label="Team name" label="Team name"
v-model="toAdd.name" v-model="toAdd.name"
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
hide-selected hide-selected
aria-required="true" aria-required="true"
@change="modelChanged" @change="modelChanged"
placeholder="Class name" placeholder="Course name"
@click:prepend="setInputElementFocus($refs.courseAutocomplete)" @click:prepend="setInputElementFocus($refs.courseAutocomplete)"
class="course-select-component" class="course-select-component"
> >
......
<template>
<BaseDialog :show-dialog="showDialog" dialog-title="Add Student to Course">
<v-card-text class="d-flex flex-column justify-end pt-7 pb-7">
<CourseNameDate
:course-name="course.name"
:course-date="course.startDate"
/>
<div class="mt-3"></div>
<UserSelectForCourse
:selected-members="courseMembers"
:are-no-available-seats="
availableSeatsInCourse(
this.course.capacity,
this.courseMembers,
this.course.registrations
) < 0
"
:available-seats-in-course="
availableSeatsInCourse(
this.course.capacity,
this.courseMembers,
this.course.registrations
)
"
:update-course-members="updateCourseMembers"
:is-course-selected="true"
/>
</v-card-text>
<template v-slot:actions>
<v-btn color="secondary" :disabled="state.isAddingBusy" @click="cancel()">
Cancel
</v-btn>
<v-btn
:disabled="!canSubmit"
color="primary"
:loading="state.isAddingBusy"
@click="addSelectedUsersToSelectedCourses"
>
Add To Course
</v-btn>
</template>
</BaseDialog>
</template>
<script>
import BaseDialog from "@/components/Dialogs/BaseDialog";
import UserSelectForCourse from "@/components/Dialogs/DialogComponents/UserSelectForCourse";
import CourseNameDate from "@/components/Dialogs/DialogComponents/CourseNameDate";
import CourseService from "@/api/services/training";
import { SET_ERROR_MESSAGE, SET_ERROR_DIALOG } from "@/store/mutation-types";
import { availableSeatsInCourse, canAddStudentsToCourse } from "@/utils/course";
export default {
name: "AddStudentsToCourseDialog",
components: { BaseDialog, UserSelectForCourse, CourseNameDate },
props: {
showDialog: Boolean,
courseId: Number,
},
methods: {
async fetchDebounced() {
this.course = await CourseService.getCourse(this.courseId);
const result = await CourseService.getCourseRegistrationsById(
this.course.id
);
this.course.registrations = result.registrations;
},
availableSeatsInCourse,
canAddStudentsToCourse,
async addSelectedUsersToSelectedCourses() {
this.isAddingBusy = true;
try {
await CourseService.addStudents(this.courseId, this.courseMembers);
this.$emit("update-students");
} catch (error) {
console.error("!add courses error!", error);
this.$store.commit(SET_ERROR_MESSAGE, error);
this.$store.commit(SET_ERROR_DIALOG, true);
} finally {
this.$refs.courseSelect && this.$refs.courseSelect.clear();
this.isAddingBusy = false;
this.isAdding = false;
this.course = { name: "", startDate: "" };
this.updateCourseMembers([]);
this.$emit("close-dialog");
}
},
updateCourseMembers(members) {
this.courseMembers = members;
},
cancel() {
this.$emit("close-dialog");
},
},
data() {
return {
state: {
isAddingBusy: false,
},
course: {
name: "",
startDate: "",
},
courseMembers: [],
};
},
computed: {
canSubmit() {
return this.canAddStudentsToCourse(
true,
this.course.capacity,
this.courseMembers,
this.course.registrations
);
},
},
async created() {
this.fetchDebounced();
},
};
</script>
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
import { setInputElementFocus } from "@/utils/events"; import { setInputElementFocus } from "@/utils/events";
export default { export default {
name: "CourseSelectDialog", name: "BaseDialog",
props: { props: {
dialogTitle: String, dialogTitle: String,
addBusy: Boolean, addBusy: Boolean,
...@@ -70,14 +70,13 @@ export default { ...@@ -70,14 +70,13 @@ export default {
.v-card__actions { .v-card__actions {
bottom: 0; bottom: 0;
position: absolute;
width: 100%; width: 100%;
text-align: center; text-align: center;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
margin-bottom: 40px; padding-bottom: 40px !important;
} }
.v-card__actions .v-btn:last-child { .v-card__actions .v-btn:last-child {
......
<template>
<div class="course-name-date d-flex flex-row justify-space-between">
<span class="course-name font-weight-bold">{{ courseName }}</span
><span class="course-date">{{ courseDate }}</span>
</div>
</template>
<script>
export default {
name: "CourseNameDate",
components: {},
props: {
courseName: {
type: String,
default: "No Course Name",
},
courseDate: {
type: String,
default: "No Course Date",
},
},
data() {
return {};
},
};
</script>
<style scoped lang="scss">
.course-name-date {
padding: 0 3em;
}
.course-name {
font-size: 15px;
}
.course-date {
font-size: 15px;
color: white;
}
.theme--light {
.course-name {
color: black;
}
.course-date {
color: black;
}
}
</style>
<template>
<div>
<UserSelect
:force-error="areNoAvailableSeats"
:force-error-message="
areNoAvailableSeats ? ['No more available seats'] : []
"
multiple
ref="userSelect"
class="course-select-dialog-user-select"
v-on:input="updateCourseMembers"
v-bind:value="selectedMembers"
/>
<label
class="text-left w-100 d-block available-seats-label"
v-if="
isCourseSelected &&
availableSeatsInCourse === 0 &&
selectedMembers.length === 0
"
>This course is full. You won't be able to add any students.</label
>
<label
class="text-left w-100 d-block available-seats-label"
v-else-if="
isCourseSelected &&
availableSeatsInCourse === 0 &&
selectedMembers.length > 0
"
>After you add the selected user{{
selectedMembers.length > 1 ? "s" : ""
}}
this course will be full</label
>
<label
class="text-left w-100 d-block available-seats-label"
v-else-if="selectedMembers.length === 0"
>No users have been selected</label
>
<label
v-else-if="isCourseSelected && !areNoAvailableSeats"
class="text-left w-100 d-block available-seats-label"
>You will have
{{ availableSeatsInCourse }}
available seat reservation{{
availableSeatsInCourse === 1 ? "" : "s"
}}
remaining after adding {{ selectedMembers.length }} to the course
</label>
</div>
</template>
<script>
import UserSelect from "@/components/UserSelect";
export default {
name: "UserSelectForCourse",
components: { UserSelect },
props: {
selectedMembers: {
type: Array,
default: () => [],
},
areNoAvailableSeats: {
type: Boolean,
default: false,
},
availableSeatsInCourse: {
type: Number,
default: 0,
},
courseStartDate: {
type: String,
default: "Select Course",
},
updateCourseMembers: {
type: Function,
default: () => {},
},
isCourseSelected: {
type: Boolean,
default: false,
},
},
data() {
return {
state: {
isAddingBusy: false,
},
};
},
};
</script>
<style>
.available-seats-label {
width: 81% !important;
display: block !important;
position: relative !important;
left: 33px !important;
bottom: 11px !important;
}
</style>
<template> <template>
<BaseDialog :show-dialog="showDialog" dialog-title="Add To Course"> <BaseDialog :show-dialog="showDialog" dialog-title="Add To Course">
<v-card-text :style="{ height: '400px' }"> <v-card-text id="select-add-student-dialog-text">
<CourseSelect <CourseSelect
v-on:input="updateCourseSelected" v-on:input="updateCourseSelected"
ref="courseSelect" ref="courseSelect"
v-model="
state.addToSelectedCourseFeature.data.coursesToAddSelectedUsersTo
"
multiple multiple
/> />
<v-text-field <v-text-field
ref="startDate" ref="startDate"
v-model="courseStartDate" v-model="course.startDate"
prepend-icon="mdi-calendar-today" prepend-icon="mdi-calendar-today"
label="Start Date" label="Start Date"
hint="* Required" hint="* Required"
:persistent-hint="true" :persistent-hint="true"
> readonly
</v-text-field> ></v-text-field>
<UserSelect <div class="mt-5"></div>
:force-error="areNoAvailableSeats" <UserSelectForCourse
:force-error-message=" :are-no-available-seats="
areNoAvailableSeats ? ['No more available seats'] : [] availableSeatsInCourse(
this.course.capacity,
this.courseMembers,
this.courseRegistrations
) < 0
" "
multiple :available-seats-in-course="
ref="userSelect" availableSeatsInCourse(
class="course-select-dialog-user-select" this.course.capacity,
v-on:input="updateCourseMembers" this.courseMembers,
v-bind:value="internalSelectedMembers" this.courseRegistrations
/> )
<label
v-if="isCourseSelected && !areNoAvailableSeats"
class="text-left w-100 d-block text--white available-seats-label"
style="
width: 81%;
display: block;
position: relative;
left: 33px;
bottom: 11px;
" "
>You have :course-start-date="course.startDate"
{{ availableSeatsInCourse - internalSelectedMembers.length }} :update-course-members="updateCourseMembers"
available seat reservations</label :is-course-selected="isCourseSelected"
> :selected-members="courseMembers"
/>
</v-card-text> </v-card-text>
<template v-slot:actions> <template v-slot:actions>
<v-btn <v-btn color="secondary" :disabled="isAddingBusy" @click="cancel()">
color=""
class="primary"
:disabled="state.isAddingBusy"
text
@click="cancel()"
>
Cancel Cancel
</v-btn> </v-btn>
<v-btn <v-btn
:disabled=" :disabled="!canSubmit"
!isCourseSelected ||
!internalSelectedMembers.length ||
areNoAvailableSeats
"
color="primary" color="primary"
:loading="state.isAddingBusy" :loading="isAddingBusy"
text
@click="addSelectedUsersToSelectedCourses()" @click="addSelectedUsersToSelectedCourses()"
> >
Add To Course Add To Course
...@@ -78,10 +59,11 @@ import CourseSelect from "@/components/CourseSelect"; ...@@ -78,10 +59,11 @@ import CourseSelect from "@/components/CourseSelect";
import { setInputElementFocus } from "@/utils/events"; import { setInputElementFocus } from "@/utils/events";
import { SET_ERROR_MESSAGE, SET_ERROR_DIALOG } from "@/store/mutation-types"; import { SET_ERROR_MESSAGE, SET_ERROR_DIALOG } from "@/store/mutation-types";
import CourseService from "@/api/services/training"; import CourseService from "@/api/services/training";
import UserSelect from "@/components/UserSelect"; import UserSelectForCourse from "@/components/Dialogs/DialogComponents/UserSelectForCourse";
import { availableSeatsInCourse, canAddStudentsToCourse } from "@/utils/course";
export default { export default {
name: "CourseSelectDialog", name: "SelectAddStudentsToCourseDialog",
props: { props: {
addBusy: Boolean, addBusy: Boolean,
showDialog: Boolean, showDialog: Boolean,
...@@ -94,70 +76,57 @@ export default { ...@@ -94,70 +76,57 @@ export default {
}, },
components: { components: {
BaseDialog, BaseDialog,
UserSelect,
CourseSelect, CourseSelect,
UserSelectForCourse,
}, },
data: () => ({ data: () => ({
course: { name: "", startDate: "" },
courseRegistrations: [],
courseMembers: [],
isUserSelectError: false, isUserSelectError: false,
userSelectErrorMessage: [], userSelectErrorMessage: [],
availableSeatsInCourse: 0, isAdding: false,
internalSelectedMembers: [], isAddingDuplicate: false,
courseStartDate: "", isAddingBusy: false,
state: {
/* Feature: Adding User To Course
controls the state of the adding user to selected course dialog */
addToSelectedCourseFeature: {
//for controlling dialog
ui: {
//dialog open/close
isAdding: false,
//allows the user to click the button to submit
isAddingDuplicate: false,
//loading
isAddingBusy: false,
},
//data for CourseSelect component
data: {
coursesToAddSelectedUsersTo: null,
},
},
},
}), }),
methods: { methods: {
canAddStudentsToCourse,
availableSeatsInCourse,
updateCourseMembers(members) { updateCourseMembers(members) {
this.internalSelectedMembers = members; this.courseMembers = members;
this.$emit("update-selected-members", members);
}, },
async updateCourseSelected(model) { async updateCourseSelected(courses) {
const course = model[0]; if (Array.isArray(courses) && courses.length > 0) {
if (course) { this.course = courses[0];
this.courseStartDate = course.startDate; if (!isNaN(this.course.id)) {
const courseRegistrations = await CourseService.getCourseRegistrationsById( const result = await CourseService.getCourseRegistrationsById(
course.id this.course.id
); );
const capacity = course.capacity, this.courseRegistrations = result.registrations;
registrations = courseRegistrations.registrations; }
const seatsLeft = capacity - registrations.length; } else {
this.availableSeatsInCourse = seatsLeft; this.course = { name: "", startDate: "" };
} }
}, },
async addSelectedUsersToSelectedCourses() { async addSelectedUsersToSelectedCourses() {
this.state.addToSelectedCourseFeature.ui.isAddingBusy = true; this.isAddingBusy = true;
try { try {
await CourseService.addStudentsToCourses( await CourseService.addStudentsToCourses(
this.state.addToSelectedCourseFeature.data [this.course],
.coursesToAddSelectedUsersTo,
this.selectedMembers this.selectedMembers
); );
this.$emit("update-students");
} catch (error) { } catch (error) {
console.error("!add courses error!", error); console.error("!add courses error!", error);
this.$store.commit(SET_ERROR_MESSAGE, error); this.$store.commit(SET_ERROR_MESSAGE, error);
this.$store.commit(SET_ERROR_DIALOG, true); this.$store.commit(SET_ERROR_DIALOG, true);
} finally { } finally {
this.$refs.courseSelect && this.$refs.courseSelect.clear(); this.$refs.courseSelect && this.$refs.courseSelect.clear();
this.state.addToSelectedCourseFeature.ui.isAddingBusy = false; this.isAddingBusy = false;
this.state.addToSelectedCourseFeature.ui.isAdding = false; this.isAdding = false;
this.state.addToSelectedCourseFeature.data.coursesToAddSelectedUsersTo = null; this.course = { name: "", startDate: "" };
this.selectedMembers = []; this.updateCourseMembers([]);
this.$emit("close-dialog"); this.$emit("close-dialog");
} }
}, },
...@@ -173,33 +142,17 @@ export default { ...@@ -173,33 +142,17 @@ export default {
setInputElementFocus, setInputElementFocus,
}, },
computed: { computed: {
areNoAvailableSeats() { canSubmit() {
return ( return this.canAddStudentsToCourse(
this.isCourseSelected && this.isCourseSelected,
this.availableSeatsInCourse - this.internalSelectedMembers.length < 0 this.course.capacity,
this.courseMembers,
this.course.registrations
); );
}, },
isCourseSelected() { isCourseSelected() {
return ( return this.course.name !== "";
Array.isArray(
this.state.addToSelectedCourseFeature.data.coursesToAddSelectedUsersTo
) &&
this.state.addToSelectedCourseFeature.data.coursesToAddSelectedUsersTo
.length > 0
);
},
areMultipleCoursesSelected() {
return (
this.isCourseSelected &&
this.state.addToSelectedCourseFeature.data.coursesToAddSelectedUsersTo
.length > 1
);
}, },
}, },
}; };
</script> </script>
<style scoped>
.course-select-dialog-user-select {
margin-top: 40px;
}
</style>
...@@ -139,6 +139,7 @@ import { minimumWait } from "@/utils/timing"; ...@@ -139,6 +139,7 @@ import { minimumWait } from "@/utils/timing";
const minCheckBoxWaitTime = 500; // ms const minCheckBoxWaitTime = 500; // ms
export default { export default {
name: "TrainingAttendance", name: "TrainingAttendance",
components: {},
props: { props: {
trainingCourse: { trainingCourse: {
type: Object, type: Object,
...@@ -231,6 +232,7 @@ export default { ...@@ -231,6 +232,7 @@ export default {
this.$store.commit(SET_ERROR_MESSAGE, errorMessage); this.$store.commit(SET_ERROR_MESSAGE, errorMessage);
} }
this.fetchDebounced(); this.fetchDebounced();
this.$emit("removed-student");
}, },
sendEmailToStudents() { sendEmailToStudents() {
const userEmails = this.selectedUsers.map((item) => item.user.email); const userEmails = this.selectedUsers.map((item) => item.user.email);
......
...@@ -133,6 +133,13 @@ body { ...@@ -133,6 +133,13 @@ body {
} }
} }
.v-select__selections {
line-height: 1.5rem;
& > .v-select__selection--comma {
margin-bottom: 5px;
}
}
@include xs { @include xs {
h1 { h1 {
font-size: 48px; font-size: 48px;
...@@ -540,3 +547,9 @@ body { ...@@ -540,3 +547,9 @@ body {
word-break: normal; word-break: normal;
} }
} }
.v-dialog .v-form {
padding: 2em !important;
padding-top: 0 !important;
margin-bottom: 0 !important;
}
export const availableSeatsInCourse = (
capacity,
selectedMembers,
registrations
) => {
if (!Number.isInteger(capacity)) {
return 0;
}
if (Array.isArray(selectedMembers)) {
capacity -= selectedMembers.length;
}
if (Array.isArray(registrations)) {
capacity -= registrations.length;
}
return capacity;
};
export const canAddStudentsToCourse = (
isCourseSelected,
capacity,
selectedMembers,
registrations
) => {
//default to allow
let canAddToCourse = true;
//check if course is selected
if (!isCourseSelected) {
canAddToCourse = false;
}
//check if seats are available
const seatsAvailable = availableSeatsInCourse(
capacity,
selectedMembers,
registrations
);
if (seatsAvailable < 0) {
canAddToCourse = false;
}
//check if selected members is an array
if (!Array.isArray(selectedMembers) || selectedMembers.length <= 0) {
canAddToCourse = false;
}
//return boolean
return canAddToCourse;
};
...@@ -105,15 +105,13 @@ ...@@ -105,15 +105,13 @@
<!-- <!--
COURSE SELECT DIALOG COURSE SELECT DIALOG
--> -->
<CourseSelectDialog <SelectAddStudentsToCourseDialog
:style="{ height: '600px' }"
ref="courseSelectDialog" ref="courseSelectDialog"
v-bind:selected-members="selectedMembers" v-bind:selected-members="selectedMembers"
v-on:update-selected-members="updateSelectedMembers" v-on:update-selected-members="updateSelectedMembers"
v-on:close-dialog="closeDialogAddUsersToCourse" v-on:close-dialog="closeDialogAddUsersToCourse"
:showDialog="state.showAddToCourseDialog" :showDialog="state.showAddToCourseDialog"
/> />
<!-- <!--
COURSE SELECT DIALOG END COURSE SELECT DIALOG END
--> -->
...@@ -126,7 +124,7 @@ ...@@ -126,7 +124,7 @@
:dialog-title="`Add new member to ${team.name}`" :dialog-title="`Add new member to ${team.name}`"
:show-dialog="state.isAdding" :show-dialog="state.isAdding"
> >
<div :style="{ padding: '2em', height: '240px' }"> <v-form>
<UserSelect <UserSelect
ref="userSelect" ref="userSelect"
v-model="membersToAdd" v-model="membersToAdd"
...@@ -140,7 +138,7 @@ ...@@ -140,7 +138,7 @@
), ),
]" ]"
/> />
</div> </v-form>
<template v-slot:actions> <template v-slot:actions>
<v-btn <v-btn
...@@ -276,7 +274,7 @@ ...@@ -276,7 +274,7 @@
</template> </template>
<script> <script>
import Vue from "vue"; import Vue from "vue";
import CourseSelectDialog from "@/components/Dialogs/CourseSelectDialog"; import SelectAddStudentsToCourseDialog from "@/components/Dialogs/SelectAddStudentsToCourseDialog";
import TeamService from "@/api/services/team"; import TeamService from "@/api/services/team";
import UserSelect from "@/components/UserSelect"; import UserSelect from "@/components/UserSelect";
import AddTeam from "@/components/AddTeam"; import AddTeam from "@/components/AddTeam";
...@@ -289,7 +287,7 @@ import BaseDialog from "@/components/Dialogs/BaseDialog"; ...@@ -289,7 +287,7 @@ import BaseDialog from "@/components/Dialogs/BaseDialog";
export default { export default {
components: { components: {
UserSelect, UserSelect,
CourseSelectDialog, SelectAddStudentsToCourseDialog,
AddTeam, AddTeam,
BaseDialog, BaseDialog,
}, },
......
...@@ -20,49 +20,13 @@ ...@@ -20,49 +20,13 @@
> >
Add Student Add Student
</v-btn> </v-btn>
<v-dialog <AddStudentsToCourseDialog
v-model="state.isAddingStudent" ref="addStudentsToCourseDialog"
persistent :show-dialog="state.isAddingStudent"
no-click-animation :course-id="Number.parseInt($route.params.trainingId)"
max-width="500px" v-on:close-dialog="closeAddStudentDialog"
> v-on:update-students="fetchDebounced"
<v-card> />
<v-card-title>
<span class="headline"
>Add new student to {{ currentTrainingCourse.name }}</span
>
</v-card-title>
<v-card-text>
<UserSelect
ref="userSelect"
v-model="studentToAdd"
multiple
required
/>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="secondary"
:disabled="state.isAddingBusy"
@click="cancelAddStudent()"
>
Cancel
</v-btn>
<v-btn
:disabled="state.isAddingDuplicate || !studentToAdd"
color="primary"
:loading="state.isAddingBusy"
text
@click="addStudent()"
>
Add Student
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-col> </v-col>
</v-row> </v-row>
<v-data-table <v-data-table
...@@ -150,15 +114,10 @@ ...@@ -150,15 +114,10 @@
color="primary" color="primary"
> >
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn <v-btn color="primary" @click="startDateMenu = false">
text
color="primary"
@click="startDateMenu = false"
>
Cancel Cancel
</v-btn> </v-btn>
<v-btn <v-btn
text
color="primary" color="primary"
@click=" @click="
$refs.startDateMenu.save( $refs.startDateMenu.save(
...@@ -202,15 +161,10 @@ ...@@ -202,15 +161,10 @@
color="primary" color="primary"
> >
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn <v-btn color="primary" @click="endDateMenu = false">
text
color="primary"
@click="endDateMenu = false"
>
Cancel Cancel
</v-btn> </v-btn>
<v-btn <v-btn
text
color="primary" color="primary"
@click=" @click="
$refs.endDateMenu.save( $refs.endDateMenu.save(
...@@ -265,7 +219,6 @@ ...@@ -265,7 +219,6 @@
<v-btn <v-btn
:disabled="!valid" :disabled="!valid"
color="primary" color="primary"
text
@click="updateCourse" @click="updateCourse"
:loading="loading" :loading="loading"
> >
...@@ -349,10 +302,12 @@ ...@@ -349,10 +302,12 @@
</div> </div>
<TrainingAttendance <TrainingAttendance
ref="trainingAttendance"
class="px-0" class="px-0"
v-if="trainingCourse.id" v-if="trainingCourse.id"
:trainingCourse="trainingCourse" :trainingCourse="trainingCourse"
:reload="trigger" :reload="trigger"
v-on:removed-student="fetchDebounced"
/> />
</v-skeleton-loader> </v-skeleton-loader>
</v-container> </v-container>
...@@ -366,9 +321,10 @@ import TrainingAttendance from "@/components/Training/TrainingAttendance"; ...@@ -366,9 +321,10 @@ import TrainingAttendance from "@/components/Training/TrainingAttendance";
import { SET_ERROR_MESSAGE, SET_ERROR_DIALOG } from "@/store/mutation-types"; import { SET_ERROR_MESSAGE, SET_ERROR_DIALOG } from "@/store/mutation-types";
import UserSelect from "@/components/UserSelect"; import UserSelect from "@/components/UserSelect";
import { DEFAULT_PAGINATION_PARAMS } from "@/config/table-constants"; import { DEFAULT_PAGINATION_PARAMS } from "@/config/table-constants";
import AddStudentsToCourseDialog from "@/components/Dialogs/AddStudentsToCourseDialog";
export default { export default {
components: { TrainingAttendance, UserSelect }, components: { AddStudentsToCourseDialog, TrainingAttendance, UserSelect },
data: () => ({ data: () => ({
selectedInstructors: [], selectedInstructors: [],
loading: false, loading: false,
...@@ -467,11 +423,8 @@ export default { ...@@ -467,11 +423,8 @@ export default {
this.initialLoad = false; this.initialLoad = false;
}, },
methods: { methods: {
cancelAddStudent() { closeAddStudentDialog() {
this.studentToAdd = null;
this.state.isAddingStudent = false; this.state.isAddingStudent = false;
this.state.isAddingDuplicate = false;
this.$refs.userSelect && this.$refs.userSelect.clear();
}, },
async fetchData() { async fetchData() {
try { try {
...@@ -541,6 +494,8 @@ export default { ...@@ -541,6 +494,8 @@ export default {
this.fetchingData = true; this.fetchingData = true;
// delay new call // delay new call
this.fetchData(); this.fetchData();
this.$refs.trainingAttendance.fetchDebounced();
this.$refs.addStudentsToCourseDialog.fetchDebounced();
}, },
async updateCourse() { async updateCourse() {
this.loading = true; this.loading = true;
...@@ -565,27 +520,6 @@ export default { ...@@ -565,27 +520,6 @@ export default {
cancelUpdateCourse() { cancelUpdateCourse() {
this.state.isEditingCourse = false; this.state.isEditingCourse = false;
}, },
async addStudent(student) {
if (!student) {
student = this.studentToAdd;
}
this.state.isAddingBusy = true;
try {
await TrainingService.addStudents(
this.currentTrainingCourse.id,
student
);
this.studentToAdd = student;
this.trigger = this.trigger + 1;
} catch (error) {
console.error("addStudent error!!: ", error);
this.$store.commit(SET_ERROR_MESSAGE, error);
} finally {
this.state.isAddingBusy = false;
this.state.isAddingStudent = false;
this.$refs.userSelect && this.$refs.userSelect.clear();
}
},
}, },
}; };
</script> </script>
......
...@@ -58,7 +58,7 @@ ...@@ -58,7 +58,7 @@
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn color="secondary" text @click="isExporting = false"> <v-btn color="secondary" @click="isExporting = false">
Cancel Cancel
</v-btn> </v-btn>
<v-btn <v-btn
......
import { createLocalVue, shallowMount } from "@vue/test-utils";
import Vuetify from "vuetify";
import Vuex from "vuex";
import AddStudentsToCourseDialog from "@/components/Dialogs/AddStudentsToCourseDialog";
import CourseService from "@/api/services/training";
const localVue = createLocalVue();
localVue.use(Vuex);
const getShallowMount = (vuetify) => {
return shallowMount(AddStudentsToCourseDialog, {
propsData: {
showDialog: true,
courseId: 1,
},
mocks: {
$store: {
state: {
user: {
user: {
permission: "Admin",
},
},
tutorial: {
tooltipName: "test",
},
userPreferences: {
userPreference: {
darkMode: true,
},
},
},
},
},
vuetify,
});
};
const checkHtml = (html, checkText) => {
return html.indexOf(checkText) !== -1;
};
const checkWrapperForHtml = (wrapper, text) => {
const html = wrapper.html();
expect(checkHtml(html, text)).toEqual(true);
};
describe("SelectAddStudentsToCourseDialog", () => {
// vuetify has to be mocked for the v-autocomplete to not crash
let vuetify;
const wrapper = getShallowMount(vuetify);
beforeEach(() => {
vuetify = new Vuetify();
});
afterEach(() => {
jest.clearAllMocks();
});
it("should have basedialog", () => {
checkWrapperForHtml(wrapper, "basedialog-stub");
});
it("dialog title should be add student to course", () => {
checkWrapperForHtml(wrapper, `dialogtitle="Add Student to Course"`);
});
it("should have user select", () => {
checkWrapperForHtml(wrapper, `userselectforcourse-stub`);
});
it("should call addStudents", async () => {
wrapper.vm.courseId = 1;
wrapper.vm.courseMembers = [{ id: 1 }];
CourseService.addStudents = jest.fn();
wrapper.vm.addSelectedUsersToSelectedCourses();
expect(CourseService.addStudents).toHaveBeenCalledTimes(1);
});
it("should call addStudents", async () => {
wrapper.vm.courseId = 1;
wrapper.vm.courseMembers = [{ id: 1 }];
CourseService.addStudents = jest.fn().mockResolvedValue({});
wrapper.vm.addSelectedUsersToSelectedCourses();
expect(CourseService.addStudents).toHaveBeenCalledTimes(1);
});
it("should handle error", async () => {
wrapper.vm.courseId = 1;
wrapper.vm.courseMembers = [{ id: 1 }];
wrapper.vm.$store.commit = jest.fn();
console.error = jest.fn();
CourseService.addStudents = jest.fn().mockRejectedValue({});
await wrapper.vm.addSelectedUsersToSelectedCourses();
expect(CourseService.addStudents).toHaveBeenCalledTimes(1);
expect(console.error).toHaveBeenCalled();
expect(wrapper.vm.$store.commit).toHaveBeenCalled();
});
it("cancel should emit close dialog", async () => {
wrapper.vm.$emit = jest.fn();
wrapper.vm.cancel();
expect(wrapper.vm.$emit).toHaveBeenCalledTimes(1);
});
it("can submit should return false if no available seats or no course members", async () => {
wrapper.vm.course.capacity = 0;
wrapper.vm.courseMembers = [];
wrapper.vm.courseRegistrations = [];
await wrapper.vm.$nextTick();
expect(wrapper.vm.canSubmit).toEqual(false);
});
it("can submit should return true if available seats or and course members", async () => {
wrapper.vm.course.capacity = 10;
wrapper.vm.courseMembers = [{ id: 1 }];
wrapper.vm.courseRegistrations = [];
await wrapper.vm.$nextTick();
expect(wrapper.vm.canSubmit).toEqual(true);
});
it("should call getCourseRegistrationsById when fetchDebouced is called", async () => {
CourseService.getCourseRegistrationsById = jest
.fn()
.mockResolvedValue({ registrations: [] });
CourseService.getCourse = jest.fn().mockResolvedValue({
name: "",
startDate: "2020-01-01",
});
await wrapper.vm.fetchDebounced();
expect(CourseService.getCourseRegistrationsById).toHaveBeenCalled();
expect(CourseService.getCourse).toHaveBeenCalled();
});
});
import { createLocalVue, shallowMount } from "@vue/test-utils";
import Vuetify from "vuetify";
import Vuex from "vuex";
import BaseDialog from "@/components/Dialogs/BaseDialog";
const localVue = createLocalVue();
localVue.use(Vuex);
describe("BaseDialog", () => {
// vuetify has to be mocked for the v-autocomplete to not crash
let vuetify;
beforeEach(() => {
vuetify = new Vuetify();
});
afterEach(() => {
jest.clearAllMocks();
});
it("should find dialog title in html", () => {
const wrapper = shallowMount(BaseDialog, {
propsData: {
dialogTitle: "123456789",
},
vuetify,
});
const html = wrapper.html();
expect(html.indexOf("123456789") !== -1).toBe(true);
});
it("should find v-dialog in html", () => {
const wrapper = shallowMount(BaseDialog, {
propsData: {
dialogTitle: "123456789",
},
vuetify,
});
const html = wrapper.html();
expect(html.indexOf("v-dialog-stub") !== -1).toBe(true);
});
it("should find yoda head in html", () => {
const wrapper = shallowMount(BaseDialog, {
propsData: {
dialogTitle: "123456789",
},
vuetify,
});
const html = wrapper.html();
expect(
html.indexOf(
`<div class="hide-overflow-container"><img src="@/assets/images/logos/YodaHead.png" alt="Yoda Head" class="yoda-head"></div>`
) !== -1
).toBe(true);
});
});
import { createLocalVue, shallowMount } from "@vue/test-utils";
import Vuetify from "vuetify";
import Vuex from "vuex";
import CourseSelectDialog from "@/components/Dialogs/CourseSelectDialog";
const localVue = createLocalVue();
localVue.use(Vuex);
const testUser = { name: "test" };
const testCourse = { name: "test" };
describe("CourseSelectDialog", () => {
// vuetify has to be mocked for the v-autocomplete to not crash
let vuetify;
beforeEach(() => {
vuetify = new Vuetify();
});
afterEach(() => {
jest.clearAllMocks();
});
it("should update internal course members when selected members changes", async () => {
const wrapper = shallowMount(CourseSelectDialog, {
propsData: {
value: [],
selectedMembers: [testUser, testUser, testUser, testUser],
},
vuetify,
});
// wrapper.vm.selectedMembers = [testUser, testUser, testUser, testUser];
await wrapper.vm.$nextTick();
expect(wrapper.props().selectedMembers).toHaveLength(4);
});
it("if areNoAvailableSeats is true then should show error on UserSelect", async () => {
const wrapper = shallowMount(CourseSelectDialog, {
propsData: {
value: [],
selectedMembers: [testUser, testUser, testUser, testUser],
showDialog: true,
},
vuetify,
});
wrapper.vm.state.addToSelectedCourseFeature.data.coursesToAddSelectedUsersTo = [
testCourse,
testCourse,
testCourse,
];
wrapper.vm.availableSeatsInCourse = 0;
await wrapper.vm.$nextTick();
const userSelectWrapper = wrapper.find(".user-select-component");
expect(userSelectWrapper.exists()).toBe(false);
});
it("availableSeatsInCourse should appear in label under UserSelect if course is selected", async () => {
const wrapper = shallowMount(CourseSelectDialog, {
propsData: {
value: [],
selectedMembers: [testUser, testUser, testUser, testUser],
showDialog: true,
},
vuetify,
});
wrapper.vm.state.addToSelectedCourseFeature.data.coursesToAddSelectedUsersTo = [
testCourse,
testCourse,
testCourse,
];
wrapper.vm.availableSeatsInCourse = 10;
await wrapper.vm.$nextTick();
const userSelectWrapper = wrapper.find(".available-seats-label");
const text = userSelectWrapper.html();
expect(text.indexOf("available seat reservations") !== -1).toBe(true);
});
it("internalSelectedMembers should start as empty array", async () => {
const wrapper = shallowMount(CourseSelectDialog, {
propsData: {
value: [],
selectedMembers: [testUser, testUser, testUser, testUser],
showDialog: true,
},
vuetify,
});
wrapper.vm.state.addToSelectedCourseFeature.data.coursesToAddSelectedUsersTo = [
testCourse,
testCourse,
testCourse,
];
wrapper.vm.availableSeatsInCourse = 10;
await wrapper.vm.$nextTick();
expect(
Array.isArray(wrapper.vm.internalSelectedMembers) &&
wrapper.vm.internalSelectedMembers.length === 0
).toBe(true);
});
it("internalSelectedMembers should be equal to selectedMembers", async () => {
const wrapper = shallowMount(CourseSelectDialog, {
propsData: {
value: [],
selectedMembers: [testUser, testUser, testUser, testUser],
showDialog: true,
},
vuetify,
});
wrapper.vm.state.addToSelectedCourseFeature.data.coursesToAddSelectedUsersTo = [
testCourse,
testCourse,
testCourse,
];
wrapper.vm.availableSeatsInCourse = 10;
await wrapper.vm.$nextTick();
expect(
wrapper.vm.selectedMembers == wrapper.vm.internalSelectedMembers
).toBe(false);
});
});
import { createLocalVue, shallowMount } from "@vue/test-utils";
import Vuetify from "vuetify";
import Vuex from "vuex";
import CourseNameDate from "@/components/Dialogs/DialogComponents/CourseNameDate";
const localVue = createLocalVue();
localVue.use(Vuex);
describe("CourseNameDate", () => {
// vuetify has to be mocked for the v-autocomplete to not crash
let vuetify;
beforeEach(() => {
vuetify = new Vuetify();
});
afterEach(() => {
jest.clearAllMocks();
});
it("course name and could be in html", async () => {
const wrapper = shallowMount(CourseNameDate, {
propsData: {
courseName: "testCourseName",
courseDate: "testCourseDate",
},
vuetify,
});
const html = wrapper.html();
expect(html.indexOf("testCourseName") !== -1).toBe(true);
expect(html.indexOf("testCourseDate") !== -1).toBe(true);
});
});
import { createLocalVue, shallowMount } from "@vue/test-utils";
import Vuetify from "vuetify";
import Vuex from "vuex";
import UserSelectForCourse from "@/components/Dialogs/DialogComponents/UserSelectForCourse";
const localVue = createLocalVue();
localVue.use(Vuex);
const testUser = { name: "test" };
describe("UserSelectForCourse", () => {
// vuetify has to be mocked for the v-autocomplete to not crash
let vuetify;
beforeEach(() => {
vuetify = new Vuetify();
});
afterEach(() => {
jest.clearAllMocks();
});
it("should have select user html", async () => {
const wrapper = shallowMount(UserSelectForCourse, {
propsData: {
availableSeatsInCourse: 100,
areNoAvailableSeats: false,
courseStartDate: "test",
updateCourseMembers: () => {},
isCourseSelected: true,
value: [],
selectedMembers: [testUser, testUser, testUser, testUser],
},
vuetify,
});
const html = wrapper.html();
const isFound = html.indexOf("userselect-stub") !== -1;
expect(isFound).toEqual(true);
});
it("should have select label with available seat reservations", async () => {
const wrapper = shallowMount(UserSelectForCourse, {
propsData: {
availableSeatsInCourse: 123456789,
areNoAvailableSeats: false,
courseStartDate: "test",
updateCourseMembers: () => {},
isCourseSelected: true,
value: [],
selectedMembers: [testUser, testUser, testUser, testUser],
},
vuetify,
});
const html = wrapper.html();
const isFound = html.indexOf("123456789") !== -1;
expect(isFound).toEqual(true);
});
it("test default values", async () => {
const wrapper = shallowMount(UserSelectForCourse, {
propsData: {
availableSeatsInCourse: 123456789,
areNoAvailableSeats: false,
courseStartDate: "test",
isCourseSelected: true,
value: [],
},
vuetify,
});
expect(wrapper.vm.selectedMembers).toEqual([]);
expect(typeof wrapper.vm.updateCourseMembers).toEqual("function");
});
});
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment