add new event and attendance table with automatic now timestamp
All checks were successful
Build Quasar SPA and Go Backend for memberApp / build-spa (push) Successful in 2m34s
Build Quasar SPA and Go Backend for memberApp / build-backend (amd64, .exe, windows) (push) Successful in 5m39s
Build Quasar SPA and Go Backend for memberApp / build-backend (amd64, , linux) (push) Successful in 5m46s
Build Quasar SPA and Go Backend for memberApp / build-backend (arm, 6, , linux) (push) Successful in 5m32s
Build Quasar SPA and Go Backend for memberApp / build-backend (arm64, , linux) (push) Successful in 5m35s

This commit is contained in:
Adrian Zürcher
2025-11-04 10:59:56 +01:00
parent 632163d751
commit aec741f094
39 changed files with 1343 additions and 229 deletions

View File

@@ -0,0 +1,91 @@
<template>
<DialogFrame ref="dialog" :header-title="localTitle">
<div class="row justify-center">
<q-select
autofocus
class="q-ml-md col-6"
:label="$t('event')"
filled
:options="events"
option-label="name"
option-value="id"
v-model="selected"
@keyup.enter="save"
map-options
emit-value
></q-select>
</div>
<div class="row justify-center">
<q-btn class="q-ma-md" color="primary" no-caps @click="save">{{ $t('save') }}</q-btn>
</div>
</DialogFrame>
</template>
<script setup lang="ts">
import DialogFrame from 'src/vueLib/dialog/DialogFrame.vue';
import { ref } from 'vue';
import { appApi } from 'src/boot/axios';
import { useNotify } from 'src/vueLib/general/useNotify';
import { i18n } from 'src/boot/lang';
import type { Event, Events } from 'src/vueLib/models/event';
import type { Members } from 'src/vueLib/models/member';
import { useAttendeesTable } from 'src/vueLib/tables/attendees/AttendeesTable';
import { useEventTable } from 'src/vueLib/tables/events/EventsTable';
const dialog = ref();
const localTitle = ref('');
const events = ref<Events>([{ id: -1, name: i18n.global.t('add'), attendees: [] }]);
const selected = ref<Event>({ id: -1, name: i18n.global.t('add'), attendees: [] });
const localMembers = ref<Members>([]);
const { updateAttendees } = useAttendeesTable();
const { updateEvents } = useEventTable();
const props = defineProps({
endpoint: {
type: String,
required: true,
},
queryId: {
type: Boolean,
},
});
const emit = defineEmits(['update-event']);
const { NotifyResponse } = useNotify();
function open(title: string, members: Members) {
localTitle.value = title;
localMembers.value = members;
events.value = [{ id: -1, name: i18n.global.t('add'), attendees: [] }];
appApi
.get('events')
.then((resp) => {
events.value.push(...resp.data);
})
.catch((err) => {
NotifyResponse(err, 'error');
});
dialog.value?.open();
}
async function save() {
await appApi
.post(props.endpoint, {
id: selected.value,
attendees: localMembers.value,
})
.then(() => {
emit('update-event', localMembers.value);
NotifyResponse(i18n.global.t('eventAdded'));
dialog.value.close();
})
.catch((err) => {
NotifyResponse(err, 'error');
});
await updateAttendees();
updateEvents();
}
defineExpose({ open });
</script>

View File

