diff --git a/aleksis/apps/chronos/frontend/components/NoTimetableCard.vue b/aleksis/apps/chronos/frontend/components/NoTimetableCard.vue index 12c21e656097e1a848b9d139e228b34e6a9d356f..84cf21c8498e54de13efd2056117fe478212aae1 100644 --- a/aleksis/apps/chronos/frontend/components/NoTimetableCard.vue +++ b/aleksis/apps/chronos/frontend/components/NoTimetableCard.vue @@ -26,9 +26,17 @@ export default { <div class="text-h5 grey--text text--darken-2 mb-2"> {{ $t(titleKey) }} </div> - <div class="text-body-2 grey--text text--darken-2"> + <div + class="text-body-2 grey--text text--darken-2" + v-if="$vuetify.breakpoint.lgAndUp" + > {{ $t(descriptionKey) }} </div> + <div v-if="$vuetify.breakpoint.mdAndDown"> + <v-btn color="primary" @click="$emit('selectTimetable')" class="mt-4"> + {{ $t("chronos.timetable.select") }} + </v-btn> + </div> </div> </v-card> </template> diff --git a/aleksis/apps/chronos/frontend/components/SelectTimetable.vue b/aleksis/apps/chronos/frontend/components/SelectTimetable.vue new file mode 100644 index 0000000000000000000000000000000000000000..069d74c56942509bae47aaac844cdf5d2205d383 --- /dev/null +++ b/aleksis/apps/chronos/frontend/components/SelectTimetable.vue @@ -0,0 +1,117 @@ +<script> +import timetableTypes from "./timetableTypes"; + +export default { + name: "SelectTimetable", + props: { + value: { + type: String | null, + required: true, + }, + availableTimetables: { + type: Array, + required: true, + }, + }, + data() { + return { + selected: null, + selectedFull: null, + search: "", + selectedTypes: ["GROUP", "TEACHER", "ROOM"], + types: timetableTypes, + }; + }, + watch: { + value(val) { + this.selectedFull = val; + this.selected = val.id; + }, + selectedFull(val) { + this.$emit("input", val); + }, + }, + computed: { + availableTimetablesFiltered() { + // Filter timetables by selected types + return this.availableTimetables.filter((timetable) => { + return this.selectedTypes.indexOf(timetable.type) !== -1; + }); + }, + }, +}; +</script> + +<template> + <div> + <v-card-text class="mb-0"> + <!-- Search field for timetables --> + <v-text-field + search + filled + rounded + clearable + autofocus + v-model="search" + :placeholder="$t('chronos.timetable.search')" + prepend-inner-icon="mdi-magnify" + hide-details="auto" + class="mb-2" + /> + + <!-- Filter by timetable types --> + <v-btn-toggle v-model="selectedTypes" dense block multiple class="d-flex"> + <v-btn + v-for="type in types" + :key="type.id" + class="flex-grow-1" + :value="type.id" + > + {{ type.name }} + </v-btn> + </v-btn-toggle> + </v-card-text> + + <!-- Select of available timetables --> + <v-data-iterator + :items="availableTimetablesFiltered" + item-key="id" + :search="search" + single-expand + disable-pagination + > + <template #default="{ items, isExpanded, expand }"> + <v-list class="scrollable-list"> + <v-list-item-group v-model="selected"> + <v-list-item + v-for="item in items" + @click="selectedFull = item" + :value="item.id" + :key="item.id" + > + <v-list-item-icon color="primary"> + <v-icon v-if="item.type in types" color="secondary"> + {{ types[item.type].icon }} + </v-icon> + <v-icon v-else color="secondary">mdi-grid</v-icon> + </v-list-item-icon> + <v-list-item-content> + <v-list-item-title>{{ item.name }}</v-list-item-title> + </v-list-item-content> + <v-list-item-action> + <v-icon>mdi-chevron-right</v-icon> + </v-list-item-action> + </v-list-item> + </v-list-item-group> + </v-list> + </template> + </v-data-iterator> + </div> +</template> + +<style scoped> +.scrollable-list { + height: 100%; + overflow-y: scroll; +} +</style> diff --git a/aleksis/apps/chronos/frontend/components/Timetable.vue b/aleksis/apps/chronos/frontend/components/Timetable.vue index 73d94954f9328d4c969a6fe7db42facd614a7b00..210bcd884993e55e331e5b1a3c7278f4b9892b7b 100644 --- a/aleksis/apps/chronos/frontend/components/Timetable.vue +++ b/aleksis/apps/chronos/frontend/components/Timetable.vue @@ -1,10 +1,12 @@ <script> import { gqlAvailableTimetables } from "./timetables.graphql"; import NoTimetableCard from "./NoTimetableCard.vue"; +import SelectTimetable from "./SelectTimetable.vue"; +import timetableTypes from "./timetableTypes"; export default { name: "Timetable", - components: { NoTimetableCard }, + components: { NoTimetableCard, SelectTimetable }, apollo: { availableTimetables: { query: gqlAvailableTimetables, @@ -14,43 +16,26 @@ export default { return { availableTimetables: [], selected: null, - selectedFull: null, search: "", selectedTypes: ["GROUP", "TEACHER", "ROOM"], - types: { - GROUP: { - name: "Groups", - id: "GROUP", - icon: "mdi-account-group-outline", - }, - TEACHER: { - name: "Teachers", - id: "TEACHER", - icon: "mdi-account-outline", - }, - ROOM: { name: "Rooms", id: "ROOM", icon: "mdi-door" }, - }, + types: timetableTypes, + selectDialog: false, }; }, watch: { selected(selected) { - if (selected == null) { - this.selectedFull = null; - } - }, - selectedFull(selectedFull) { // Align navigation with currently selected timetable - if (!selectedFull) { + if (!selected) { this.$router.push({ name: "chronos.timetable" }); } else if ( - selectedFull.objId !== this.$route.params.id || - selectedFull.type !== this.$route.params.type + selected.objId !== this.$route.params.id || + selected.type !== this.$route.params.type ) { this.$router.push({ name: "chronos.timetableWithId", params: { - type: selectedFull.type.toLowerCase(), - id: selectedFull.objId, + type: selected.type.toLowerCase(), + id: selected.objId, }, }); } @@ -59,7 +44,7 @@ export default { methods: { findNextTimetable(offset = 1) { const currentIndex = this.availableTimetablesIds.indexOf( - this.selectedFull.id + this.selected.id ); const newIndex = currentIndex + offset; if (newIndex < 0 || newIndex >= this.availableTimetablesIds.length) { @@ -68,8 +53,7 @@ export default { return this.availableTimetables[newIndex]; }, selectTimetable(timetable) { - this.selected = timetable.id; - this.selectedFull = timetable; + this.selected = timetable; }, }, computed: { @@ -100,88 +84,77 @@ export default { <template> <div> <v-row> - <v-col cols="2"> + <v-dialog + v-model="selectDialog" + fullscreen + hide-overlay + transition="dialog-bottom-transition" + > <v-card> - <v-card-text class="mb-0"> - <!-- Search field for timetables --> - <v-text-field - search - filled - rounded - clearable - autofocus - v-model="search" - :placeholder="$t('chronos.timetable.search')" - prepend-inner-icon="mdi-magnify" - hide-details="auto" - class="mb-2" - /> - - <!-- Filter by timetable types --> - <v-btn-toggle - v-model="selectedTypes" - dense - block - multiple - class="d-flex" - > - <v-btn - v-for="type in types" - :key="type.id" - class="flex-grow-1" - :value="type.id" - > - {{ type.name }} - </v-btn> - </v-btn-toggle> - </v-card-text> + <v-toolbar dark color="primary"> + <v-toolbar-title>{{ + $t("chronos.timetable.select") + }}</v-toolbar-title> + <v-spacer></v-spacer> + </v-toolbar> + <select-timetable + v-model="selected" + @input="selectDialog = false" + :availableTimetables="availableTimetables" + /> + </v-card> + </v-dialog> - <!-- Select of available timetables --> - <v-data-iterator - :items="availableTimetablesFiltered" - item-key="id" - :search="search" - single-expand - > - <template #default="{ items, isExpanded, expand }"> - <v-list> - <v-list-item-group v-model="selected"> - <v-list-item - v-for="item in items" - @click="selectedFull = item" - :value="item.id" - :key="item.id" - > - <v-list-item-icon color="primary"> - <v-icon v-if="item.type in types" color="secondary"> - {{ types[item.type].icon }} - </v-icon> - <v-icon v-else color="secondary">mdi-grid</v-icon> - </v-list-item-icon> - <v-list-item-content> - <v-list-item-title>{{ item.name }}</v-list-item-title> - </v-list-item-content> - <v-list-item-action> - <v-icon>mdi-chevron-right</v-icon> - </v-list-item-action> - </v-list-item> - </v-list-item-group> - </v-list> - </template> - </v-data-iterator> + <v-col md="3" lg="3" xl="2" v-if="$vuetify.breakpoint.lgAndUp"> + <v-card> + <select-timetable + v-model="selected" + :availableTimetables="availableTimetables" + /> </v-card> </v-col> - <v-col cols="10" class="full-height"> + <v-col sm="12" md="12" lg="9" xl="10" class="full-height"> <!-- No timetable card--> - <no-timetable-card v-if="selectedFull == null" /> + <no-timetable-card + v-if="selected == null" + @selectTimetable="selectDialog = true" + /> <!-- Calendar card--> <v-card v-else> - <div class="d-flex"> + <div class="d-flex flex-column" v-if="$vuetify.breakpoint.smAndDown"> + <v-card-title class="pt-2"> + <v-btn + icon + :disabled="!prevTimetable" + @click="selectTimetable(prevTimetable)" + :title="$t('chronos.timetable.prev')" + class="mr-1" + > + <v-icon>mdi-chevron-left</v-icon> + </v-btn> + <v-spacer /> + <v-chip outlined color="secondary" @click="selectDialog = true"> + {{ selected.name }} + <v-icon right>mdi-chevron-down</v-icon> + </v-chip> + <v-spacer /> + <v-btn + icon + :disabled="!nextTimetable" + @click="selectTimetable(nextTimetable)" + :title="$t('chronos.timetable.next')" + class="ml-1 float-right" + > + <v-icon>mdi-chevron-right</v-icon> + </v-btn> + </v-card-title> + </div> + + <div class="d-flex flex-wrap justify-space-between mb-2" v-else> <v-card-title> - {{ selectedFull.name }} + {{ selected.name }} </v-card-title> - <v-spacer /> <div class="pa-2 mt-1"> <v-btn icon @@ -192,7 +165,7 @@ export default { <v-icon>mdi-chevron-left</v-icon> </v-btn> <v-chip label color="secondary" outlined class="mx-1">{{ - selectedFull.shortName + selected.shortName }}</v-chip> <v-btn icon @@ -206,7 +179,7 @@ export default { </div> <calendar-with-controls :calendar-feeds="[{ name: 'lesson' }]" - :params="{ type: selectedFull.type, id: selectedFull.objId }" + :params="{ type: selected.type, id: selected.objId }" /> </v-card> </v-col> diff --git a/aleksis/apps/chronos/frontend/components/timetableTypes.js b/aleksis/apps/chronos/frontend/components/timetableTypes.js new file mode 100644 index 0000000000000000000000000000000000000000..692854f18411cc2f3ed1abf35069d28c9b18e51c --- /dev/null +++ b/aleksis/apps/chronos/frontend/components/timetableTypes.js @@ -0,0 +1,13 @@ +export default { + GROUP: { + name: "Groups", + id: "GROUP", + icon: "mdi-account-group-outline", + }, + TEACHER: { + name: "Teachers", + id: "TEACHER", + icon: "mdi-account-outline", + }, + ROOM: { name: "Rooms", id: "ROOM", icon: "mdi-door" }, +}; diff --git a/aleksis/apps/chronos/frontend/messages/en.json b/aleksis/apps/chronos/frontend/messages/en.json index 4128f0b933a0d07e698204c678da08ca7a9177d3..22fca0f29e11645887a1018809263a998e77e77f 100644 --- a/aleksis/apps/chronos/frontend/messages/en.json +++ b/aleksis/apps/chronos/frontend/messages/en.json @@ -10,7 +10,8 @@ }, "search": "Search Timetables", "prev": "Previous Timetable", - "next": "Next Timetable" + "next": "Next Timetable", + "select": "Select Timetable" }, "lessons": { "menu_title_daily": "Daily lessons"