✨ Implement timetable editing
This commit is contained in:
23
src/App.vue
23
src/App.vue
@ -41,7 +41,7 @@ const isDataView = computed(() => route.meta.dataView || false);
|
||||
:error="loadingFailed"
|
||||
/>
|
||||
<div class="center">
|
||||
<main>
|
||||
<div class="container">
|
||||
<DateSelector
|
||||
:selectedDate="selectedDate"
|
||||
:selectedDay="selectedDay"
|
||||
@ -49,10 +49,12 @@ const isDataView = computed(() => route.meta.dataView || false);
|
||||
@changeDate="(date) => (changeDate = date)"
|
||||
v-show="isDataView"
|
||||
/>
|
||||
<div class="wrapper">
|
||||
<RouterView />
|
||||
</div>
|
||||
</main>
|
||||
<main>
|
||||
<div class="wrapper">
|
||||
<RouterView />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<BottomNavbar v-show="!$route.meta.hideNav" />
|
||||
</div>
|
||||
</div>
|
||||
@ -90,15 +92,20 @@ body {
|
||||
}
|
||||
|
||||
main {
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
width: inherit;
|
||||
max-width: 900px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
@ -1,8 +1,25 @@
|
||||
<script setup>
|
||||
import { getSubstitutionColor } from "@/util";
|
||||
import { times } from "@/store";
|
||||
import { ref } from "vue";
|
||||
import { TrashIcon } from "lucide-vue-next";
|
||||
|
||||
defineProps(["lesson", "index"]);
|
||||
const props = defineProps(["lesson", "index", "edit", "buttons"]);
|
||||
const emit = defineEmits(["change", "delete"]);
|
||||
|
||||
const subjectElement = ref();
|
||||
const teacherElement = ref();
|
||||
const roomElement = ref();
|
||||
const lessonLength = ref(props.lesson.length || 1);
|
||||
|
||||
function update() {
|
||||
emit("change", {
|
||||
subject: subjectElement.value.innerText,
|
||||
teacher: teacherElement.value.innerText,
|
||||
room: roomElement.value.innerText,
|
||||
length: lessonLength.value,
|
||||
});
|
||||
}
|
||||
|
||||
function isChanged(lesson, key) {
|
||||
const substitution = lesson.substitution;
|
||||
@ -43,13 +60,21 @@ function getTime(index) {
|
||||
<div class="infos">
|
||||
<!-- Subject changed -->
|
||||
<span class="subject" v-if="isChanged(lesson, 'subject')">
|
||||
<s>{{ lesson.subject }}</s> {{ lesson.substitution.change.subject }}
|
||||
<s>{{ lesson.subject }}</s>
|
||||
{{ lesson.substitution.change.subject }}
|
||||
</span>
|
||||
<!-- Cancellation -->
|
||||
<span class="subject" v-else-if="isCancelled(lesson.substitution)">
|
||||
<s>{{ lesson.subject }}</s>
|
||||
</span>
|
||||
<span class="subject" v-else>
|
||||
<span
|
||||
class="subject"
|
||||
v-else
|
||||
:contenteditable="edit"
|
||||
data-ph="Subject"
|
||||
ref="subjectElement"
|
||||
@input="update"
|
||||
>
|
||||
{{ lesson.subject }}
|
||||
</span>
|
||||
<div class="info">
|
||||
@ -58,22 +83,43 @@ function getTime(index) {
|
||||
<s>{{ lesson.teacher }}</s>
|
||||
{{ lesson.substitution.change.teacher }}</span
|
||||
>
|
||||
<span class="info" v-else> {{ lesson.teacher }}</span>
|
||||
<span
|
||||
class="info"
|
||||
v-else
|
||||
:contenteditable="edit"
|
||||
data-ph="Teacher"
|
||||
ref="teacherElement"
|
||||
@input="update"
|
||||
>
|
||||
{{ lesson.teacher }}</span
|
||||
>
|
||||
<!-- Room changed -->
|
||||
<span class="info" v-if="isChanged(lesson, 'room')"
|
||||
>, <s>{{ lesson.room }}</s> {{ lesson.substitution.change.room }}
|
||||
</span>
|
||||
<span class="info" v-else-if="lesson.room">, {{ lesson.room }}</span>
|
||||
<span class="info" v-else-if="lesson.room || edit"
|
||||
>,
|
||||
<span
|
||||
:contenteditable="edit"
|
||||
data-ph="Room"
|
||||
ref="roomElement"
|
||||
@input="update"
|
||||
>{{ lesson.room }}</span
|
||||
></span
|
||||
>
|
||||
<!-- Show notes if available -->
|
||||
<span class="info" v-if="getNotes(lesson.substitution)"
|
||||
>, {{ $t("timetable.notes") }} {{ getNotes(lesson.substitution) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="times" v-if="getTime(index).start">
|
||||
<div class="times" v-if="getTime(index).start && !edit">
|
||||
<span>{{ getTime(index).start }} -</span>
|
||||
<span>{{ getTime(index).end }}</span>
|
||||
</div>
|
||||
<div class="buttons" v-if="edit && buttons">
|
||||
<TrashIcon @click="$emit('delete')" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -119,4 +165,19 @@ function getTime(index) {
|
||||
opacity: 0.5;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
margin-left: auto;
|
||||
opacity: 0.7;
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
[contenteditable="true"]:empty:before {
|
||||
content: attr(data-ph);
|
||||
opacity: 0.4;
|
||||
cursor: text;
|
||||
}
|
||||
</style>
|
||||
|
100
src/components/lesson-group-list.vue
Normal file
100
src/components/lesson-group-list.vue
Normal file
@ -0,0 +1,100 @@
|
||||
<script setup>
|
||||
import { toRaw, ref } from "vue";
|
||||
import LessonCard from "@/components/lesson-card.vue";
|
||||
import {
|
||||
ArrowUpIcon,
|
||||
ArrowDownIcon,
|
||||
SettingsIcon,
|
||||
TrashIcon,
|
||||
PlusIcon,
|
||||
} from "lucide-vue-next";
|
||||
|
||||
const props = defineProps(["lesson", "index", "edit"]);
|
||||
const emit = defineEmits(["change", "move"]);
|
||||
|
||||
const advancedOptions = ref(false);
|
||||
|
||||
const lessonLength = ref(1);
|
||||
function updateLength() {
|
||||
let newLesson = structuredClone(toRaw(props.lesson));
|
||||
for (let entry of newLesson) {
|
||||
entry.length = lessonLength.value;
|
||||
}
|
||||
emit("change", newLesson);
|
||||
}
|
||||
|
||||
function appendOption() {
|
||||
let newLesson = structuredClone(toRaw(props.lesson));
|
||||
if (Array.isArray(newLesson)) newLesson.push({});
|
||||
else newLesson = [newLesson, {}];
|
||||
emit("change", newLesson);
|
||||
}
|
||||
|
||||
function lessonChanged(index, data) {
|
||||
let newLesson = structuredClone(toRaw(props.lesson));
|
||||
newLesson[index] = data;
|
||||
if (newLesson.length > 1)
|
||||
newLesson[index].group = `${data.subject}: ${data.teacher}`;
|
||||
else delete newLesson[index].group;
|
||||
emit("change", newLesson);
|
||||
}
|
||||
|
||||
function lessonDeleted(index) {
|
||||
let newLesson = structuredClone(toRaw(props.lesson));
|
||||
newLesson.splice(index, 1);
|
||||
if (newLesson.length == 0) emit("delete");
|
||||
else emit("change", newLesson);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="lessonList">
|
||||
<div class="buttons" v-if="edit">
|
||||
<ArrowUpIcon @click="$emit('move', 'up')" />
|
||||
<ArrowDownIcon @click="$emit('move', 'down')" />
|
||||
<SettingsIcon @click="advancedOptions = !advancedOptions" />
|
||||
<TrashIcon @click="$emit('delete')" />
|
||||
<PlusIcon @click="appendOption" />
|
||||
</div>
|
||||
<LessonCard
|
||||
v-for="(option, groupIndex) in lesson"
|
||||
:key="groupIndex"
|
||||
:lesson="option"
|
||||
:index="index"
|
||||
:edit="edit"
|
||||
:buttons="edit"
|
||||
@change="(data) => lessonChanged(groupIndex, data)"
|
||||
@delete="() => lessonDeleted(groupIndex)"
|
||||
/>
|
||||
<div class="advanced" v-show="advancedOptions" v-if="edit">
|
||||
<span>Length: </span>
|
||||
<input
|
||||
type="number"
|
||||
v-model="lessonLength"
|
||||
@change="updateLength"
|
||||
min="1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.lessonList {
|
||||
border-left: 2px solid var(--text-color);
|
||||
padding-left: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
opacity: 0.7;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 30px;
|
||||
}
|
||||
</style>
|
44
src/components/settings/editor-navbar.vue
Normal file
44
src/components/settings/editor-navbar.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<script setup>
|
||||
import { BanIcon, SaveIcon } from "lucide-vue-next";
|
||||
|
||||
defineEmits(["abort", "save"]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bottomnav">
|
||||
<div class="entry danger" @click="$emit('back')">
|
||||
<BanIcon />
|
||||
<span>Abort</span>
|
||||
</div>
|
||||
<div class="entry success" @click="$emit('save')">
|
||||
<SaveIcon />
|
||||
<span>Save</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.bottomnav {
|
||||
width: calc(90% - 40px);
|
||||
height: 50px;
|
||||
max-width: 800px;
|
||||
padding: 0px 20px;
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
display: grid;
|
||||
grid-auto-columns: 1fr;
|
||||
grid-auto-flow: column;
|
||||
z-index: 100;
|
||||
border-radius: 10px;
|
||||
background-color: var(--bottomnav-color);
|
||||
box-shadow: var(--bottomnav-shadow);
|
||||
}
|
||||
|
||||
.entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
@ -42,7 +42,7 @@ const linkedTimetable = computed(() => {
|
||||
</div>
|
||||
<div class="listContainer">
|
||||
<template v-for="(lesson, index) in linkedTimetable" :key="index">
|
||||
<LessonCard :lesson="lesson" :index="index" />
|
||||
<LessonCard :lesson="lesson" :index="index" :edit="false" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -9,6 +9,7 @@ import LoginView from "@/views/LoginView.vue";
|
||||
import TokenView from "@/views/TokenView.vue";
|
||||
import FilteringSettings from "@/views/settings/FilteringSettings.vue";
|
||||
import TimetableSettings from "@/views/settings/TimetableSettings.vue";
|
||||
import TimetableEditor from "@/views/settings/TimetableEditor.vue";
|
||||
import TimetableGroupSettings from "@/views/settings/TimetableGroupSettings.vue";
|
||||
import AppearanceSettings from "@/views/settings/AppearanceSettings.vue";
|
||||
import AboutPage from "@/views/settings/AboutPage.vue";
|
||||
@ -76,6 +77,14 @@ const router = createRouter({
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/settings/timetable/edit/:id?",
|
||||
name: "title.editTimetable",
|
||||
component: TimetableEditor,
|
||||
meta: {
|
||||
hideNav: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
name: "title.login",
|
||||
|
16
src/store.js
16
src/store.js
@ -34,9 +34,13 @@ watch(
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
watch(localTimetables, (newValue) => {
|
||||
localStorage.setItem("timetables", JSON.stringify(newValue));
|
||||
});
|
||||
watch(
|
||||
localTimetables,
|
||||
(newValue) => {
|
||||
localStorage.setItem("timetables", JSON.stringify(newValue));
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
watch(theme, (newValue) => {
|
||||
localStorage.setItem("theme", newValue);
|
||||
});
|
||||
@ -186,7 +190,7 @@ export const parsedTimetable = computed(() => {
|
||||
let usedLesson = { ...lesson };
|
||||
// Check if lesson has multiple options
|
||||
// (timetable groups)
|
||||
if (Array.isArray(lesson)) {
|
||||
if (Array.isArray(lesson) && lesson.length > 1) {
|
||||
let matchingLesson = lesson.find((e) =>
|
||||
timetableGroups.value.includes(e.group)
|
||||
);
|
||||
@ -200,6 +204,8 @@ export const parsedTimetable = computed(() => {
|
||||
};
|
||||
}
|
||||
usedLesson = { ...matchingLesson };
|
||||
} else if (Array.isArray(lesson)) {
|
||||
usedLesson = { ...lesson[0] };
|
||||
}
|
||||
// Duplicate the lesson if its length is > 1 for it
|
||||
// to show up multiple times in the timetable view
|
||||
@ -220,7 +226,7 @@ export const possibleTimetableGroups = computed(() => {
|
||||
for (const lesson of day) {
|
||||
if (Array.isArray(lesson)) {
|
||||
for (const group of lesson) {
|
||||
if (!foundTimetableGroups.includes(group.group)) {
|
||||
if (group.group && !foundTimetableGroups.includes(group.group)) {
|
||||
foundTimetableGroups.push(group.group);
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ export const strings = {
|
||||
history: "History",
|
||||
login: "Login",
|
||||
token: "Token",
|
||||
editTimetable: "Edit Timetable",
|
||||
settings: {
|
||||
main: "Settings",
|
||||
filtering: "Filtering",
|
||||
@ -28,6 +29,7 @@ export const strings = {
|
||||
text: {
|
||||
filtering:
|
||||
"Select a class here so the correct timetable is used and only relevant substitutions are shown.",
|
||||
createTimetable: "Create a new Timetable",
|
||||
timetableGroups:
|
||||
"A timetable group defines, which lesson should be displayed, and which substitution should be shown if there are multiple possibilities for a single lesson within one class.",
|
||||
language: "Change the language of all texts in the application.",
|
||||
@ -110,6 +112,9 @@ export const strings = {
|
||||
token: {
|
||||
header: "Generate API-Token",
|
||||
},
|
||||
editor: {
|
||||
newLesson: "Create new Lesson",
|
||||
},
|
||||
},
|
||||
de: {
|
||||
title: {
|
||||
@ -117,6 +122,7 @@ export const strings = {
|
||||
substitutions: "Vertretungsplan",
|
||||
history: "Verlauf",
|
||||
login: "Anmelden",
|
||||
editTimetable: "Stundenplan Editieren",
|
||||
settings: {
|
||||
main: "Einstellungen",
|
||||
filtering: "Filter",
|
||||
@ -139,6 +145,7 @@ export const strings = {
|
||||
text: {
|
||||
filtering:
|
||||
"Wähle hier deine Klasse aus, damit du deinen Stundenplan angezeigt bekommst und du nur relevante Vertretungen siehst.",
|
||||
createTimetable: "Neuen Stundenplan erstellen",
|
||||
timetableGroups:
|
||||
"Stundenplan-Gruppen legen fest, welche Stundenplan-Daten du angezeigt bekommst, wenn es mehrere Möglichkeiten für eine Stunde gibt.",
|
||||
language: "Ändere die Sprache aller Texte dieser Anwendung.",
|
||||
@ -220,5 +227,8 @@ export const strings = {
|
||||
"An den Vertretungen für diesen Tag wurde noch nichts geändert",
|
||||
},
|
||||
},
|
||||
editor: {
|
||||
newLesson: "Neue Stunde hinzufügen",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
221
src/views/settings/TimetableEditor.vue
Normal file
221
src/views/settings/TimetableEditor.vue
Normal file
@ -0,0 +1,221 @@
|
||||
<script setup>
|
||||
import { useRoute } from "vue-router";
|
||||
import { Swiper, SwiperSlide } from "swiper/vue";
|
||||
import { ref, toRaw } from "vue";
|
||||
import { localTimetables } from "@/store";
|
||||
import { PlusIcon } from "lucide-vue-next";
|
||||
import EditorNavbar from "@/components/settings/editor-navbar.vue";
|
||||
import DateSelector from "@/components/date-selector.vue";
|
||||
import ScrollableContainer from "@/components/scrollable-container.vue";
|
||||
import LessonGroupList from "@/components/lesson-group-list.vue";
|
||||
|
||||
const route = useRoute();
|
||||
const timetable = localTimetables.value.find(
|
||||
(e) => e.id == route.params.id
|
||||
) || {
|
||||
data: [],
|
||||
};
|
||||
timetable.data = timetable.data.map((day) => {
|
||||
if (!day) return [];
|
||||
return day.map((lesson) => {
|
||||
if (Array.isArray(lesson)) return toRaw(lesson);
|
||||
else return [toRaw(lesson)];
|
||||
});
|
||||
});
|
||||
const timetableClone = ref(structuredClone(toRaw(timetable)));
|
||||
|
||||
const swiperElement = ref();
|
||||
function setSwiper(swiper) {
|
||||
swiperElement.value = swiper;
|
||||
}
|
||||
|
||||
function getLessonIndex(index, day) {
|
||||
let lessonIndex = 0;
|
||||
for (let i = 0; i < index; i++) {
|
||||
lessonIndex += day[i][0].length || 1;
|
||||
}
|
||||
return lessonIndex;
|
||||
}
|
||||
|
||||
function getLessonLength(lesson) {
|
||||
if (Array.isArray(lesson)) return lesson[0].length || 1;
|
||||
else return lesson.length || 1;
|
||||
}
|
||||
|
||||
function arrayMove(arr, fromIndex, toIndex) {
|
||||
let element = arr[fromIndex];
|
||||
arr.splice(fromIndex, 1);
|
||||
arr.splice(toIndex, 0, element);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="editor">
|
||||
<DateSelector
|
||||
:selectedDay="(swiperElement || { activeIndex: 0 }).activeIndex"
|
||||
@changeDay="
|
||||
(day) => swiperElement.slideTo(swiperElement.activeIndex + day)
|
||||
"
|
||||
/>
|
||||
<div class="settings">
|
||||
<span>Name: </span>
|
||||
<input type="text" v-model="timetableClone.title" />
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<swiper
|
||||
:slides-per-view="1"
|
||||
:space-between="50"
|
||||
:initial-slide="0"
|
||||
@swiper="setSwiper"
|
||||
>
|
||||
<swiper-slide :key="day" v-for="day in 5">
|
||||
<ScrollableContainer>
|
||||
<div class="list">
|
||||
<div
|
||||
class="lesson"
|
||||
v-for="(lesson, index) in timetableClone.data[day - 1]"
|
||||
:key="index"
|
||||
>
|
||||
<!-- Add main editable element -->
|
||||
<LessonGroupList
|
||||
:index="getLessonIndex(index, timetableClone.data[day - 1])"
|
||||
:lesson="lesson"
|
||||
:edit="true"
|
||||
@change="(e) => (timetableClone.data[day - 1][index] = e)"
|
||||
@delete="timetableClone.data[day - 1].splice(index, 1)"
|
||||
@move="
|
||||
(direction) => {
|
||||
if (direction == 'up' && index > 0) {
|
||||
arrayMove(
|
||||
timetableClone.data[day - 1],
|
||||
index,
|
||||
index - 1
|
||||
);
|
||||
} else if (
|
||||
direction == 'down' &&
|
||||
index < timetableClone.data[day - 1].length
|
||||
) {
|
||||
arrayMove(
|
||||
timetableClone.data[day - 1],
|
||||
index,
|
||||
index + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
"
|
||||
/>
|
||||
<!-- Add placeholder elements for lessons with length > 1 -->
|
||||
<div
|
||||
class="inactive"
|
||||
v-for="placeholderLesson in getLessonLength(lesson) - 1"
|
||||
:key="placeholderLesson"
|
||||
>
|
||||
<LessonGroupList
|
||||
:index="
|
||||
getLessonIndex(index, timetableClone.data[day - 1]) +
|
||||
placeholderLesson
|
||||
"
|
||||
:lesson="lesson"
|
||||
:edit="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="create"
|
||||
@click="
|
||||
if (timetableClone.data[day - 1]) {
|
||||
timetableClone.data[day - 1].push([{}]);
|
||||
} else {
|
||||
timetableClone.data[day - 1] = [[{}]];
|
||||
}
|
||||
"
|
||||
>
|
||||
<PlusIcon />
|
||||
<span>{{ $t("editor.newLesson") }}</span>
|
||||
</div>
|
||||
</ScrollableContainer>
|
||||
</swiper-slide>
|
||||
</swiper>
|
||||
</div>
|
||||
</div>
|
||||
<div class="navbar">
|
||||
<EditorNavbar
|
||||
@back="$router.back()"
|
||||
@save="
|
||||
() => {
|
||||
timetable.data = timetableClone.data;
|
||||
timetable.title = timetableClone.title;
|
||||
timetable.source = 'Manual';
|
||||
timetable.trusted = true;
|
||||
$router.back();
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@import "swiper/css";
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.editor {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.settings {
|
||||
padding: 5px 10px 0px 10px;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 10px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.settings input {
|
||||
border: unset;
|
||||
background-color: unset;
|
||||
color: unset;
|
||||
border-bottom: 1px solid var(--text-color);
|
||||
outline: unset;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.swiper {
|
||||
padding: 5px 10px 0px 10px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.inactive {
|
||||
opacity: 0.7;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.create {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 5px;
|
||||
margin-top: 10px;
|
||||
gap: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
@ -1,6 +1,7 @@
|
||||
<script setup>
|
||||
import TimetableCard from "@/components/settings/timetable-card.vue";
|
||||
import { timetables, localTimetables, timetableId } from "@/store";
|
||||
import { PlusIcon } from "lucide-vue-next";
|
||||
import { toRaw } from "vue";
|
||||
|
||||
function copyTimetable(timetable) {
|
||||
@ -9,39 +10,54 @@ function copyTimetable(timetable) {
|
||||
newTimetable.id = new Date().getTime();
|
||||
localTimetables.value.push(newTimetable);
|
||||
}
|
||||
|
||||
function createTimetable() {
|
||||
localTimetables.value.push({
|
||||
id: new Date().getTime(),
|
||||
title: "New timetable",
|
||||
data: [],
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2>{{ $t("settings.heading.localTimetables") }}</h2>
|
||||
<div class="list">
|
||||
<TimetableCard
|
||||
v-for="timetable in localTimetables"
|
||||
:key="timetable.id"
|
||||
:timetable="timetable"
|
||||
:selected="timetableId == timetable.id"
|
||||
:editable="true"
|
||||
@click="timetableId = timetable.id"
|
||||
@copy="copyTimetable(timetable)"
|
||||
@delete="
|
||||
localTimetables.splice(
|
||||
localTimetables.findIndex((e) => e.id == timetable.id),
|
||||
1
|
||||
)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<h2>{{ $t("settings.heading.remoteTimetables") }}</h2>
|
||||
<div class="list">
|
||||
<TimetableCard
|
||||
v-for="timetable in timetables"
|
||||
:key="timetable.id"
|
||||
:timetable="timetable"
|
||||
:selected="timetableId == timetable.id"
|
||||
:editable="false"
|
||||
@click="timetableId = timetable.id"
|
||||
@copy="copyTimetable(timetable)"
|
||||
/>
|
||||
<div class="content" v-if="$route.name == 'title.settings.timetable'">
|
||||
<h2>{{ $t("settings.heading.localTimetables") }}</h2>
|
||||
<div class="list">
|
||||
<TimetableCard
|
||||
v-for="timetable in localTimetables"
|
||||
:key="timetable.id"
|
||||
:timetable="timetable"
|
||||
:selected="timetableId == timetable.id"
|
||||
:editable="true"
|
||||
@click="timetableId = timetable.id"
|
||||
@edit="$router.push('timetable/edit/' + timetable.id)"
|
||||
@copy="copyTimetable(timetable)"
|
||||
@delete="
|
||||
localTimetables.splice(
|
||||
localTimetables.findIndex((e) => e.id == timetable.id),
|
||||
1
|
||||
)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="create" @click="createTimetable">
|
||||
<PlusIcon /> {{ $t("settings.text.createTimetable") }}
|
||||
</div>
|
||||
<h2>{{ $t("settings.heading.remoteTimetables") }}</h2>
|
||||
<div class="list">
|
||||
<TimetableCard
|
||||
v-for="timetable in timetables"
|
||||
:key="timetable.id"
|
||||
:timetable="timetable"
|
||||
:selected="timetableId == timetable.id"
|
||||
:editable="false"
|
||||
@click="timetableId = timetable.id"
|
||||
@copy="copyTimetable(timetable)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<RouterView v-else />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@ -57,4 +73,13 @@ p {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.create {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 5px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
Reference in New Issue
Block a user