first commit

This commit is contained in:
Adrian Zürcher
2025-10-12 14:56:18 +02:00
parent a9f2e11fe6
commit a908db4f38
92 changed files with 13273 additions and 0 deletions

View File

@@ -0,0 +1,139 @@
<template>
<DialogFrame
ref="dialog"
:header-title="
newMember ? 'Add new Member' : 'Edit ' + localMember.firstName + ' ' + localMember.lastName
"
:height="600"
:width="500"
>
<div class="row justify-center q-gutter-md">
<q-input
class="q-ml-md col-5"
label="First Name"
filled
v-model="localMember.firstName"
autofocus
></q-input>
<q-input
class="q-ml-md col-5"
label="Last Name"
filled
v-model="localMember.lastName"
></q-input>
<q-input
class="q-ml-md col-5"
label="Birthday"
filled
v-model="localMember.birthday"
></q-input>
<q-input class="q-ml-md col-5" label="Address" filled v-model="localMember.address"></q-input>
<q-input class="q-ml-md col-5" label="Town" filled v-model="localMember.town"></q-input>
<q-input class="q-ml-md col-5" label="Zip Code" filled v-model="localMember.zip"></q-input>
<q-input class="q-ml-md col-5" label="Phone" filled v-model="localMember.phone"></q-input>
<q-input class="q-ml-md col-5" label="E-Mail" filled v-model="localMember.email"></q-input>
<q-input class="q-ml-md col-5" label="Group" filled v-model="localMember.group"></q-input>
<q-input
class="q-ml-md col-5"
label="Responsible"
filled
v-model="localMember.responsiblePerson"
></q-input>
<q-input
v-if="!newMember"
class="q-ml-md col-11"
label="First Visit"
filled
v-model="localMember.firstVisit"
></q-input>
<q-input
v-if="!newMember"
class="q-ml-md col-11"
label="Last Visit"
filled
v-model="localMember.lastVisit"
></q-input>
</div>
<div class="row justify-center">
<q-btn class="q-ma-md" color="primary" no-caps @click="save">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 { Member } from 'src/vueLib/models/member';
import { useNotify } from 'src/vueLib/general/useNotify';
const { NotifyResponse } = useNotify();
const dialog = ref();
const newMember = ref(false);
const localMember = ref<Member>({
id: 0,
firstName: '',
lastName: '',
birthday: '',
age: '',
address: '',
town: '',
zip: '',
phone: '',
email: '',
group: '',
responsiblePerson: '',
firstVisit: '',
lastVisit: '',
});
const emit = defineEmits(['update-member']);
function open(member: Member | null) {
if (member === undefined) {
return;
}
if (member !== null) {
localMember.value = member;
newMember.value = false;
} else {
localMember.value = {
id: 0,
firstName: '',
lastName: '',
birthday: '',
age: '',
address: '',
town: '',
zip: '',
phone: '',
email: '',
group: '',
responsiblePerson: '',
firstVisit: '',
lastVisit: '',
};
newMember.value = true;
}
dialog.value?.open();
}
function save() {
let query = 'secure/members/edit?id=' + localMember.value.id;
if (newMember.value) {
query = 'ecure/members/add';
}
appApi
.post(query, JSON.stringify(localMember.value))
.then(() => {
emit('update-member', '');
dialog.value.close();
})
.catch((err) => NotifyResponse(err, 'error'));
}
defineExpose({ open });
</script>

View File