@@ -1,17 +1,35 @@
<template>
<DialogFrame ref="dialog" :header-title="'Edit ' + localTitle">
<div class="row justify-center">
<q-input
autofocus
class="q-ml-md col-6"
:label="localTitle"
filled
v-model="value"
@keyup.enter="save"
></q-input>
<q-input autofocus :label="localTitle" filled v-model="value" @keyup.enter="save">
<template v-if="['firstVisit', 'lastVisit', 'date'].includes(localField)" v-slot:prepend>
<q-icon name="event" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-date v-model="value" mask="YYYY-MM-DD HH:mm:ss">
<div class="row items-center justify-end">
<q-btn :label="$t('now')" color="primary" no-caps flat @click="setTimeNow" />
<q-btn no-caps v-close-popup :label="$t('close')" color="primary" flat />
</div>
</q-date>
</q-popup-proxy>
</q-icon>
</template>
<template v-if="['firstVisit', 'lastVisit', 'date'].includes(localField)" v-slot:append>
<q-icon name="access_time" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-time with-seconds v-model="value" mask="YYYY-MM-DD HH:mm:ss" format24h>
<div class="row items-center justify-end">
<q-btn :label="$t('now')" color="primary" no-caps flat @click="setTimeNow" />
<q-btn no-caps v-close-popup :label="$t('close')" color="primary" flat />
</div>
</q-time>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
</div>
<div class="row justify-center">
<q-btn class="q-ma-md" color="primary" no-caps @click="save">Save</q-btn>
<q-btn class="q-ma-md" color="primary" no-caps @click="save">{{ $t('save') }}</q-btn>
</div>
</DialogFrame>
</template>
@@ -22,6 +40,7 @@ import { ref } from 'vue';
import { appApi } from 'src/boot/axios';
import type { Member } from 'src/vueLib/models/member';
import { useNotify } from 'src/vueLib/general/useNotify';
import { i18n } from 'src/boot/lang';
const dialog = ref();
const localMember = ref();
@@ -34,9 +53,6 @@ const props = defineProps({
type: String,
required: true,
},
queryId: {
type: Boolean,
},
});
const emit = defineEmits(['update']);
@@ -52,26 +68,24 @@ function open(label: string, field: string, member: Member) {
}
function save() {
let query = props.endpoint;
if (props.queryId) {
query += '?id=' + localMember.value.id;
}
let payload = {};
if (value.value === localMember.value[localField.value]) {
dialog.value.close();
return;
}
payload = {
id: localMember.value.id,
[localField.value]: value.value,
};
payload = [
{
id: localMember.value.id,
[localField.value]: value.value,
},
];
appApi
.post(query, payload)
.then((resp) => {
.post(props.endpoint, payload)
.then(() => {
emit('update');
NotifyResponse(resp.data);
NotifyResponse(i18n.global.t('memberUpdated'));
dialog.value.close();
})
.catch((err) => {
@@ -79,5 +93,18 @@ function save() {
});
}
function setTimeNow() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0'); // Months are 0-based
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
value.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
defineExpose({ open });
</script>

View File

@@ -0,0 +1,93 @@
<template>
<DialogFrame
ref="dialog"
:header-title="newEvent ? $t('addNewEvent') : 'Edit ' + localEvent.name"
:height="250"
:width="500"
>
<q-form ref="form">
<div class="row justify-center q-gutter-md">
<q-input
class="q-ml-md col-5 required"
:label="$t('eventName')"
filled
:rules="[(val) => !!val || $t('eventNameIsRequired')]"
v-model="localEvent.name"
autofocus
@keyup.enter="save"
></q-input>
</div>
</q-form>
<div class="row justify-center">
<q-btn class="q-ma-md" color="primary" no-caps @click="save">{{ $t('save') }}</q-btn>
</div>
</DialogFrame>
</template>
<script setup lang="ts">
import DialogFrame from 'src/vueLib/dialog/DialogFrame.vue';
import { ref } from 'vue';
import { appApi } from 'src/boot/axios';
import type { Event } from 'src/vueLib/models/event';
import { useNotify } from 'src/vueLib/general/useNotify';
const { NotifyResponse } = useNotify();
const dialog = ref();
const form = ref();
const newEvent = ref(false);
const localEvent = ref<Event>({
id: 0,
name: '',
attendees: [],
});
const emit = defineEmits(['update']);
function open(Event: Event | null) {
if (Event === undefined) {
return;
}
if (Event !== null) {
localEvent.value = Event;
newEvent.value = false;
} else {
localEvent.value = {
id: 0,
name: '',
attendees: [],
};
newEvent.value = true;
}
dialog.value?.open();
}
async function save() {
const valid = await form.value.validate();
if (!valid) return;
let query = 'events/edit?id=' + localEvent.value.id;
if (newEvent.value) {
query = 'events/add?name=' + localEvent.value.name;
}
appApi
.post(query, JSON.stringify(localEvent.value))
.then(() => {
emit('update');
dialog.value.close();
})
.catch((err) => NotifyResponse(err, 'error'));
}
defineExpose({ open });
</script>
<style>
.required .q-field__label::after {
content: ' *';
color: red;
}
</style>

View File

@@ -90,7 +90,7 @@
</div>
</q-form>
<div class="row justify-center">
<q-btn class="q-ma-md" color="primary" no-caps @click="save">Save</q-btn>
<q-btn class="q-ma-md" color="primary" no-caps @click="save">{{ $t('save') }}</q-btn>
</div>
</DialogFrame>
</template>
@@ -101,6 +101,7 @@ import { ref } from 'vue';
import { appApi } from 'src/boot/axios';
import type { Member } from 'src/vueLib/models/member';
import { useNotify } from 'src/vueLib/general/useNotify';
import { i18n } from 'src/boot/lang';
const { NotifyResponse } = useNotify();
const dialog = ref();
@@ -158,18 +159,21 @@ function open(member: Member | null) {
async function save() {
const valid = await form.value.validate();
if (!valid) {
NotifyResponse(i18n.global.t('notAllRequiredFieldsFilled'), 'error');
return;
}
if (!valid) return;
let query = 'members/edit?id=' + localMember.value.id;
let query = 'members/edit';
if (newMember.value) {
query = 'members/add';
}
appApi
.post(query, JSON.stringify(localMember.value))
await appApi
.post(query, JSON.stringify([localMember.value]))
.then(() => {
emit('update-member');
NotifyResponse(i18n.global.t('memberUpdated'));
dialog.value.close();
})
.catch((err) => NotifyResponse(err, 'error'));

View File

@@ -27,7 +27,7 @@
</q-card>
</div>
<div class="row justify-center">
<q-btn class="q-ma-md" color="primary" no-caps @click="save">Save</q-btn>
<q-btn class="q-ma-md" color="primary" no-caps @click="save">{{ $t('save') }}</q-btn>
</div>
</DialogFrame>
</template>

View File

@@ -100,7 +100,7 @@
</div>
</q-form>
<div class="row justify-center">
<q-btn class="q-ma-md" color="primary" no-caps @click="save">Save</q-btn>
<q-btn class="q-ma-md" color="primary" no-caps @click="save">{{ $t('save') }}</q-btn>
</div>
</DialogFrame>
</template>