466 lines
14 KiB
Vue
466 lines
14 KiB
Vue
<script setup lang="ts">
|
|
|
|
import {computed, onMounted, reactive, ref, toRaw, watch} from "vue";
|
|
import TablePlan from "@/components/TablePlan.vue";
|
|
import {API, UnauthorizedError} from "@/services/api";
|
|
import type {Gottesdienst, GottesdienstGroup, Mark, SimplifiedMinistrant} from "@/models/models";
|
|
import MobilePlan from "@/components/MobilePlan.vue";
|
|
import ListView from "@/components/ListView.vue";
|
|
import PlanActionBar from "@/components/PlanActionBar.vue";
|
|
import {Auth} from "@/services/auth";
|
|
import GroupView from "@/components/GroupView.vue";
|
|
import {Dialogs} from "@/services/DialogService";
|
|
import ConfirmDialog from "@/components/dialog/ConfirmDialog.vue";
|
|
import CreatePlanDialog from "@/components/dialog/CreatePlanDialog.vue";
|
|
import CreateGottesdienstDialog from "@/components/dialog/CreateGottesdienstDialog.vue";
|
|
import CreateMinistrantDialog from "@/components/dialog/CreateMinistrantDialog.vue";
|
|
import {useRoute, useRouter} from "vue-router";
|
|
import debounce from "underscore/modules/debounce.js"
|
|
import SavingIndicator from "@/components/SavingIndicator.vue";
|
|
import ImportZelebrationsplanDialog from "@/components/dialog/ImportZelebrationsplanDialog.vue";
|
|
import {LoginService} from "@/services/LoginService";
|
|
|
|
const MAX_WIDTH_MOBILE = 600;
|
|
|
|
const router = useRouter()
|
|
const route = useRoute()
|
|
|
|
const plan = reactive<{
|
|
gottesdienste: Gottesdienst[],
|
|
ministranten: SimplifiedMinistrant[],
|
|
marks: Mark[],
|
|
editable: string[],
|
|
groups: GottesdienstGroup[]
|
|
}>({
|
|
gottesdienste: [],
|
|
ministranten: [],
|
|
marks: [],
|
|
editable: [],
|
|
groups: []
|
|
})
|
|
const mobile = ref(window.innerWidth <= MAX_WIDTH_MOBILE)
|
|
const viewMode = ref<"table" | "list">("table")
|
|
const currentUsername = ref("")
|
|
const editedMarks = reactive<Mark[]>([]);
|
|
const editPlanAdmin = ref(false)
|
|
const planId = ref(parseInt(route.params.id as string))
|
|
const isSaving = ref(false)
|
|
|
|
|
|
onMounted(async () => {
|
|
const groups = await API.getPlans()
|
|
console.log("Groups", groups)
|
|
plan.groups = groups
|
|
loadPlan()
|
|
})
|
|
|
|
watch(() => route.params.id, async (value, oldValue) => {
|
|
planId.value = parseInt(value as string)
|
|
await loadPlan()
|
|
})
|
|
|
|
async function loadPlan() {
|
|
try {
|
|
const { ministranten, gottesdienste, marks} = await API.getPlan(planId.value)
|
|
plan.ministranten = ministranten
|
|
plan.gottesdienste = gottesdienste
|
|
plan.marks = marks
|
|
Auth.checkForToken()
|
|
} catch (e: any) {
|
|
if(e instanceof UnauthorizedError) {
|
|
LoginService.showLoginPopup()
|
|
let subscription = Auth.loggedInSubject.subscribe((loggedIn) => {
|
|
subscription.unsubscribe()
|
|
if(loggedIn) {
|
|
loadPlan()
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
Auth.loggedInSubject.subscribe(async (loggedIn) => {
|
|
console.log("logged in " + loggedIn)
|
|
if (loggedIn) {
|
|
currentUsername.value = Auth.getUser()
|
|
plan.editable = Auth.getPrivileges()
|
|
if (Auth.getUser() == "admin") {
|
|
editPlanAdmin.value = true
|
|
plan.ministranten = await API.getMinistranten();
|
|
console.log("Plan", plan.ministranten)
|
|
}
|
|
} else {
|
|
currentUsername.value = ""
|
|
editPlanAdmin.value = false
|
|
plan.editable = []
|
|
}
|
|
})
|
|
|
|
window.addEventListener("resize", (ev) => {
|
|
mobile.value = window.innerWidth <= MAX_WIDTH_MOBILE
|
|
})
|
|
|
|
|
|
const sortedGottesdienste = computed(() => {
|
|
return plan.gottesdienste.sort((a, b) => {
|
|
return a.date - b.date
|
|
})
|
|
})
|
|
|
|
async function createGottesdienst(gottesdienstId?: number){
|
|
let gottesdienstRef = plan.gottesdienste.find(gottesdienst => gottesdienst.id == gottesdienstId)
|
|
let gottesdienst = gottesdienstRef ? Object.assign({}, toRaw(gottesdienstRef)) : null;
|
|
Dialogs.createDialog(CreateGottesdienstDialog, {
|
|
onPositive() {},
|
|
onNegative() {},
|
|
onDismiss() {}
|
|
}, {
|
|
gottesdienst,
|
|
planId: parseInt(planId.value as unknown),
|
|
async onCreate(godi: Gottesdienst) {
|
|
if(godi.id == -1) {
|
|
const newGodi = await API.addGottesdienstNew(godi)
|
|
godi.id = newGodi.id
|
|
plan.gottesdienste.push(godi)
|
|
} else {
|
|
await API.updateGottesdienst(godi)
|
|
const index = plan.gottesdienste.findIndex(g => g.id == godi.id)
|
|
plan.gottesdienste.splice(index, 1)
|
|
plan.gottesdienste.push(godi)
|
|
}
|
|
},
|
|
onDelete: gottesdienst ? () => deleteGottedienst(gottesdienst.id) : undefined
|
|
})
|
|
}
|
|
|
|
async function addGodi(data, validate) {
|
|
console.log("Test")
|
|
console.log(data)
|
|
let date = Date.parse(data.date + "T" + data.time);
|
|
let attendance = data.attendance && data.attendance != ""
|
|
? Date.parse(data.date + "T" + data.attendance)
|
|
: (date - 1000 * 60 * 30)
|
|
console.log(date, attendance, data.date + "T" + data.attendance)
|
|
let newGodi = await API.addGottesdienst(
|
|
data.name,
|
|
new Date(date),
|
|
new Date(attendance),
|
|
parseInt(planId.value as string)
|
|
)
|
|
console.log(newGodi)
|
|
plan.gottesdienste.push(newGodi);
|
|
validate()
|
|
}
|
|
|
|
async function deleteGottedienst(id) {
|
|
let deleted = await API.deleteGottesdienst(id)
|
|
if (deleted) {
|
|
let index = plan.gottesdienste.findIndex(godi => godi.id == id)
|
|
plan.gottesdienste.splice(index, 1)
|
|
}
|
|
}
|
|
|
|
|
|
function getMarks(): Mark[] {
|
|
return plan.marks.filter((mark: Mark) => {
|
|
let difMark = editedMarks.find((m: Mark) => m.gid == mark.gid && m.mid == mark.mid)
|
|
return !difMark
|
|
}).concat(editedMarks);
|
|
}
|
|
|
|
function getDif(): Mark[] {
|
|
return editedMarks.filter((mark: Mark) => {
|
|
let sameMark = plan.marks.find((m: Mark) => m.gid == mark.gid && m.mid == mark.mid)
|
|
return (!sameMark && mark.value != 0) || (sameMark && mark.value != sameMark.value)
|
|
})
|
|
}
|
|
|
|
|
|
const saveChanges = debounce(saveChangesFunc, 600)
|
|
|
|
async function saveChangesFunc() {
|
|
const saved = await API.setMarks(getDif())
|
|
if (saved) {
|
|
isSaving.value = false
|
|
}
|
|
}
|
|
|
|
async function resetPassword(username: string) {
|
|
const result = await API.resetPassword(username)
|
|
alert("Neues Passwort für " + username + "\n\n" + result.password)
|
|
console.log(result)
|
|
}
|
|
|
|
function canEdit(username: string) {
|
|
return plan.editable.includes(username)
|
|
}
|
|
|
|
|
|
function toggleMark(gid, mid) {
|
|
// TODO: track changes
|
|
const username = plan.ministranten.find(m => m.id == mid)?.username
|
|
if (!canEdit(username)) return;
|
|
|
|
let mark = editedMarks.find(m => m.mid == mid && m.gid == gid);
|
|
if(!mark) {
|
|
let markFromPlan = plan.marks.find(m => m.gid == gid && m.mid == mid)
|
|
if(markFromPlan) {
|
|
mark = {
|
|
...markFromPlan
|
|
}
|
|
editedMarks.push(mark)
|
|
}
|
|
}
|
|
console.log(mark, editedMarks, plan.marks)
|
|
if (mark) {
|
|
mark.value = ((mark.value + 2) % 3) - 1
|
|
} else {
|
|
mark = {
|
|
gid, mid, value: 1
|
|
}
|
|
editedMarks.push(mark)
|
|
}
|
|
|
|
isSaving.value = true
|
|
saveChanges()
|
|
}
|
|
|
|
|
|
async function createNewPlan() {
|
|
Dialogs.createDialog(CreatePlanDialog, {
|
|
onPositive: () => {},
|
|
onNegative: () => {},
|
|
onDismiss: () => {},
|
|
}, {
|
|
onCreate: async (group: GottesdienstGroup) => {
|
|
const newPlan = await API.createPlan(new Date(group.from).getTime(), new Date(group.to).getTime());
|
|
plan.groups = [newPlan].concat(toRaw(plan.groups))
|
|
router.replace("/" + newPlan.id)
|
|
}
|
|
})
|
|
}
|
|
|
|
async function deletePlan(id) {
|
|
if(!confirm("Möchtest du den Plan wirklich löschen?")) return
|
|
await API.deletePlan(id)
|
|
location.reload()
|
|
plan.groups.splice(plan.groups.findIndex(g => g.id == id), 1)
|
|
}
|
|
|
|
async function editPlan(id) {
|
|
const group = Object.assign({}, toRaw(plan.groups.find(g => g.id == id)))
|
|
group.from = new Date(group.from).toISOString().substring(0,10)
|
|
group.to = new Date(group.to).toISOString().substring(0,10)
|
|
Dialogs.createDialog(CreatePlanDialog, {
|
|
onPositive() {},
|
|
onNegative() {},
|
|
onDismiss() {}
|
|
}, {
|
|
group,
|
|
async onCreate(group) {
|
|
group.from = new Date(group.from).getTime()
|
|
group.to = new Date(group.to).getTime()
|
|
await API.updatePlan(group)
|
|
location.reload()
|
|
}
|
|
})
|
|
}
|
|
|
|
async function createMinistrant(ministrantId?: number) {
|
|
let ministrantRef = plan.ministranten.find(ministrant => ministrant.id == ministrantId)
|
|
const ministrant = ministrantRef ? Object.assign({}, toRaw(ministrantRef)) : null;
|
|
console.log("Found mini ref?", ministrant)
|
|
Dialogs.createDialog(CreateMinistrantDialog, {
|
|
onPositive() {},
|
|
onNegative() {},
|
|
onDismiss() {}
|
|
}, {
|
|
ministrant,
|
|
async onCreate(ministrant) {
|
|
ministrant.birthday = ministrant.birthday.getTime()
|
|
if(ministrant.id == -1) {
|
|
const newMinistrant = await API.createMinistrant(ministrant)
|
|
ministrant.id = newMinistrant.id
|
|
plan.ministranten.push(ministrant)
|
|
}else {
|
|
await API.updateMinistrant(ministrant)
|
|
const index = plan.ministranten.findIndex(m => m.id == ministrant.id)
|
|
plan.ministranten.splice(index, 1)
|
|
plan.ministranten.push(ministrant)
|
|
}
|
|
},
|
|
async onDelete(id) {
|
|
let deleted = await API.deleteMinistrant(id)
|
|
if (deleted) {
|
|
let index = plan.ministranten.findIndex(godi => godi.id == id)
|
|
plan.ministranten.splice(index, 1)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
async function importZelebrationsplan() {
|
|
Dialogs.createDialog(ImportZelebrationsplanDialog, {
|
|
onPositive() {},
|
|
onNegative() {},
|
|
onDismiss() {}
|
|
}, {
|
|
planId: planId.value,
|
|
async onImport(godis: Gottesdienst[]) {
|
|
plan.gottesdienste.push(...godis)
|
|
}
|
|
})
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<main>
|
|
|
|
<div class="container" :class="{
|
|
'mobile-view': mobile && viewMode === 'table'
|
|
}">
|
|
|
|
<GroupView :groups="plan.groups" :admin="editPlanAdmin"
|
|
@new="createNewPlan" @delete="deletePlan" @edit="editPlan" class="no-print"/>
|
|
|
|
|
|
<ListView
|
|
:gottesdienste="sortedGottesdienste"
|
|
:ministranten="plan.ministranten"
|
|
:marks="getMarks()"
|
|
:editable="plan.editable"
|
|
:current-username="currentUsername"
|
|
:admin="editPlanAdmin"
|
|
@toggle-mark="toggleMark"
|
|
@add-gottesdienst="createGottesdienst()"
|
|
@add-ministrant="createMinistrant()"
|
|
@edit-ministrant="(id) => createMinistrant(id)"
|
|
@edit-gottesdienst="(id) => createGottesdienst(id)"
|
|
@import-zelebrationsplan="importZelebrationsplan()"
|
|
class="plan list"
|
|
v-if="viewMode === 'list'">
|
|
|
|
</ListView>
|
|
|
|
<template v-if="viewMode === 'table'">
|
|
|
|
<PlanActionBar
|
|
class="action-bar no-print"
|
|
:save="getDif().length > 0"
|
|
:plan="false"
|
|
:godi="true"
|
|
@save="saveChanges()"
|
|
@add-godi="createGottesdienst()"
|
|
@add-mini="createMinistrant()"
|
|
@import-zelebrationsplan="importZelebrationsplan()"
|
|
v-if="editPlanAdmin"
|
|
/>
|
|
|
|
<TablePlan
|
|
:gottesdienste="sortedGottesdienste"
|
|
:ministranten="plan.ministranten"
|
|
:marks="getMarks()"
|
|
:editable="plan.editable"
|
|
:edit="editPlanAdmin"
|
|
:small-mode="editPlanAdmin"
|
|
@added="addGodi"
|
|
@delete="deleteGottedienst"
|
|
@edit="createGottesdienst"
|
|
@toggle-mark="toggleMark"
|
|
@end-edit="editPlanAdmin = false"
|
|
@reset-password="resetPassword"
|
|
@create-ministrant="createMinistrant"
|
|
@edit-ministrant="createMinistrant"
|
|
class="plan table"
|
|
v-if="!mobile">
|
|
|
|
</TablePlan>
|
|
|
|
<MobilePlan
|
|
:gottesdienste="sortedGottesdienste"
|
|
:ministranten="plan.ministranten"
|
|
:marks="getMarks()"
|
|
:editable="plan.editable"
|
|
@toggle-mark="toggleMark"
|
|
class="plan mobile"
|
|
v-else>
|
|
|
|
</MobilePlan>
|
|
</template>
|
|
|
|
<div class="view-toggle no-print" v-if="plan.gottesdienste.length > 0">
|
|
<button
|
|
class="flat"
|
|
:class="{ active: viewMode === 'table' }"
|
|
@click="viewMode = 'table'"
|
|
><i>table_rows</i> Tabelle</button>
|
|
<button
|
|
class="flat"
|
|
:class="{ active: viewMode === 'list' }"
|
|
@click="viewMode = 'list'"
|
|
><i>list</i> Liste</button>
|
|
</div>
|
|
|
|
<SavingIndicator :visible="isSaving"/>
|
|
</div>
|
|
</main>
|
|
</template>
|
|
|
|
|
|
<style scoped lang="less">
|
|
|
|
.container {
|
|
width: 100%;
|
|
overflow-x: auto;
|
|
|
|
@media print {
|
|
overflow-x: unset;
|
|
}
|
|
}
|
|
|
|
.plan {
|
|
padding-bottom: 24px;
|
|
}
|
|
|
|
.plan.table {
|
|
//width: 100%;
|
|
}
|
|
|
|
.container .view-toggle {
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 4px;
|
|
padding: 8px 12px;
|
|
|
|
button {
|
|
font-size: 14px;
|
|
padding: 6px 14px;
|
|
|
|
i {
|
|
font-size: 18px;
|
|
}
|
|
|
|
&.active {
|
|
background: #e0e0e0;
|
|
border-color: #c4c4c4;
|
|
}
|
|
}
|
|
}
|
|
|
|
.container.mobile-view {
|
|
.plan.list {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
}
|
|
|
|
.action-bar {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 100;
|
|
}
|
|
</style>
|