@@ -0,0 +1,207 @@
<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>
</div>
<div class="row justify-center">
<q-btn class="q-ma-md" color="primary" no-caps @click="save">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 { Member } from 'src/vueLib/models/member';
import { useNotify } from 'src/vueLib/general/useNotify';
const dialog = ref();
const localMember = ref();
const localTitle = ref('');
const localField = ref('');
const value = ref();
const emit = defineEmits(['update-member']);
const { NotifyResponse } = useNotify();
function open(label: string, field: string, member: Member) {
localTitle.value = label;
localField.value = field;
localMember.value = member;
switch (field) {
case 'firstName':
value.value = member.firstName;
break;
case 'lastName':
value.value = member.lastName;
break;
case 'birthday':
value.value = member.birthday;
break;
case 'address':
value.value = member.address;
break;
case 'town':
value.value = member.town;
break;
case 'zip':
value.value = member.zip;
break;
case 'phone':
value.value = member.phone;
break;
case 'email':
value.value = member.email;
break;
case 'group':
value.value = member.group;
break;
case 'responsiblePerson':
value.value = member.responsiblePerson;
break;
case 'firstVisit':
value.value = member.firstVisit;
break;
case 'lastVisit':
value.value = member.lastVisit;
break;
}
dialog.value?.open();
}
function save() {
const query = 'secure/members/edit?id=' + localMember.value.id;
let payload = {};
switch (localField.value) {
case 'firstName':
if (value.value === localMember.value.firstName) {
dialog.value.close();
return;
}
payload = {
firstName: value.value,
};
break;
case 'lastName':
if (value.value === localMember.value.lastName) {
dialog.value.close();
return;
}
payload = {
lastName: value.value,
};
break;
case 'birthday':
if (value.value === localMember.value.birthday) {
dialog.value.close();
return;
}
payload = {
birthday: value.value,
};
break;
case 'address':
if (value.value === localMember.value.address) {
dialog.value.close();
return;
}
payload = {
address: value.value,
};
break;
case 'town':
if (value.value === localMember.value.town) {
dialog.value.close();
return;
}
payload = {
town: value.value,
};
break;
case 'zip':
if (value.value === localMember.value.zip) {
dialog.value.close();
return;
}
payload = {
zip: value.value,
};
break;
case 'phone':
if (value.value === localMember.value.phone) {
dialog.value.close();
return;
}
payload = {
phone: value.value,
};
break;
case 'email':
if (value.value === localMember.value.email) {
dialog.value.close();
return;
}
payload = {
email: value.value,
};
break;
case 'group':
if (value.value === localMember.value.group) {
dialog.value.close();
return;
}
payload = {
group: value.value,
};
break;
case 'responsiblePerson':
if (value.value === localMember.value.responsiblePerson) {
dialog.value.close();
return;
}
payload = {
responsiblePerson: value.value,
};
break;
case 'firstVisit':
if (value.value === localMember.value.firstVisit) {
dialog.value.close();
return;
}
payload = {
firstVisit: value.value,
};
break;
case 'lastVisit':
if (value.value === localMember.value.lastVisit) {
dialog.value.close();
return;
}
payload = {
lastVisit: value.value,
};
break;
}
appApi
.post(query, payload)
.then(() => {
emit('update-member', '');
dialog.value.close();
})
.catch((err) => {
NotifyResponse(err, 'error');
});
}
defineExpose({ open });
</script>

View File

