This commit is contained in:
212
public/src/components/GroupView.vue
Normal file
212
public/src/components/GroupView.vue
Normal file
@@ -0,0 +1,212 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {onMounted, reactive, ref, watch} from "vue";
|
||||
import Input from "@/components/Input.vue";
|
||||
import {useRouter} from "vue-router";
|
||||
|
||||
const props = defineProps<{
|
||||
groups: any[],
|
||||
admin: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(["change", "new", "delete", "edit"])
|
||||
const router = useRouter()
|
||||
|
||||
const prev = ref<number | null>(null)
|
||||
const cur = ref(0)
|
||||
const next = ref<number | null>(null)
|
||||
|
||||
const newPlan = reactive({
|
||||
id: 0,
|
||||
from: null,
|
||||
to: null
|
||||
})
|
||||
|
||||
// watch(props, (value, oldValue, onCleanup) => {
|
||||
// setToCurrentDate()
|
||||
// })
|
||||
|
||||
onMounted(() => {
|
||||
setToCurrentDate()
|
||||
})
|
||||
|
||||
function getGroups() {
|
||||
if (newPlan.id != 0) {
|
||||
return [newPlan].concat(props.groups)
|
||||
} else {
|
||||
return props.groups
|
||||
}
|
||||
}
|
||||
|
||||
function setToCurrentDate() {
|
||||
const date = new Date().getTime()
|
||||
let g = props.groups.findIndex(g => date >= g.from && date <= g.to)
|
||||
if (g == -1) {
|
||||
prev.value = 1;
|
||||
cur.value = 0;
|
||||
next.value = null
|
||||
g = props.groups[0]
|
||||
} else {
|
||||
prev.value = g + 1
|
||||
cur.value = g
|
||||
next.value = g - 1
|
||||
}
|
||||
emit("change", get(cur.value)?.id ?? 0)
|
||||
}
|
||||
|
||||
function forward() {
|
||||
prev.value--;
|
||||
cur.value--;
|
||||
next.value--;
|
||||
|
||||
// emit("change", get(cur.value).id)
|
||||
router.push("/" + get(cur.value).id)
|
||||
}
|
||||
|
||||
function back() {
|
||||
prev.value++;
|
||||
cur.value++;
|
||||
next.value++;
|
||||
// emit("change", get(cur.value).id)
|
||||
router.push("/" + get(cur.value).id)
|
||||
}
|
||||
|
||||
function two(s) {
|
||||
return (s < 10 ? "0" : "") + s
|
||||
}
|
||||
|
||||
function formatDate(time) {
|
||||
const date = new Date(time)
|
||||
return two(date.getDate()) + ". " + getNameOfMonth(date.getMonth())
|
||||
}
|
||||
|
||||
function formatDateShort(time) {
|
||||
const date = new Date(time)
|
||||
return two(date.getDate()) + "." + two(date.getMonth() + 1) + "."
|
||||
}
|
||||
|
||||
function getNameOfMonth(month): string {
|
||||
switch (month) {
|
||||
case 0:
|
||||
return "Januar";
|
||||
case 1:
|
||||
return "Februar";
|
||||
case 2:
|
||||
return "März";
|
||||
case 3:
|
||||
return "April";
|
||||
case 4:
|
||||
return "Mai";
|
||||
case 5:
|
||||
return "Juni";
|
||||
case 6:
|
||||
return "Juli";
|
||||
case 7:
|
||||
return "August";
|
||||
case 8:
|
||||
return "September";
|
||||
case 9:
|
||||
return "Oktober";
|
||||
case 10:
|
||||
return "November";
|
||||
case 11:
|
||||
return "Dezember";
|
||||
}
|
||||
return "?"
|
||||
}
|
||||
|
||||
function get(i) {
|
||||
if (i < 0) return null;
|
||||
return getGroups()[i]
|
||||
}
|
||||
|
||||
function index(id) {
|
||||
return id
|
||||
}
|
||||
|
||||
function dateToValueString(time) {
|
||||
const date = new Date(time)
|
||||
return date.getFullYear() + "-" + two(date.getMonth() + 1) + "-" + two(date.getDate())
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bar">
|
||||
<div class="controls">
|
||||
<button class="flat left" v-if="prev != null && prev <= groups.length - 1" @click="back">
|
||||
<i>arrow_left_alt</i>{{ formatDateShort(get(prev).from) }} - {{ formatDateShort(get(prev).to) }}
|
||||
</button>
|
||||
<div class="width: 100%"/>
|
||||
<button class="flat right" v-if="next != null && next > 0" @click="forward">
|
||||
{{ formatDateShort(get(next).from) }} - {{ formatDateShort(get(next).to) }}<i>arrow_right_alt</i>
|
||||
</button>
|
||||
<button class="flat right" v-if="(next == null || next <= 0) && admin" style="margin-right: 20px"
|
||||
@click="$emit('new')">
|
||||
<i>add</i> Neuer plan
|
||||
</button>
|
||||
</div>
|
||||
<template v-if="groups.length > 0">
|
||||
<span style="z-index: 1">Miniplan vom {{ formatDate(get(cur).from) }} bis {{ formatDate(get(cur).to) }}</span>
|
||||
<span v-if="admin" style="display: flex; align-items: center; z-index: 1">
|
||||
<button class="icon flat" @click="$emit('delete', get(cur).id)"><i>delete</i></button>
|
||||
<button class="icon flat" @click="$emit('edit', get(cur).id)"><i>edit</i></button>
|
||||
</span>
|
||||
</template>
|
||||
<span v-else>
|
||||
Keine Gottesdienstgruppen vorhanden
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
.bar {
|
||||
display: flex;
|
||||
width: calc(100% - 30px);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-bottom: 1px solid #d7d5d5;
|
||||
z-index: 10;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: calc(100% - 10px);
|
||||
height: calc(100% - 10px);
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.left, .right{
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.left {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.right {
|
||||
i {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.input {
|
||||
margin: 0 10px
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -1,29 +1,71 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {ref} from "vue";
|
||||
import {onMounted, ref} from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
const props = withDefaults(defineProps<{
|
||||
value: any,
|
||||
label?: string,
|
||||
disabled?: boolean,
|
||||
type?: string
|
||||
}>()
|
||||
type?: string,
|
||||
dateFormat?: "string" | "number",
|
||||
focus?: boolean
|
||||
}>(), {
|
||||
dateFormat: "string"
|
||||
})
|
||||
|
||||
const emit = defineEmits(["update:value"])
|
||||
const inputEl = ref<HTMLInputElement | undefined>()
|
||||
const focus = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
if(props.focus) {
|
||||
inputEl.value?.focus()
|
||||
}
|
||||
})
|
||||
|
||||
function update($event) {
|
||||
if(props.type == "date" && props.dateFormat == "number") {
|
||||
console.log($event.target.value)
|
||||
emit("update:value", new Date($event.target.value).getTime())
|
||||
}else{
|
||||
emit('update:value', $event.target.value)
|
||||
}
|
||||
}
|
||||
|
||||
function zeros(val) {
|
||||
return val < 10 ? "0" + val : val + ""
|
||||
}
|
||||
function leading(val) {
|
||||
let leading = ""
|
||||
if(val < 10) leading = "000"
|
||||
else if(val < 100) leading = "00"
|
||||
else if(val < 1000) leading = "0"
|
||||
return leading + val
|
||||
}
|
||||
|
||||
function getValue(){
|
||||
if(props.type == "date" && props.dateFormat == "number") {
|
||||
console.log(props.value)
|
||||
const date = new Date(props.value)
|
||||
return leading(date.getFullYear()) + "-" + zeros(date.getMonth() + 1) + "-" + zeros(date.getDate())
|
||||
}else{
|
||||
return props.value
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="input">
|
||||
<label v-if="label" :class="{up: props.value != '' || focus, focus}">{{ label }}</label>
|
||||
<label v-if="label" :class="{up: props.value != '' || focus || type == 'date', focus}">{{ label }}</label>
|
||||
<input
|
||||
:value="value"
|
||||
@input="$emit('update:value', $event.target.value)"
|
||||
:value="getValue()"
|
||||
@input="update"
|
||||
:type="props.type ? props.type : 'text'"
|
||||
:disabled="disabled"
|
||||
@focusin="focus = true"
|
||||
@focusout="focus = false">
|
||||
@focusout="focus = false"
|
||||
ref="inputEl">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import Input from "@/components/Input.vue";
|
||||
import {onMounted, ref} from "vue";
|
||||
import {onMounted, ref, watch} from "vue";
|
||||
import {API} from "@/services/api";
|
||||
import {Auth} from "@/services/auth";
|
||||
|
||||
@@ -21,12 +21,23 @@ onMounted(() => {
|
||||
|
||||
const username = ref("")
|
||||
const password = ref("")
|
||||
const invalid = ref(false)
|
||||
|
||||
watch(props, (value, oldValue, onCleanup) => {
|
||||
if(!value.active) {
|
||||
username.value = ""
|
||||
password.value = ""
|
||||
invalid.value = false
|
||||
}
|
||||
})
|
||||
|
||||
async function attemptLogin() {
|
||||
let login = await Auth.login(username.value, password.value)
|
||||
if(login.success){
|
||||
console.log("success", login)
|
||||
emit("success", login)
|
||||
}else{
|
||||
invalid.value = true
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -37,6 +48,7 @@ async function attemptLogin() {
|
||||
<span class="title">Anmelden</span>
|
||||
<Input class="input" v-model:value="username" label="Nutzername"/>
|
||||
<Input class="input" v-model:value="password" label="Passwort" type="password"/>
|
||||
<span v-if="invalid" style="color: red; text-align: center; margin-bottom: 10px">Benutzername oder<br> Passwort falsch.</span>
|
||||
<button @click="attemptLogin"><i>login</i>Anmelden</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ function two(s) {
|
||||
|
||||
function formatDay(time) {
|
||||
let date = new Date(time)
|
||||
return two(date.getDate()) + "." + two(date.getMonth()) + "."
|
||||
return two(date.getDate()) + "." + two(date.getMonth() + 1) + "."
|
||||
}
|
||||
|
||||
function formatTime(time) {
|
||||
@@ -99,7 +99,7 @@ function getMinistrantClasses(mini: SimplifiedMinistrant) {
|
||||
}
|
||||
|
||||
function getMinis() {
|
||||
return props.ministranten.filter(m => props.editable.includes(m.username))
|
||||
return props.ministranten.filter(m => props.editable.length === 0 || props.editable.includes(m.username))
|
||||
}
|
||||
function getMiniName(mini) {
|
||||
return mini.firstname + " " + mini.lastname
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
const emit = defineEmits(["addPlan", "save"])
|
||||
const emit = defineEmits(["addPlan", "addGodi", "addMini", "save"])
|
||||
const props = defineProps<{
|
||||
save: boolean,
|
||||
plan: boolean,
|
||||
@@ -12,6 +12,8 @@ const props = defineProps<{
|
||||
<div class="action-bar" :class="{save: props.save}">
|
||||
<div class="other-action">
|
||||
<button class="add-plan" :class="{show: props.plan}" @click="$emit('addPlan')"> <i class="icon">add_box</i> Neuer Plan</button>
|
||||
<button class="add-godi" :class="{show: props.godi}" @click="$emit('addGodi')"> <i class="icon">add</i> Gottesdienst</button>
|
||||
<button class="add-mini" :class="{show: props.godi}" @click="$emit('addMini')"> <i class="icon">add</i> Ministrant</button>
|
||||
</div>
|
||||
<button class="save" :class="{show: props.save}" @click="$emit('save')"><i class="icon">save</i> Änderungen speichern </button>
|
||||
</div>
|
||||
@@ -58,7 +60,11 @@ button {
|
||||
}
|
||||
}
|
||||
|
||||
button{
|
||||
@media print {
|
||||
.action-bar{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
import {API} from "@/services/api";
|
||||
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import {onMounted, reactive, ref, toRaw} from "vue";
|
||||
import type {Gottesdienst, Mark, PlanModel, SimplifiedMinistrant} from "@/models/models";
|
||||
import Input from "@/components/Input.vue";
|
||||
import GroupView from "@/components/GroupView.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
gottesdienste: Gottesdienst[],
|
||||
@@ -14,22 +15,13 @@ const props = defineProps<{
|
||||
edit: boolean,
|
||||
smallMode: boolean
|
||||
}>()
|
||||
const emit = defineEmits(["toggleMark", "added", "delete", "endEdit", "resetPassword"])
|
||||
|
||||
const emit = defineEmits(["toggleMark", "added", "delete", "endEdit", "resetPassword", "deleteMinistrant", "createMinistrant", "editMinistrant"])
|
||||
const openEditUser = ref<number>(-1)
|
||||
const miniCopy = reactive<{ data?: SimplifiedMinistrant }>({})
|
||||
const data = reactive({
|
||||
godi: {}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener("keypress", ev => {
|
||||
if(ev.key == "Enter" && props.edit){
|
||||
emit("added", data.godi, () => {
|
||||
data.godi = {}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
function getIconForMark(gid, mid) {
|
||||
const mark = getMark(gid, mid).value
|
||||
@@ -45,7 +37,6 @@ function getIconForMark(gid, mid) {
|
||||
return ""
|
||||
}
|
||||
|
||||
|
||||
function getClassForMark(gid, mid) {
|
||||
const mark = getMark(gid, mid).value
|
||||
return {
|
||||
@@ -74,7 +65,7 @@ function two(s) {
|
||||
|
||||
function formatDay(time) {
|
||||
let date = new Date(time)
|
||||
return two(date.getDate()) + "." + two(date.getMonth()) + "."
|
||||
return two(date.getDate()) + "." + two(date.getMonth() + 1) + "."
|
||||
}
|
||||
|
||||
function formatTime(time) {
|
||||
@@ -112,82 +103,103 @@ function getMinistrantClasses(mini: SimplifiedMinistrant) {
|
||||
edit: props.editable.includes(mini.username)
|
||||
}
|
||||
}
|
||||
|
||||
function toggleEditMinistrant(mini: SimplifiedMinistrant) {
|
||||
console.log("Toggled", miniCopy.data, mini, props.ministranten, openEditUser)
|
||||
if (openEditUser.value == mini.id) {
|
||||
miniCopy.data = undefined
|
||||
openEditUser.value = -1;
|
||||
} else {
|
||||
openEditUser.value = mini.id
|
||||
miniCopy.data = toRaw(mini)
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<table>
|
||||
<div class="container">
|
||||
|
||||
<thead>
|
||||
<table>
|
||||
|
||||
<tr v-if="props.edit">
|
||||
<th></th>
|
||||
<th v-for="godi in props.gottesdienste"><i @click="$emit('delete', godi.id)">delete</i></th>
|
||||
<th><i @click="$emit('endEdit')">close</i></th>
|
||||
</tr>
|
||||
<thead>
|
||||
|
||||
<tr>
|
||||
<th></th>
|
||||
<th v-for="godi in props.gottesdienste">{{ godi.name }}</th>
|
||||
<th class="edit" v-if="props.edit"><Input v-model:value="data.godi.name"/></th>
|
||||
</tr>
|
||||
<tr class="bold">
|
||||
<th>Datum</th>
|
||||
<th v-for="godi in props.gottesdienste">{{ formatDay(godi.date) }}</th>
|
||||
<th class="edit" v-if="props.edit"><Input v-model:value="data.godi.date" type="date"/></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Uhrzeit</th>
|
||||
<th v-for="godi in props.gottesdienste">{{ formatTime(godi.date) }}</th>
|
||||
<th class="edit" v-if="props.edit"><Input v-model:value="data.godi.time" type="time"/></th>
|
||||
</tr>
|
||||
<tr class="bold">
|
||||
<th>Anwesenheit</th>
|
||||
<th v-for="godi in props.gottesdienste">{{ formatTime(godi.attendance) }}</th>
|
||||
<th class="edit" v-if="props.edit"><Input v-model:value="data.godi.attendance" type="time"/></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Wochentag</th>
|
||||
<th v-for="godi in props.gottesdienste">{{ formatWeekday(godi.date) }}</th>
|
||||
<th class="edit" v-if="props.edit"></th>
|
||||
</tr>
|
||||
<tr v-if="props.edit" class="no-print">
|
||||
<th></th>
|
||||
<th v-for="godi in props.gottesdienste"><i @click="$emit('delete', godi.id)">delete</i></th>
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th v-for="godi in props.gottesdienste" class="name">{{ godi.name }}</th>
|
||||
</tr>
|
||||
<tr class="bold">
|
||||
<th>Datum</th>
|
||||
<th v-for="godi in props.gottesdienste">{{ formatDay(godi.date) }}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Uhrzeit</th>
|
||||
<th v-for="godi in props.gottesdienste">{{ formatTime(godi.date) }}</th>
|
||||
</tr>
|
||||
<tr class="bold">
|
||||
<th>Anwesenheit</th>
|
||||
<th v-for="godi in props.gottesdienste">{{ formatTime(godi.attendance) }}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Wochentag</th>
|
||||
<th v-for="godi in props.gottesdienste">{{ formatWeekday(godi.date) }}</th>
|
||||
</tr>
|
||||
|
||||
<tr v-for="mini in props.ministranten" class="ministrant" :class="getMinistrantClasses(mini)">
|
||||
<td class="name"><i v-if="edit" style="margin-right: 10px" @click="$emit('resetPassword', mini.username)">lock_reset</i>{{ mini.id }} {{ mini.firstname }} {{ mini.lastname }}</td>
|
||||
<td
|
||||
v-for="godi in props.gottesdienste"
|
||||
class="mark"
|
||||
:class="getClassForMark(godi.id, mini.id)"
|
||||
@click="$emit('toggleMark', godi.id, mini.id)">
|
||||
<i class="icon"> {{ getIconForMark(godi.id, mini.id) }} </i><br>
|
||||
<span class="hint">{{ getHintForMark(godi.id, mini.id) }}</span>
|
||||
</td>
|
||||
<td class="edit" v-if="props.edit"></td>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<tr v-for="mini in props.ministranten" class="ministrant" :class="getMinistrantClasses(mini)">
|
||||
<td class="name">
|
||||
<div class="center">
|
||||
<i class="edit-button no-print"
|
||||
v-if="edit"
|
||||
style="margin-right: 10px; font-size: 18px"
|
||||
@click="$emit('editMinistrant', mini.id)">edit</i>
|
||||
{{ mini.firstname }}
|
||||
{{ mini.lastname }}
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
v-for="godi in props.gottesdienste"
|
||||
class="mark"
|
||||
:class="getClassForMark(godi.id, mini.id)"
|
||||
@click="$emit('toggleMark', godi.id, mini.id)">
|
||||
<i class="icon"> {{ getIconForMark(godi.id, mini.id) }} </i><br>
|
||||
<span class="hint no-print">{{ getHintForMark(godi.id, mini.id) }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
|
||||
table {
|
||||
border-spacing: 0;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
tr{
|
||||
th{
|
||||
tr {
|
||||
th {
|
||||
font-weight: 400;
|
||||
&.edit{
|
||||
|
||||
&.edit {
|
||||
text-align: start;
|
||||
}
|
||||
padding: 10px;
|
||||
}
|
||||
&.bold th{
|
||||
|
||||
&.bold th {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
@@ -199,9 +211,10 @@ td {
|
||||
td:first-child, th:first-child {
|
||||
padding: 6px 30px 6px 12px;
|
||||
text-align: left;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
td:nth-child(2n), th:nth-child(2n){
|
||||
td:nth-child(2n), th:nth-child(2n) {
|
||||
background: #8ce081;
|
||||
}
|
||||
|
||||
@@ -222,19 +235,15 @@ td:nth-child(2n), th:nth-child(2n){
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
&.minus {
|
||||
background: #fdd5d5;
|
||||
color: #690b0b;
|
||||
|
||||
i {
|
||||
@media not print {
|
||||
&.minus {
|
||||
background: #fdd5d5;
|
||||
color: #690b0b;
|
||||
}
|
||||
}
|
||||
|
||||
&.cross {
|
||||
background: #d1fcd1;
|
||||
color: #045b04;
|
||||
|
||||
i {
|
||||
&.cross {
|
||||
background: #d1fcd1;
|
||||
color: #045b04;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,34 +262,70 @@ td:nth-child(2n), th:nth-child(2n){
|
||||
//mix-blend-mode: difference;
|
||||
}
|
||||
|
||||
|
||||
&:not(.showIcon){
|
||||
&:not(.showIcon) {
|
||||
padding: 0 !important;
|
||||
.hint, br{
|
||||
|
||||
.hint, br {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ministrant {
|
||||
.center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.edit-button {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&:not(.show) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
padding: 20px 10px;
|
||||
|
||||
.input {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ministrant.edit {
|
||||
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
|
||||
z-index: 10;
|
||||
position: relative;
|
||||
|
||||
td {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.name{
|
||||
.name {
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mark{
|
||||
cursor: pointer;
|
||||
&.neutral i {
|
||||
i {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.mark {
|
||||
cursor: pointer;
|
||||
|
||||
@media not print {
|
||||
&.neutral i {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.hint {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
37
public/src/components/dialog/AlertDialog.vue
Normal file
37
public/src/components/dialog/AlertDialog.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import Dialog from "@/components/dialog/Dialog.vue";
|
||||
import type {DialogControls} from "@/components/dialog/dialog";
|
||||
|
||||
interface AlertDialogProps extends DialogControls {
|
||||
title: string,
|
||||
text: string,
|
||||
positive: string,
|
||||
icon?: string
|
||||
}
|
||||
|
||||
const props = defineProps<AlertDialogProps>()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<Dialog class="dialog">
|
||||
<h3 style="margin-bottom: 10px"><i v-if="icon">{{icon}}</i>{{title}}</h3>
|
||||
<p>{{text}}</p>
|
||||
<div class="buttons" style="display: flex; justify-content: end; margin-top: 20px;">
|
||||
<button @click="onPositive" colored>{{positive}}</button>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
.dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 500px
|
||||
}
|
||||
|
||||
</style>
|
||||
39
public/src/components/dialog/ConfirmDialog.vue
Normal file
39
public/src/components/dialog/ConfirmDialog.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import Dialog from "@/components/dialog/Dialog.vue";
|
||||
import type {DialogControls} from "@/components/dialog/dialog";
|
||||
|
||||
interface ConfirmDialogProps extends DialogControls {
|
||||
title: string,
|
||||
text: string,
|
||||
positive: string,
|
||||
negative: string,
|
||||
icon?: string
|
||||
}
|
||||
|
||||
const props = defineProps<ConfirmDialogProps>()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<Dialog class="dialog">
|
||||
<h3 style="margin-bottom: 10px"><i v-if="icon">{{icon}}</i>{{title}}</h3>
|
||||
<p>{{text}}</p>
|
||||
<div class="buttons" style="display: flex; justify-content: end; margin-top: 20px;">
|
||||
<button @click="onNegative">{{negative}}</button>
|
||||
<button @click="onPositive" colored>{{positive}}</button>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
.dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 500px
|
||||
}
|
||||
|
||||
</style>
|
||||
81
public/src/components/dialog/CreateGottesdienstDialog.vue
Normal file
81
public/src/components/dialog/CreateGottesdienstDialog.vue
Normal file
@@ -0,0 +1,81 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import Dialog from "@/components/dialog/Dialog.vue";
|
||||
import type {DialogControls} from "@/components/dialog/dialog";
|
||||
import Input from "@/components/Input.vue";
|
||||
import {onMounted, ref, toRaw} from "vue";
|
||||
import type {Gottesdienst, GottesdienstGroup} from "@/models/models";
|
||||
import {onKeyPress} from "@/composables/enter";
|
||||
interface CreateGottesdienstDialogProps extends DialogControls {
|
||||
onCreate: (Gottesdienst) => (Promise<any> | undefined),
|
||||
godi?: Gottesdienst,
|
||||
planId: number
|
||||
}
|
||||
|
||||
onKeyPress("Enter", create)
|
||||
|
||||
const props = defineProps<CreateGottesdienstDialogProps>()
|
||||
|
||||
const date = ref("")
|
||||
const time = ref("")
|
||||
|
||||
const godi = ref(props.godi ?? {
|
||||
planId: props.planId,
|
||||
date: "",
|
||||
attendance: "",
|
||||
name: "",
|
||||
id: -1
|
||||
})
|
||||
|
||||
let submitted = false
|
||||
|
||||
|
||||
async function create(){
|
||||
if(submitted) return;
|
||||
submitted = true
|
||||
await props.onCreate({
|
||||
planId: godi.value.planId,
|
||||
date: new Date(date.value + "T" + time.value),
|
||||
attendance: new Date(date.value + "T" + godi.value.attendance),
|
||||
name: godi.value.name,
|
||||
id: godi.value.id
|
||||
})
|
||||
props.onDismiss()
|
||||
submitted = false
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<Dialog class="dialog">
|
||||
<h3>Gottesdienst {{ godi.id == -1 ? "erstellen" : "bearbeiten"}}</h3>
|
||||
<Input class="input" v-model:value="godi.name" label="Name" focus/>
|
||||
<Input class="input" v-model:value="date" type="date" label="Datum"/>
|
||||
<Input class="input" v-model:value="time" type="time" label="Um"/>
|
||||
<Input class="input" v-model:value="godi.attendance" type="time" label="Anwesenheit"/>
|
||||
<div class="buttons" style="display: flex; justify-content: end; margin-top: 20px;">
|
||||
<button @click="onDismiss">Abbrechen</button>
|
||||
<button @click="create">{{ godi?.id == -1 ? "Erstellen" : "Speichern"}}</button>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
.dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 500px;
|
||||
|
||||
h3{
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.input {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
107
public/src/components/dialog/CreateMinistrantDialog.vue
Normal file
107
public/src/components/dialog/CreateMinistrantDialog.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import Dialog from "@/components/dialog/Dialog.vue";
|
||||
import type {DialogControls} from "@/components/dialog/dialog";
|
||||
import Input from "@/components/Input.vue";
|
||||
import {ref, toRaw} from "vue";
|
||||
import {onKeyPress} from "@/composables/enter";
|
||||
import {API} from "@/services/api";
|
||||
import {Dialogs} from "@/services/DialogService";
|
||||
interface CreateMinistrantDialogProps extends DialogControls {
|
||||
onCreate: (any) => (Promise<any> | undefined),
|
||||
onDelete: (id) => (Promise<any> | undefined),
|
||||
ministrant?: any
|
||||
}
|
||||
|
||||
onKeyPress("Enter", create)
|
||||
|
||||
const props = defineProps<CreateMinistrantDialogProps>()
|
||||
|
||||
const date = ref("")
|
||||
const time = ref("")
|
||||
|
||||
let submitted = false
|
||||
|
||||
const ministrant = ref(props.ministrant ?? {
|
||||
id: -1,
|
||||
username: "",
|
||||
firstname: "",
|
||||
lastname: "",
|
||||
birthday: "",
|
||||
privileges: "",
|
||||
})
|
||||
|
||||
async function create(){
|
||||
if(submitted) return;
|
||||
submitted = true
|
||||
const { id, username, firstname, lastname, privileges, birthday } = toRaw(ministrant.value)
|
||||
await props.onCreate({
|
||||
id, username, firstname, lastname, privileges,
|
||||
birthday: new Date(birthday)
|
||||
})
|
||||
props.onDismiss()
|
||||
submitted = false
|
||||
}
|
||||
|
||||
async function deleteMinistrant() {
|
||||
if(submitted) return;
|
||||
submitted = true
|
||||
const mini = ministrant.value
|
||||
const shouldDelete = confirm("Möchtest du wirklich " + mini.firstname + " " + mini.lastname + " löschen? Der Ministrant und alle Eintragungen können nicht wiederhergestellt werden!")
|
||||
|
||||
if(!shouldDelete) return;
|
||||
|
||||
await props.onDelete(mini.id);
|
||||
props.onDismiss()
|
||||
submitted = 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)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<Dialog class="dialog">
|
||||
<h3>Ministrant {{ ministrant.id == -1 ? "erstellen" : "bearbeiten"}}</h3>
|
||||
<Input class="input" v-model:value="ministrant.username" label="Nutzername" focus/>
|
||||
<Input class="input" v-model:value="ministrant.firstname" label="Vorname"/>
|
||||
<Input class="input" v-model:value="ministrant.lastname" label="Nachname"/>
|
||||
<Input class="input" v-model:value="ministrant.birthday" type="date" label="Geburtstag"/>
|
||||
<Input class="input" v-model:value="ministrant.privileges" label="Privilegien"/>
|
||||
|
||||
<div class="actions" v-if="ministrant.id != -1">
|
||||
<button @click="resetPassword(ministrant.username)"><i>lock_reset</i>Passwort zurücksetzen
|
||||
</button>
|
||||
<button class="red" @click="deleteMinistrant()"><i>delete</i>Entfernen</button>
|
||||
</div>
|
||||
|
||||
<div class="buttons" style="display: flex; justify-content: end; margin-top: 20px;">
|
||||
<button @click="onDismiss">Abbrechen</button>
|
||||
<button @click="create">{{ ministrant.id == -1 ? "Erstellen" : "Speichern"}}</button>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
.dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 500px;
|
||||
|
||||
h3{
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.input {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
66
public/src/components/dialog/CreatePlanDialog.vue
Normal file
66
public/src/components/dialog/CreatePlanDialog.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import Dialog from "@/components/dialog/Dialog.vue";
|
||||
import type {DialogControls} from "@/components/dialog/dialog";
|
||||
import Input from "@/components/Input.vue";
|
||||
import {ref, toRaw} from "vue";
|
||||
import type {GottesdienstGroup} from "@/models/models";
|
||||
import {onKeyPress} from "@/composables/enter";
|
||||
|
||||
interface CreatePlanDialogProps extends DialogControls {
|
||||
onCreate: (GottesdienstGroup) => (Promise<any> | undefined),
|
||||
group?: GottesdienstGroup
|
||||
}
|
||||
|
||||
onKeyPress("Enter", create)
|
||||
|
||||
const props = defineProps<CreatePlanDialogProps>()
|
||||
let submitted = false;
|
||||
|
||||
const plan = ref<GottesdienstGroup>(props.group ?? {
|
||||
from: "",
|
||||
to: "",
|
||||
id: -1
|
||||
})
|
||||
|
||||
async function create() {
|
||||
if(submitted) return;
|
||||
submitted = true
|
||||
await props.onCreate(toRaw(plan.value))
|
||||
props.onDismiss()
|
||||
submitted = false
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<Dialog class="dialog">
|
||||
<h3>Neuen Plan {{ plan.id == -1 ? "erstellen" : "bearbeiten" }}</h3>
|
||||
<Input class="input" v-model:value="plan.from" type="date" label="Von" focus/>
|
||||
<Input class="input" v-model:value="plan.to" type="date" label="Bis"/>
|
||||
<div class="buttons" style="display: flex; justify-content: end; margin-top: 20px;">
|
||||
<button @click="onDismiss">Abbrechen</button>
|
||||
<button @click="create">{{ plan.id == -1 ? "Erstellen" : "Speichern" }}</button>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
.dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 500px;
|
||||
|
||||
h3{
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.input {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
26
public/src/components/dialog/Dialog.vue
Normal file
26
public/src/components/dialog/Dialog.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import type {DialogOptions} from "@/components/dialog/dialog";
|
||||
|
||||
const props = defineProps<DialogOptions>()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<div class="dialog">
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
.dialog {
|
||||
padding: 10px 30px 30px 30px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 3px 6px rgba(0,0,0,0.08), 0 3px 6px rgba(0,0,0,0.16);
|
||||
}
|
||||
|
||||
</style>
|
||||
76
public/src/components/dialog/DialogHost.vue
Normal file
76
public/src/components/dialog/DialogHost.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import type {Component} from "vue";
|
||||
import {createApp, ref} from "vue";
|
||||
import type {DialogOptions} from "@/components/dialog/dialog";
|
||||
import {Dialogs} from "@/services/DialogService";
|
||||
|
||||
const host = ref<HTMLDivElement>()
|
||||
const active = ref(false)
|
||||
let mountedApplication = null
|
||||
|
||||
Dialogs.subject.subscribe(({component, controls, props}) => {
|
||||
createDialog(component, controls, props)
|
||||
})
|
||||
|
||||
|
||||
|
||||
function createDialog(component: Component, controls: DialogOptions, props: any) {
|
||||
const mergedProps = {
|
||||
onPositive: (...args) => { controls?.onPositive?.call(this, ...args); closeDialog()},
|
||||
onNegative: (...args) => { controls?.onNegative?.call(this, ...args); closeDialog()},
|
||||
onDismiss: closeDialog,
|
||||
...props
|
||||
}
|
||||
|
||||
closeDialog()
|
||||
const app = createApp(component, mergedProps)
|
||||
app.mount(host.value!!)
|
||||
mountedApplication = app
|
||||
active.value = true
|
||||
}
|
||||
|
||||
function closeDialog() {
|
||||
if(mountedApplication != null){
|
||||
mountedApplication.unmount()
|
||||
mountedApplication = null
|
||||
active.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<div class="host" ref="host" :class="{active}" @mousedown.self="closeDialog()">
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
.host {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
z-index: 100;
|
||||
pointer-events: none;
|
||||
transition: 200ms opacity;
|
||||
opacity: 0;
|
||||
|
||||
&.active{
|
||||
pointer-events: auto;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
18
public/src/components/dialog/dialog.ts
Normal file
18
public/src/components/dialog/dialog.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export interface DialogControls {
|
||||
onPositive: () => void,
|
||||
onNegative: () => void,
|
||||
onDismiss: () => void,
|
||||
}
|
||||
|
||||
export interface DialogOptions {
|
||||
onPositive?: () => void,
|
||||
onNegative?: () => void,
|
||||
}
|
||||
|
||||
export interface ConfirmDialogOptions {
|
||||
title: string,
|
||||
text: string,
|
||||
positive: string,
|
||||
negative: string,
|
||||
icon?: string
|
||||
}
|
||||
Reference in New Issue
Block a user