pipeline and improvements
Some checks failed
Deploy Miniplan / build (push) Failing after 2m23s

This commit is contained in:
walamana
2024-08-07 20:08:52 +02:00
parent 24f14da9b2
commit dde21c3ac5
54 changed files with 1651 additions and 237 deletions

View 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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;
}

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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
}