@@ -0,0 +1,218 @@
<template>
<DialogFrame ref="dialog" header-title="Import CSV" :height="dialogHeight" :width="dialogWidth">
<div class="column q-gutter-xs">
<div class="row">
<q-uploader
style="max-width: 300px"
:url="`http://localhost:` + portApp + `/api/members/import/csv`"
label="Import CSV"
multiple
accept=".csv"
field-name="file"
method="POST"
:form-fields="metaData"
v-on:failed="onImportFail"
:headers="[{ name: 'Accept', value: 'application/json' }]"
@uploaded="onUploaded"
/>
</div>
<q-card :flat="!showOptions">
<div class="row q-ma-sm">
<div class="text-bold q-ma-sm">
<q-btn
dense
flat
size="sm"
:icon="showOptions ? 'remove' : 'add'"
@click="showOptions = !showOptions"
></q-btn>
CSV options
</div>
<div class="row justify-center q-gutter-md">
<q-input
v-if="showOptions"
class="col-4"
label="Row Index of Column titles"
dense
borderless
type="number"
v-model.number="data.rowIndex"
></q-input>
<q-input
v-if="showOptions"
class="col-4"
label="Seperator"
dense
borderless
v-model="data.seperator"
></q-input>
<q-space class="col-1"></q-space>
<q-input
v-if="showOptions"
class="col-2"
label="First Name"
dense
borderless
v-model="data.firstName"
autofocus
></q-input>
<q-input
v-if="showOptions"
class="col-2"
label="Last Name"
dense
borderless
v-model="data.lastName"
></q-input>
<q-input
v-if="showOptions"
class="col-2"
label="Birthday"
dense
borderless
v-model="data.birthday"
></q-input>
<q-input
v-if="showOptions"
class="col-2"
label="Address"
dense
borderless
v-model="data.address"
></q-input>
<q-input
v-if="showOptions"
class="col-2"
label="Town"
dense
borderless
v-model="data.town"
></q-input>
<q-input
v-if="showOptions"
class="col-2"
label="Zip Code"
dense
borderless
v-model="data.zip"
></q-input>
<q-input
v-if="showOptions"
class="col-2"
label="Phone"
dense
borderless
v-model="data.phone"
></q-input>
<q-input
v-if="showOptions"
class="col-2"
label="E-Mail"
dense
borderless
v-model="data.email"
></q-input>
</div>
</div>
</q-card>
<div class="row justify-end q-ma-sm">
<q-btn no-caps color="primary" class="col-3" v-close-popup>{{ $t('close') }}</q-btn>
</div>
</div>
</DialogFrame>
</template>
<script setup lang="ts">
import DialogFrame from 'src/vueLib/dialog/DialogFrame.vue';
import { ref, watch } from 'vue';
import type { MetaData } from 'src/vueLib/models/metaData';
import { useNotify } from 'src/vueLib/general/useNotify';
import { portApp } from 'src/boot/axios';
const dialogHeight = ref(300);
const dialogWidth = ref(500);
const { NotifyResponse } = useNotify();
const emit = defineEmits(['update-upload']);
const dialog = ref();
const showOptions = ref(true);
const data = ref<MetaData>({
rowIndex: 0,
seperator: ';',
firstName: 'firstName',
lastName: 'lastName',
birthday: 'birthday',
address: 'address',
town: 'town',
zip: 'zip',
phone: 'phone',
email: 'email',
});
const metaData = ref([
{ name: 'rowIndex', value: String(data.value.rowIndex) },
{ name: 'seperator', value: data.value.seperator },
{ name: 'firstName', value: data.value.firstName },
{ name: 'lastName', value: data.value.lastName },
{ name: 'birthday', value: data.value.birthday },
{ name: 'address', value: data.value.address },
{ name: 'town', value: data.value.town },
{ name: 'zip', value: data.value.zip },
{ name: 'phone', value: data.value.phone },
{ name: 'email', value: data.value.email },
]);
function open() {
showOptions.value = false;
dialog.value?.open();
}
watch(showOptions, () => {
if (dialogWidth.value === 500) {
dialogWidth.value = 325;
dialogHeight.value = 325;
return;
}
dialogHeight.value = 500;
dialogWidth.value = 500;
});
watch(
data,
() => {
metaData.value = [
{ name: 'rowIndex', value: String(data.value.rowIndex) },
{ name: 'seperator', value: data.value.seperator },
{ name: 'firstName', value: data.value.firstName },
{ name: 'lastName', value: data.value.lastName },
{ name: 'birthday', value: data.value.birthday },
{ name: 'address', value: data.value.address },
{ name: 'town', value: data.value.town },
{ name: 'zip', value: data.value.zip },
{ name: 'phone', value: data.value.phone },
{ name: 'email', value: data.value.email },
];
},
{ deep: true },
);
function onImportFail(info: { files: readonly File[]; xhr: XMLHttpRequest }) {
const response = JSON.parse(info.xhr.responseText);
if (response.message) {
NotifyResponse(response.message, 'error', 15000);
}
emit('update-upload');
}
function onUploaded(info: { files: readonly File[]; xhr: XMLHttpRequest }) {
const response = JSON.parse(info.xhr.responseText);
if (response.message) {
NotifyResponse(response.message);
}
emit('update-upload');
}
defineExpose({ open });
</script>

View File

@@ -0,0 +1,92 @@
<template>
<q-dialog ref="dialog">
<q-card :style="'width:' + props.width">
<q-card-section
v-if="props.dialogLabel"
class="text-h6 text-center"
:class="'text-' + props.labelColor"
>{{ props.dialogLabel }}</q-card-section
>
<q-card-section v-if="props.text" class="text-center" style="white-space: pre-line">{{
props.text
}}</q-card-section>
<q-card-actions align="right" class="text-primary">
<q-btn
v-if="props.buttonCancelLabel"
:flat="props.buttonCancelFlat"
:label="props.buttonCancelLabel"
v-close-popup
>
</q-btn>
<q-btn
v-if="props.buttonOkLabel"
:flat="props.buttonOkFlat"
:label="props.buttonOkLabel"
:color="props.buttonOkColor"
v-close-popup
@click="confirm"
>
</q-btn>
</q-card-actions>
</q-card>
</q-dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const props = defineProps({
buttonOkLabel: {
type: String,
default: 'OK',
},
buttonOkColor: {
type: String,
default: '',
},
buttonOkFlat: {
type: Boolean,
default: true,
},
labelColor: {
type: String,
default: 'primary',
},
dialogLabel: {
type: String,
default: '',
},
text: {
type: String,
default: '',
},
buttonCancelLabel: {
type: String,
default: '',
},
buttonCancelFlat: {
type: Boolean,
default: true,
},
width: {
type: String,
default: '300px',
},
});
const emit = defineEmits(['update-confirm']);
const dialog = ref();
const localInput = ref();
const open = (input?: unknown) => {
localInput.value = input;
dialog.value?.show();
};
const confirm = () => {
emit('update-confirm', localInput.value);
};
defineExpose({ open });
</script>