UNCLASSIFIED

Commit 79a95b8f authored by graham.smith's avatar graham.smith
Browse files

Merge branch 'BULL-797' into 'master'

BULL-797: Update LB from code.il2

See merge request !130
parents 83f88ca7 062d988f
...@@ -9,26 +9,30 @@ ...@@ -9,26 +9,30 @@
:position="user.affiliation" :position="user.affiliation"
:avatar="user.avatar" :avatar="user.avatar"
/> />
<Section class="mt-6" v-if="showWelcomeMessage"> <Section
<h4 slot="header" class="ma-0 pl-5 text-left"> class="mt-6"
Welcome to Platform One v-if="showWelcomeMessage"
</h4> headerTitle="Welcome to Platform One"
>
<WelcomeSummary slot="content" /> <WelcomeSummary slot="content" />
</Section> </Section>
<Section class="mt-6"> <Section
<h4 slot="header" class="ma-0 pl-5 text-left">My Courses</h4> class="mt-6"
<InstructorCourses slot="content" /> sync
:loading="getCourseState"
:refreshClick="refreshMyCourseData"
headerTitle="Curriculum Schedules"
>
<AdminCourses slot="content" ref="myCoursesRefresh" />
</Section> </Section>
<Section class="mt-6">
<h4 slot="header" class="ma-0 pl-5 text-left"> <Section
Curriculum Schedules class="mt-6"
</h4> v-if="$vuetify.breakpoint.lgAndUp"
<AllCurriculumSchedule slot="content" /> headerTitle="Help Desk"
</Section> >
<Section class="mt-6"> <HelpDeskSummary slot="content" />
<h4 slot="header" class="ma-0 pl-5 text-left">Remaining Seats</h4>
<RemainingSeats slot="content" />
</Section> </Section>
</v-col> </v-col>
...@@ -37,42 +41,67 @@ ...@@ -37,42 +41,67 @@
lg="6" lg="6"
class="d-flex flex-column py-0 pt-lg-3 monthlyseats" class="d-flex flex-column py-0 pt-lg-3 monthlyseats"
> >
<Section> <Section
<h4 slot="header" class="ma-0 pl-5 text-left"> sync
Past Monthly Seat Metrics :loading="getPastMetricState"
</h4> :refreshClick="refreshPastSeatMetricsData"
headerTitle="Past Monthly Seat Metrics"
>
<SeatMetrics <SeatMetrics
slot="content" slot="content"
v-bind:incomingMonth="currentMonth - 1" v-bind:incomingMonth="currentMonth - 1"
v-bind:incomingYear="currentYear" v-bind:incomingYear="currentYear"
:loadingMetric="GET_PAST_METRIC_STATE"
:setMetric="SET_PAST_METRICS_STATE"
monthMetric="past" monthMetric="past"
height="150" height="150"
ref="seatMetricsPast"
/> />
</Section> </Section>
<Section class="mt-6"> <Section
<h4 slot="header" class="ma-0 pl-5 text-left"> class="mt-6"
Present Monthly Seat Metrics sync
</h4> :loading="getPresentMetricState"
:refreshClick="refreshPresentSeatMetricsData"
headerTitle="Present Monthly Seat Metrics"
>
<SeatMetrics <SeatMetrics
slot="content" slot="content"
v-bind:incomingMonth="currentMonth" v-bind:incomingMonth="currentMonth"
v-bind:incomingYear="currentYear" v-bind:incomingYear="currentYear"
:loadingMetric="GET_PRESENT_METRIC_STATE"
:setMetric="SET_PRESENT_METRICS_STATE"
monthMetric="present" monthMetric="present"
height="150" height="150"
ref="seatMetricsPresent"
/> />
</Section> </Section>
<Section class="mt-6"> <Section
<h4 slot="header" class="ma-0 pl-5 text-left"> class="mt-6"
Future Monthly Seat Metrics sync
</h4> :loading="getFutureMetricState"
:refreshClick="refreshFutureSeatMetricsData"
headerTitle="Future Monthly Seat Metrics"
>
<SeatMetrics <SeatMetrics
slot="content" slot="content"
v-bind:incomingMonth="currentMonth + 1" v-bind:incomingMonth="currentMonth + 1"
v-bind:incomingYear="currentYear" v-bind:incomingYear="currentYear"
:loadingMetric="GET_FUTURE_METRIC_STATE"
:setMetric="SET_FUTURE_METRICS_STATE"
monthMetric="future" monthMetric="future"
height="150" height="150"
ref="seatMetricsFuture"
/> />
</Section> </Section>
<Section
class="mt-6"
v-if="$vuetify.breakpoint.mdAndDown"
headerTitle="Help Desk"
>
<HelpDeskSummary slot="content" />
</Section>
</v-col> </v-col>
</v-row> </v-row>
</v-container> </v-container>
...@@ -82,27 +111,42 @@ ...@@ -82,27 +111,42 @@
<script> <script>
import UserBanner from "@/components/UserBanner"; import UserBanner from "@/components/UserBanner";
import Section from "@/components/Section"; import Section from "@/components/Section";
import InstructorCourses from "@/components/InstructorCourses"; import AdminCourses from "@/components/AdminCourses";
import AllCurriculumSchedule from "@/components/AllCurriculumSchedule"; import HelpDeskSummary from "@/components/HelpDeskSummary";
import RemainingSeats from "@/components/RemainingSeats"; import {
SET_PAST_METRICS_STATE,
SET_PRESENT_METRICS_STATE,
SET_FUTURE_METRICS_STATE,
} from "@/store/mutation-types";
import {
GET_PAST_METRIC_STATE,
GET_PRESENT_METRIC_STATE,
GET_FUTURE_METRIC_STATE,
} from "@/store/getter-types";
import WelcomeSummary from "@/components/WelcomeSummary"; import WelcomeSummary from "@/components/WelcomeSummary";
import SeatMetrics from "@/components/MonthlySeatMetric"; import SeatMetrics from "@/components/MonthlySeatMetric";
export default { export default {
components: { components: {
UserBanner, UserBanner,
Section, Section,
InstructorCourses, AdminCourses,
AllCurriculumSchedule, HelpDeskSummary,
RemainingSeats,
WelcomeSummary, WelcomeSummary,
SeatMetrics, SeatMetrics,
}, },
name: "launchboard-super-admin-dashboard", name: "launchboard-super-admin-dashboard",
data: () => ({ data: () => ({
user: {}, user: {},
loadingProjectData: false, loadingSeatMetrics: false,
currentMonth: null, currentMonth: null,
currentYear: null, currentYear: null,
SET_PAST_METRICS_STATE,
SET_PRESENT_METRICS_STATE,
SET_FUTURE_METRICS_STATE,
GET_PAST_METRIC_STATE,
GET_PRESENT_METRIC_STATE,
GET_FUTURE_METRIC_STATE,
}), }),
beforeMount() { beforeMount() {
const date = new Date(); const date = new Date();
...@@ -113,159 +157,35 @@ export default { ...@@ -113,159 +157,35 @@ export default {
this.user = this.$store.state.user.user; this.user = this.$store.state.user.user;
}, },
methods: { methods: {
refreshProjectData() { refreshMyCourseData() {
this.$refs.projectSummary.refreshProjects(); this.$refs.myCoursesRefresh.refreshMyCourses();
}, },
setProjectLoading(loading) { refreshPastSeatMetricsData() {
this.loadingProjectData = loading; this.$refs.seatMetricsPast.refreshSeatMetrics();
},
refreshPresentSeatMetricsData() {
this.$refs.seatMetricsPresent.refreshSeatMetrics();
},
refreshFutureSeatMetricsData() {
this.$refs.seatMetricsFuture.refreshSeatMetrics();
}, },
}, },
computed: { computed: {
showWelcomeMessage() { showWelcomeMessage() {
return this.$store.state.userPreferences.userPreference.welcomeMessage; return this.$store.state.userPreferences.userPreference.welcomeMessage;
}, },
getCourseState() {
return this.$store.state.loading.courseState;
},
getPastMetricState() {
return this.$store.state.loading.pastMetricState;
},
getPresentMetricState() {
return this.$store.state.loading.presentMetricState;
},
getFutureMetricState() {
return this.$store.state.loading.futureMetricState;
},
}, },
}; };
</script> </script>
<style lang="scss">
.class-name {
color: #bdc931;
padding-left: 35px;
}
.theme--light .class-name {
color: #0e6b90;
}
.theme--light .v-progress-linear__background.white {
background-color: #04294a !important;
}
.dot-border-green {
border-left: 1px solid #bdc931;
}
.dot-border-white {
border-left: 1px solid white;
}
.theme--light .dot-border-white,
.theme--light .dot-border-green {
border-left: 1px solid #031322;
}
.class-container {
position: relative;
height: 100%;
}
.dot-border {
position: absolute;
top: 28px;
height: 96%;
}
.last .dot-border {
border-left: none;
}
.dot {
z-index: 3;
position: absolute;
border-radius: 50%;
height: 12px;
width: 12px;
margin-left: 7px;
margin-top: 17px;
}
.dot-white {
border: 1px solid white;
background-color: white;
}
.theme--light .dot-white {
border: 1px solid #031322;
}
.dot-green {
border: 1px solid #bdc931;
background-color: #031322;
}
.theme--light .dot-green {
border: 1px solid #031322;
}
@media only screen and (max-width: 1425px) {
.resText {
font-size: 12px;
}
}
@media only screen and (max-width: 1263px) {
.monthlyseats {
margin-top: 25px;
}
.resText {
font-size: 14px;
}
}
@media only screen and (max-width: 680px) {
.resText {
font-size: 12px;
}
}
@media only screen and (max-width: 600px) {
.class-name {
min-width: 33%;
}
}
@media only screen and (max-width: 480px) {
.class-name {
min-width: 42%;
}
}
@media all and (max-width: 400px) {
.curriculum-schedule {
overflow: hidden !important;
.col-10.d-flex.mx-auto {
display: flex;
flex-direction: column;
padding: 0 !important;
margin: 0 !important;
transform: scale(0.8);
}
}
div.section-content.pa-3.position-relative > div > div > div > div > div {
font-size: 10px;
padding: 0;
margin: 0 !important;
}
.dot-border {
left: 23px;
}
div.section-content.pa-3.position-relative
> div
> div
> div
> div:nth-child(3)
> div
> div:nth-child(1)
> div
.dot-border {
left: 23px;
}
.dot-border-green {
display: none;
}
}
</style>
<template> <template>
<div> <div>
<v-container> <v-container class="px-3">
<v-skeleton-loader <v-skeleton-loader
:loading="state.isLoading" :loading="state.isLoading"
type="table-heading,table-thead, table-tbody" type="table-heading,table-thead, table-tbody"
> >
<div class="d-flex flex-wrap justify-space-between"> <v-row class="px-3">
<div class="w-50 d-flex flex-row justify-space-between"> <h4 class="pl-0 pb-2 mb-0 font-weight-bold">Filter</h4>
</v-row>
<v-row class="px-3">
<v-col cols="12" md="6" lg="3" class="filter-input pa-0 pr-md-3 mb-3">
<v-select <v-select
:items="filterItemDropdown" :items="filterItemDropdown"
item-text="text" item-text="text"
item-value="value" item-value="value"
v-model="filterSelectedValue" v-model="filterSelectedValue"
label="Filter" light
></v-select> flat
single-line
hide-details
>
</v-select>
</v-col>
<v-col cols="12" md="6" lg="3" class="filter-input pa-0 mb-6 mt-md-1">
<v-text-field <v-text-field
class="ml-2" name="search"
class="search-text"
v-model="filterSearchValue" v-model="filterSearchValue"
label="Search" outlined
placeholder="Search"
append-icon="mdi-magnify"
clearable
light
flat
dense
hide-details
/> />
</div> </v-col>
<div class="w-50 d-flex flex-row justify-space-between">
<!-- START: Modal for Exporting List -->
<v-dialog
v-model="isExporting"
persistent
no-click-animation
max-width="500px"
>
<template v-slot:activator="{ on, attrs }">
<v-btn
color="secondary"
class="no-link ml-2"
target="_blank"
@click="isExporting = true"
v-bind="attrs"
v-on="on"
>
Export List
</v-btn>
</template>
<v-card>
<v-card-title>
<span class="headline">Export List</span>
</v-card-title>
<v-card-text>
<v-select
v-model="fileType"
:items="fileTypeDropdown"
item-text="text"
item-value="value"
label="File Type"
required
></v-select>
</v-card-text>
<v-card-actions> <v-col
<v-spacer></v-spacer> cols="12"
<v-btn color="secondary" @click="isExporting = false"> lg="6"
Cancel class="pa-0 mb-3 justify-end d-flex flex-wrap"
</v-btn> >
<v-btn <v-btn
color="primary" color="secondary"
:loading="isExportingActive" class="pa-0 mb-3 mr-sm-3"
@click="exportList" :block="$vuetify.breakpoint.xs"
> target="_blank"
Export List rel="noopener noreferrer"
</v-btn> href="https://jira.il2.dso.mil/servicedesk/customer/portal/1"
</v-card-actions> >
</v-card> Upgrade Permission
</v-dialog> </v-btn>
<!-- END: Modal for Exporting List -->
<!-- START: Modal for Adding Personnel -->
<v-dialog <v-dialog
class="header-action-buttons" class="header-action-buttons"
v-model="personnel.userSelect.isAddingPersonnel" v-model="personnel.userSelect.isAddingPersonnel"
...@@ -83,8 +62,8 @@ ...@@ -83,8 +62,8 @@
> >
<template v-slot:activator="{ on, attrs }"> <template v-slot:activator="{ on, attrs }">
<v-btn <v-btn
class="ml-2"
color="primary" color="primary"
:block="$vuetify.breakpoint.xs"
@click="personnel.userSelect.isAddingPersonnel = true" @click="personnel.userSelect.isAddingPersonnel = true"
v-bind="attrs" v-bind="attrs"
v-on="on" v-on="on"
...@@ -92,6 +71,7 @@ ...@@ -92,6 +71,7 @@
Add Personnel Add Personnel
</v-btn> </v-btn>
</template> </template>
<v-card> <v-card>
<v-card-title> <v-card-title>
<span class="headline">Add new Personnel</span> <span class="headline">Add new Personnel</span>
...@@ -137,19 +117,20 @@ ...@@ -137,19 +117,20 @@
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-dialog> </v-dialog>
<!-- END: Modal for Adding Personnel --> </v-col>
</div> <v-col cols="12" sm="6" class="py-0 text-left"> </v-col>
</div> </v-row>
<div style="height: 40px; display: block"></div> <v-row class="px-3 pt-12 pt-md-8">
<PersonnelTable <PersonnelTable
title="P1 PERSONNEL" title="P1 PERSONNEL"
type="" class="w-100"
ref="personnel" type=""
:selectedFilter="filterSelectedValue" ref="personnel"
:searchValue="filterSearchValue" :selectedFilter="filterSelectedValue"
/> :searchValue="filterSearchValue"
/>
</v-row>
</v-skeleton-loader> </v-skeleton-loader>
<div class="vertical-spacer"></div>
</v-container> </v-container>
</div> </div>
</template> </template>
...@@ -157,19 +138,21 @@ ...@@ -157,19 +138,21 @@
<script> <script>
import map from "lodash/map"; import map from "lodash/map";
import download from "downloadjs"; import download from "downloadjs";
import UserSelect from "@/components/UserSelect";
import UserService from "@/api/services/user"; import UserService from "@/api/services/user";
import PersonnelTable from "@/components/PersonnelTable"; import PersonnelTable from "@/components/PersonnelTable";
import { defaultSnackbarTimeout } from "@/config/config"; import { defaultSnackbarTimeout } from "@/config/config";
import inputRules from "@/utils/inputRules"; import inputRules from "@/utils/inputRules";
import Permission from "@/config/user-permissions";
import PersonnelType from "@/config/user-personnel-type"; import PersonnelType from "@/config/user-personnel-type";
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";
export default { export default {
components: { PersonnelTable, UserSelect }, components: { PersonnelTable, UserSelect },
data() { data() {
return { return {
personnelTypes: PersonnelType, personnelTypes: PersonnelType,
permissionTypes: Permission,
fileTypeMap: { fileTypeMap: {
csv: { mimeType: "text/csv", delimeter: "," }, csv: { mimeType: "text/csv", delimeter: "," },
txt: { mimeType: "text/plain", delimeter: "," }, txt: { mimeType: "text/plain", delimeter: "," },
...@@ -217,7 +200,10 @@ export default { ...@@ -217,7 +200,10 @@ export default {
async addPersonnel() { async addPersonnel() {
const personnelToAdd = this.personnel.userSelect.personnelToAdd.map( const personnelToAdd = this.personnel.userSelect.personnelToAdd.map(
(user) => { (user) => {
user.personnelType = this.personnelTypes[this.filterSelectedValue]; user.personnelType =
this.userPermission === this.permissionTypes.ADMIN
? this.personnelTypes[this.userPermission]
: this.personnelTypes[this.filterSelectedValue];
return user; return user;
} }
); );
...@@ -313,16 +299,31 @@ export default { ...@@ -313,16 +299,31 @@ export default {
return { text: label, value: key }; return { text: label, value: key };
}); });
}, },
userPermission() {
return this.$store.state.user.user.permission;
},
}, },
}; };
</script> </script>
<style scoped> <style lang="scss" scoped>
.input__slot > .v-text-field__slot { .search-text {
height: 39px; --height: 33px;
height: var(--height);
::v-deep .v-input__slot {
height: var(--height);
min-height: 33px !important;
padding-left: 0 !important;
padding-right: 4px !important;
// padding: 0 !important;
> .v-input__append-inner {
margin-top: 4px !important;
}
}
} }
tr.v-data-table__expanded__content {
.vertical-spacer { background: #002743 !important;
height: 80px; box-shadow: none;
color: white;
} }
</style> </style>
...@@ -81,6 +81,7 @@ ...@@ -81,6 +81,7 @@
<a <a
:href="`mailto:${lead.email}`" :href="`mailto:${lead.email}`"
target="_blank" target="_blank"
rel="noopener"
class="lead-name" class="lead-name"
>{{ lead.name }}</a >{{ lead.name }}</a
> >
...@@ -95,6 +96,7 @@ ...@@ -95,6 +96,7 @@
<a <a
:href="`mailto:${lead.email}`" :href="`mailto:${lead.email}`"
target="_blank" target="_blank"
rel="noopener"
class="lead-name" class="lead-name"
>{{ lead.email }}</a >{{ lead.email }}</a
> >
...@@ -110,20 +112,6 @@ ...@@ -110,20 +112,6 @@
{{ item.endDate }} {{ item.endDate }}
</div> </div>
</template> </template>
<template v-slot:[`item.memberCount`]="{ item }">
<div class="d-flex align-items-center">
<v-progress-linear
class="availability-bar"
rounded
background-color="white"
height="8px"
:value="(100 * item.memberCount) / item.capacity"
></v-progress-linear>
<div class="white-space-nowrap ml-2">
{{ item.memberCount }} / {{ item.capacity }}
</div>
</div>
</template>
</v-data-table> </v-data-table>
<div class="d-flex flex-wrap mt-5 mb-3"> <div class="d-flex flex-wrap mt-5 mb-3">
...@@ -255,7 +243,7 @@ export default { ...@@ -255,7 +243,7 @@ export default {
{ {
text: "Members", text: "Members",
value: "memberCount", value: "memberCount",
width: "200px", width: "20px",
sortable: false, sortable: false,
}, },
], ],
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
> >
<v-data-table <v-data-table
id="training-table" id="training-table"
:headers="headers" :headers="header"
:loading="fetchingData" :loading="fetchingData"
:items="courses" :items="courses"
v-model="selectedCourses" v-model="selectedCourses"
...@@ -16,16 +16,22 @@ ...@@ -16,16 +16,22 @@
:footer-props="footerProps" :footer-props="footerProps"
:server-items-length="total" :server-items-length="total"
show-select show-select
:disable-sort="onMobile"
calculate-widths calculate-widths
mobile-breakpoint="800"
loading-text="Loading Courses..." loading-text="Loading Courses..."
class="background-transparent position-relative" class="background-transparent position-relative lb-mobile-enabled"
:show-expand="onMobile"
:expanded.sync="expanded"
:hide-default-footer="courses.length === 0" :hide-default-footer="courses.length === 0"
:hide-default-header="onMobile"
:mobile-breakpoint="mobileBreakpoint"
expand-icon="mdi-chevron-right"
color="tertiary"
> >
<template v-slot:top> <template v-slot:top>
<v-container id="training-table-header"> <v-container id="training-table-header">
<v-row> <v-row>
<h4 class="pl-0 pb-2 mb-0">Filter</h4> <h4 class="pl-0 pb-2 mb-0 font-weight-bold">Filter</h4>
</v-row> </v-row>
<v-row> <v-row>
<v-col <v-col
...@@ -144,16 +150,18 @@ ...@@ -144,16 +150,18 @@
:add-busy="state.isAddingBusy" :add-busy="state.isAddingBusy"
/> />
</template> </template>
<template v-slot:[`item.name`]="{ item }"> <template v-slot:[`item.name`]="{ item }">
<router-link <router-link
:to="{ name: 'TrainingDetails', params: { trainingId: item.id } }" :to="{
name: 'TrainingDetails',
params: { trainingId: item.id },
}"
class="column-link-highlight" class="column-link-highlight"
> >
{{ item.name }} {{ item.name }}
</router-link> </router-link>
</template> </template>
<template v-slot:[`item.instructors`]="{ item }"> <template v-slot:[`item.instructors`]="{ item }" color="white">
<div class="d-flex flex-wrap"> <div class="d-flex flex-wrap">
<div <div
v-for="(instructor, index) in item.instructors" v-for="(instructor, index) in item.instructors"
...@@ -161,8 +169,10 @@ ...@@ -161,8 +169,10 @@
class="d-flex align-items-center" class="d-flex align-items-center"
> >
<a <a
v-if="!onMobile"
:href="`mailto:${instructor.email}`" :href="`mailto:${instructor.email}`"
target="_blank" target="_blank"
rel="noopener"
class="white-space-nowrap" class="white-space-nowrap"
> >
{{ instructor.name }}</a {{ instructor.name }}</a
...@@ -175,6 +185,7 @@ ...@@ -175,6 +185,7 @@
<v-tooltip top v-if="item.instructors.length > 1"> <v-tooltip top v-if="item.instructors.length > 1">
<template v-slot:activator="{ on, attrs }"> <template v-slot:activator="{ on, attrs }">
<v-btn <v-btn
v-if="!onMobile"
icon icon
color="secondary" color="secondary"
class="no-border no-link ml-2" class="no-border no-link ml-2"
...@@ -191,17 +202,16 @@ ...@@ -191,17 +202,16 @@
</div> </div>
</template> </template>
<template v-slot:[`item.startDate`]="{ item }"> <template v-slot:[`item.startDate`]="{ item }">
<div class="white-space-nowrap"> <div class="white-space-nowrap" v-if="!onMobile">
{{ item.startDate }} {{ item.startDate }}
</div> </div>
</template> </template>
<template v-slot:[`item.endDate`]="{ item }"> <template v-slot:[`item.endDate`]="{ item }">
<div class="white-space-nowrap"> <div class="white-space-nowrap" v-if="!onMobile">
{{ item.endDate }} {{ item.endDate }}
</div> </div>
</template> </template>
<template v-slot:[`item.availability`]="{ item }" v-if="!onMobile">
<template v-slot:[`item.availability`]="{ item }">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<v-progress-linear <v-progress-linear
class="availability-bar" class="availability-bar"
...@@ -215,6 +225,67 @@ ...@@ -215,6 +225,67 @@
</div> </div>
</div> </div>
</template> </template>
<template v-slot:expanded-item="{ item }" v-if="onMobile">
<tr class="d-flex flex-wrap ml-10 pl-8 mt-3" v-if="onMobile">
<div
v-for="(instructor, index) in item.instructors"
:key="index"
class="d-flex align-items-center"
>
<a
:href="`mailto:${instructor.email}`"
target="_blank"
rel="noopener"
class="white-space-nowrap"
>
{{ instructor.name }}
</a>
<span v-if="index < item.instructors.length - 1" class="mr-1"
>,</span
>
</div>
<v-tooltip top v-if="item.instructors.length > 1">
<template v-slot:activator="{ on, attrs }">
<v-btn
v-if="!onMobile"
icon
color="secondary"
class="no-border no-link ml-2"
v-bind="attrs"
v-on="on"
:href="`mailto:${item.instructors[0].email},${item.instructors[1].email}`"
target="_blank"
>
<v-icon>mdi-email</v-icon>
</v-btn>
</template>
<span>Email both Instructors</span>
</v-tooltip>
</tr>
<tr class="white-space-nowrap">
<div class="ml-10 mt-5 pl-8">
{{ item.startDate }}
to
{{ item.endDate }}
</div>
</tr>
<tr
class="d-flex align-items-center pl-8 ml-10 mt-5 mb-5"
v-if="onMobile"
>
<v-progress-linear
class="availability-bar"
rounded
background-color="white"
height="8px"
:value="(100 * item.registeredCount) / item.capacity"
></v-progress-linear>
<div class="white-space-nowrap ml-2">
{{ item.registeredCount }} / {{ item.capacity }}
</div>
</tr>
</template>
</v-data-table> </v-data-table>
<div class="d-flex flex-wrap mt-5 mb-3"> <div class="d-flex flex-wrap mt-5 mb-3">
...@@ -314,6 +385,7 @@ export default { ...@@ -314,6 +385,7 @@ export default {
}, },
data: () => ({ data: () => ({
search: null, search: null,
expanded: [],
darkMode: null, darkMode: null,
initialLoad: true, initialLoad: true,
lastOptions: null, lastOptions: null,
...@@ -364,9 +436,14 @@ export default { ...@@ -364,9 +436,14 @@ export default {
sortable: false, sortable: false,
}, },
], ],
mobileHeaders: [
{ value: "name", color: "green", width: "100%" },
{ value: "data-table-expand", width: "20px" },
],
selectedCourses: [], selectedCourses: [],
deletingCourses: [], deletingCourses: [],
courseToAdd: null, courseToAdd: null,
mobileBreakpoint: 800,
}), }),
watch: { watch: {
options: { options: {
...@@ -473,7 +550,6 @@ export default { ...@@ -473,7 +550,6 @@ export default {
this.fetchingData = false; this.fetchingData = false;
} }
}, },
async addCourse(toAdd) { async addCourse(toAdd) {
this.state.isAddingBusy = true; this.state.isAddingBusy = true;
...@@ -547,26 +623,30 @@ export default { ...@@ -547,26 +623,30 @@ export default {
return `${this.deletingCourses[0].name} removed`; return `${this.deletingCourses[0].name} removed`;
} }
}, },
onMobile() {
return this.$vuetify.breakpoint.width < this.mobileBreakpoint;
},
header() {
return this.onMobile ? this.mobileHeaders : this.headers;
},
}, },
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
#training-table { #training-table {
.availability-bar {
min-width: 120px;
width: 120px;
}
tr.deleting { tr.deleting {
opacity: 0.4; opacity: 0.4;
pointer-events: none; pointer-events: none;
} }
.v-skeleton-loader__table-row .v-skeleton-loader__table-cell, .v-skeleton-loader__table-row .v-skeleton-loader__table-cell,
.v-skeleton-loader__table-thead .v-skeleton-loader__heading { .v-skeleton-loader__table-thead .v-skeleton-loader__heading {
&:nth-last-child(-n + 2) { &:nth-last-child(-n + 2) {
display: none; display: none;
} }
} }
.availability-bar {
min-width: 120px;
width: 120px;
}
} }
</style> </style>
...@@ -12,28 +12,39 @@ ...@@ -12,28 +12,39 @@
:position="user.affiliation" :position="user.affiliation"
:avatar="user.avatar" :avatar="user.avatar"
/> />
<Section class="mt-6" v-if="showWelcomeMessage"> <Section
<h4 slot="header" class="ma-0 pl-5 text-left"> class="mt-6"
Welcome to <br class="hidden-sm-and-up" />Platform One v-if="showWelcomeMessage"
</h4> headerTitle="Welcome to Platform One"
>
<WelcomeSummary slot="content" /> <WelcomeSummary slot="content" />
</Section> </Section>
<Section class="mt-6"> <Section
<h4 slot="header" class="ma-0 pl-5 text-left"> class="mt-6"
My Curriculum Schedule sync
</h4> :loading="loadingCurriculumData"
<CurriculumSchedule slot="content" /> :refreshClick="refreshCurriculumSchedule"
headerTitle="My Curriculum Schedule"
>
<CurriculumSchedule
slot="content"
ref="curriculumSchedule"
:setCurriculumSchedule="setCurriculumSchedule"
/>
</Section> </Section>
<!-- Display Help Desk above My Project Summary if large screensize and above --> <!-- Display Help Desk above My Project Summary if large screensize and above -->
<Section class="mt-6" v-if="$vuetify.breakpoint.lgAndUp"> <Section
<h4 slot="header" class="ma-0 pl-5 text-left"> class="mt-6"
v-if="$vuetify.breakpoint.lgAndUp"
headerTitle="Help Desk"
>
<span slot="userinfo">
<TutorialTooltip tooltipName="helpDesk"> <TutorialTooltip tooltipName="helpDesk">
<span id="helpDeskTutorialTooltip"></span> <span id="helpDeskTutorialTooltip"></span>
</TutorialTooltip> </TutorialTooltip>
Help Desk </span>
</h4>
<HelpDeskSummary slot="content" /> <HelpDeskSummary slot="content" />
</Section> </Section>
</v-col> </v-col>
...@@ -42,29 +53,16 @@ ...@@ -42,29 +53,16 @@
lg="6" lg="6"
class="d-flex flex-column py-0 pt-lg-3 mt-6 mt-lg-0" class="d-flex flex-column py-0 pt-lg-3 mt-6 mt-lg-0"
> >
<Section> <Section
<h4 slot="header" class="ma-0 pl-5 text-left"> sync
:loading="loadingProjectData"
:refreshClick="refreshProjectData"
headerTitle="My Project Summary"
>
<span slot="userinfo">
<TutorialTooltip tooltipName="projects"> <TutorialTooltip tooltipName="projects">
<span id="projectsTutorialTooltip"></span> <span id="projectsTutorialTooltip"></span>
</TutorialTooltip> </TutorialTooltip>
My Project Summary
</h4>
<span slot="header-right">
<v-tooltip top>
<template v-slot:activator="{ on, attrs }">
<v-btn
icon
v-bind="attrs"
v-on="on"
@click="refreshProjectData"
:loading="loadingProjectData"
>
<v-icon>mdi-refresh</v-icon>
</v-btn>
</template>
<span>Refresh</span>
</v-tooltip>
</span> </span>
<ProjectsSummary <ProjectsSummary
slot="content" slot="content"
...@@ -74,23 +72,25 @@ ...@@ -74,23 +72,25 @@
</Section> </Section>
<!-- Display Help Desk below My Project Summary if medium screensize and below --> <!-- Display Help Desk below My Project Summary if medium screensize and below -->
<Section class="mt-6 hidden-lg-and-up"> <Section
<h4 slot="header" class="ma-0 pl-5 text-left"> class="mt-6"
v-if="$vuetify.breakpoint.mdAndDown"
headerTitle="Help Desk"
>
<span slot="userinfo">
<TutorialTooltip tooltipName="helpDesk"> <TutorialTooltip tooltipName="helpDesk">
<span id="helpDeskTutorialTooltip"></span> <span id="helpDeskTutorialTooltip"></span>
</TutorialTooltip> </TutorialTooltip>
Help Desk </span>
</h4>
<HelpDeskSummary slot="content" /> <HelpDeskSummary slot="content" />
</Section> </Section>
<Section class="mt-6"> <Section class="mt-6" headerTitle="Tools">
<h4 slot="header" class="ma-0 pl-5 text-left"> <span slot="userinfo">
<TutorialTooltip tooltipName="tools"> <TutorialTooltip tooltipName="tools">
<span id="toolsTutorialTooltip"></span> <span id="toolsTutorialTooltip"></span>
</TutorialTooltip> </TutorialTooltip>
Tools </span>
</h4>
<ToolsSummary slot="content" /> <ToolsSummary slot="content" />
</Section> </Section>
</v-col> </v-col>
...@@ -105,8 +105,8 @@ import CurriculumSchedule from "@/components/CurriculumSchedule"; ...@@ -105,8 +105,8 @@ import CurriculumSchedule from "@/components/CurriculumSchedule";
import ProjectsSummary from "@/components/ProjectsSummary"; import ProjectsSummary from "@/components/ProjectsSummary";
import HelpDeskSummary from "@/components/HelpDeskSummary"; import HelpDeskSummary from "@/components/HelpDeskSummary";
import ToolsSummary from "@/components/ToolsSummary"; import ToolsSummary from "@/components/ToolsSummary";
import WelcomeSummary from "@/components/WelcomeSummary";
import TutorialTooltip from "@/components/TutorialTooltip"; import TutorialTooltip from "@/components/TutorialTooltip";
import WelcomeSummary from "@/components/WelcomeSummary";
import WelcomeTutorialModal from "@/components/WelcomeTutorialModal"; import WelcomeTutorialModal from "@/components/WelcomeTutorialModal";
export default { export default {
...@@ -116,15 +116,16 @@ export default { ...@@ -116,15 +116,16 @@ export default {
CurriculumSchedule, CurriculumSchedule,
ProjectsSummary, ProjectsSummary,
HelpDeskSummary, HelpDeskSummary,
TutorialTooltip,
ToolsSummary, ToolsSummary,
WelcomeSummary, WelcomeSummary,
TutorialTooltip,
WelcomeTutorialModal, WelcomeTutorialModal,
}, },
name: "launchboard-user-dashboard", name: "launchboard-user-dashboard",
data: () => ({ data: () => ({
user: {}, user: {},
loadingProjectData: false, loadingProjectData: false,
loadingCurriculumData: false,
displayTutorialModal: true, displayTutorialModal: true,
}), }),
mounted() { mounted() {
...@@ -134,9 +135,15 @@ export default { ...@@ -134,9 +135,15 @@ export default {
refreshProjectData() { refreshProjectData() {
this.$refs.projectSummary.refreshProjects(); this.$refs.projectSummary.refreshProjects();
}, },
refreshCurriculumSchedule() {
this.$refs.curriculumSchedule.refreshCurriculumSchedule();
},
setProjectLoading(loading) { setProjectLoading(loading) {
this.loadingProjectData = loading; this.loadingProjectData = loading;
}, },
setCurriculumSchedule(loading) {
this.loadingCurriculumData = loading;
},
setDisplayTutorialModal(value) { setDisplayTutorialModal(value) {
this.displayTutorialModal = value; this.displayTutorialModal = value;
}, },
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
"id": 1, "id": 1,
"name": "Test Team", "name": "Test Team",
"description": "This is a test team", "description": "This is a test team",
"capacity": 10,
"createdAt": "2021-05-10T00:00:00.000Z", "createdAt": "2021-05-10T00:00:00.000Z",
"updatedAt": "2021-05-10T00:00:00.000Z", "updatedAt": "2021-05-10T00:00:00.000Z",
"deletedAt": null, "deletedAt": null,
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
"id": 1, "id": 1,
"name": "Test Team", "name": "Test Team",
"description": "This is a test team", "description": "This is a test team",
"capacity": 10,
"createdAt": "2021-05-10T00:00:00.000Z", "createdAt": "2021-05-10T00:00:00.000Z",
"updatedAt": "2021-05-10T00:00:00.000Z", "updatedAt": "2021-05-10T00:00:00.000Z",
"deletedAt": null, "deletedAt": null,
......
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
"id": 1, "id": 1,
"name": "Test Team 1", "name": "Test Team 1",
"description": "This is a test team", "description": "This is a test team",
"capacity": 10,
"createdAt": "2021-05-10T00:00:00.000Z", "createdAt": "2021-05-10T00:00:00.000Z",
"updatedAt": "2021-05-10T00:00:00.000Z", "updatedAt": "2021-05-10T00:00:00.000Z",
"deletedAt": null, "deletedAt": null,
...@@ -24,7 +23,6 @@ ...@@ -24,7 +23,6 @@
"id": 2, "id": 2,
"name": "Test Team 2", "name": "Test Team 2",
"description": "This is a test team", "description": "This is a test team",
"capacity": 10,
"createdAt": "2021-05-10T00:00:00.000Z", "createdAt": "2021-05-10T00:00:00.000Z",
"updatedAt": "2021-05-10T00:00:00.000Z", "updatedAt": "2021-05-10T00:00:00.000Z",
"deletedAt": null, "deletedAt": null,
......
import { shallowMount, createLocalVue } from "@vue/test-utils";
import TrainingService from "@/api/services/training";
import AdminCourses from "@/components/AdminCourses.vue";
import flushPromises from "flush-promises";
import Vuex from "vuex";
import Vuetify from "vuetify";
const vuetify = new Vuetify();
const localVue = createLocalVue();
localVue.use(Vuex);
describe("AdminCourses", () => {
const mockCourses = [
{ course: { name: "course1" } },
{ course: { name: "course2" } },
];
const mockError = "mock error";
const store = new Vuex.Store({
state: {
loading: {
pastMetricState: false,
presentMetricState: false,
futureMetricState: false,
},
},
});
it("should get schedule from api", async () => {
TrainingService.getCourses = jest
.fn()
.mockResolvedValue({ courses: mockCourses });
const wrapper = shallowMount(AdminCourses, {
mocks: {
$vuetify: { breakpoint: { xsOnly: true } },
},
vuetify,
store,
localVue,
});
await flushPromises();
expect(wrapper.vm.schedule).toEqual(mockCourses);
});
it("should set error message on error", async () => {
console.error = jest.fn(); // mock console.error when expecting error messages
TrainingService.getCourses = jest.fn().mockRejectedValue(mockError);
const wrapper = shallowMount(AdminCourses, {
mocks: {
$vuetify: { breakpoint: { xsOnly: true } },
},
vuetify,
store,
localVue,
});
await flushPromises();
expect(wrapper.vm.errorMessage).toEqual(mockError);
});
});
...@@ -36,6 +36,11 @@ describe("components/MonthlySeatMetric", () => { ...@@ -36,6 +36,11 @@ describe("components/MonthlySeatMetric", () => {
$store: { $store: {
state: { state: {
error: {}, error: {},
loading: {
pastMetricState: false,
presentMetricState: false,
futureMetricState: false,
},
}, },
commit: jest.fn(), commit: jest.fn(),
}, },
...@@ -76,6 +81,11 @@ describe("components/MonthlySeatMetric", () => { ...@@ -76,6 +81,11 @@ describe("components/MonthlySeatMetric", () => {
$store: { $store: {
state: { state: {
error: {}, error: {},
loading: {
pastMetricState: false,
presentMetricState: false,
futureMetricState: false,
},
}, },
commit: jest.fn(), commit: jest.fn(),
}, },
...@@ -117,6 +127,11 @@ describe("components/MonthlySeatMetric", () => { ...@@ -117,6 +127,11 @@ describe("components/MonthlySeatMetric", () => {
$store: { $store: {
state: { state: {
error: {}, error: {},
loading: {
pastMetricState: false,
presentMetricState: false,
futureMetricState: false,
},
}, },
commit: jest.fn(), commit: jest.fn(),
}, },
...@@ -158,6 +173,11 @@ describe("components/MonthlySeatMetric", () => { ...@@ -158,6 +173,11 @@ describe("components/MonthlySeatMetric", () => {
$store: { $store: {
state: { state: {
error: {}, error: {},
loading: {
pastMetricState: false,
presentMetricState: false,
futureMetricState: false,
},
}, },
commit: jest.fn(), commit: jest.fn(),
}, },
...@@ -193,6 +213,11 @@ it("should throw error", async () => { ...@@ -193,6 +213,11 @@ it("should throw error", async () => {
$store: { $store: {
state: { state: {
error: {}, error: {},
loading: {
pastMetricState: false,
presentMetricState: false,
futureMetricState: false,
},
}, },
commit: jest.fn(), commit: jest.fn(),
}, },
......
...@@ -284,59 +284,6 @@ describe("Team", () => { ...@@ -284,59 +284,6 @@ describe("Team", () => {
).toEqual("User is already a member of this team"); ).toEqual("User is already a member of this team");
}); });
}); });
describe("availableSlots", () => {
it("should compute availableSlots when empty", async () => {
// empty state
TeamService.getTeamForUser = jest
.fn()
.mockResolvedValue({ name: "mock" });
const wrapper = shallowMount(Team, {
mocks: {
$vuetify: { theme: { dark: true } },
$route,
$store: {
state: {
error: {},
user: {
user: { name: "mock user", permission: Permission.ADMIN },
},
},
commit: jest.fn(),
},
},
localVue,
vuetify,
});
await flushPromises();
expect(wrapper.vm.availableSlots).toEqual(-1);
});
it("should compute availableSlots", async () => {
TeamService.getTeam = jest.fn().mockResolvedValue({
name: "mock",
capacity: 3,
members: [{}, {}],
});
const wrapper = shallowMount(Team, {
mocks: {
$vuetify: { theme: { dark: true } },
$route,
$store: {
state: {
user: {
user: { name: "mock user", permission: Permission.ADMIN },
},
error: {},
},
commit: jest.fn(),
},
},
localVue,
vuetify,
});
await flushPromises();
expect(wrapper.vm.availableSlots).toEqual(1);
});
});
it("should compute emailSelectedHref", async () => { it("should compute emailSelectedHref", async () => {
TeamService.getTeam = jest.fn().mockResolvedValue({ name: "mock" }); TeamService.getTeam = jest.fn().mockResolvedValue({ name: "mock" });
const wrapper = shallowMount(Team, { const wrapper = shallowMount(Team, {
......
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