Files
miniplan/public/src/views/PlanView.vue
T

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>