9 Commits

Author SHA1 Message Date
Adrian Zürcher
31929ea366 lift version
All checks were successful
Build Quasar SPA and Go Backend for memberApp / build-spa (push) Successful in 3m19s
Build Quasar SPA and Go Backend for memberApp / build-backend (amd64, .exe, windows) (push) Successful in 4m20s
Build Quasar SPA and Go Backend for memberApp / build-backend (arm, 6, , linux) (push) Successful in 4m12s
Build Quasar SPA and Go Backend for memberApp / build-backend (arm64, , linux) (push) Successful in 4m14s
Build Quasar SPA and Go Backend for memberApp / build-backend (amd64, , linux) (push) Successful in 4m31s
2026-02-12 12:10:25 +01:00
Adrian Zürcher
095d6411ba add label prop 2026-02-12 12:09:55 +01:00
Adrian Zürcher
031b4d84ec change page localstorage names for new report page 2026-02-12 12:09:44 +01:00
Adrian Zürcher
1657123cc1 add new feature report page 2026-02-12 12:08:55 +01:00
Adrian Zürcher
9a55d4c2f0 fix source string undefined in logo image 2026-02-09 07:38:47 +01:00
Adrian Zürcher
bf481da21e hide image if undefined 2026-02-09 07:36:32 +01:00
Adrian Zürcher
b0d225f7b8 fix add member with with group close #38 2026-02-09 07:28:20 +01:00
Adrian Zürcher
5324787f23 fix language duplicate error 2026-02-09 07:16:11 +01:00
Adrian Zürcher
bb626bf6b5 fix cookie error close #40 2026-02-09 07:15:28 +01:00
19 changed files with 352 additions and 49 deletions

View File

@@ -4,7 +4,7 @@ go 1.25.4
require ( require (
gitea.tecamino.com/paadi/access-handler v1.0.34 gitea.tecamino.com/paadi/access-handler v1.0.34
gitea.tecamino.com/paadi/memberDB v1.1.19 gitea.tecamino.com/paadi/memberDB v1.1.27
gitea.tecamino.com/paadi/tecamino-dbm v0.1.1 gitea.tecamino.com/paadi/tecamino-dbm v0.1.1
gitea.tecamino.com/paadi/tecamino-logger v0.2.1 gitea.tecamino.com/paadi/tecamino-logger v0.2.1
github.com/gin-contrib/cors v1.7.6 github.com/gin-contrib/cors v1.7.6
@@ -14,7 +14,7 @@ require (
) )
require ( require (
gitea.tecamino.com/paadi/dbHandler v1.1.10 // indirect gitea.tecamino.com/paadi/dbHandler v1.1.11 // indirect
github.com/bytedance/sonic v1.14.0 // indirect github.com/bytedance/sonic v1.14.0 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect github.com/cloudwego/base64x v0.1.6 // indirect

View File

@@ -1,9 +1,9 @@
gitea.tecamino.com/paadi/access-handler v1.0.34 h1:6P65HiusSfvgv/ezOvxSahqyRJMK9UrxtGsz6loLoUk= gitea.tecamino.com/paadi/access-handler v1.0.34 h1:6P65HiusSfvgv/ezOvxSahqyRJMK9UrxtGsz6loLoUk=
gitea.tecamino.com/paadi/access-handler v1.0.34/go.mod h1:HyMp1WvzmqLw8Ljt3r1qlF8fY+T5WFXr9Da/CTIM0H8= gitea.tecamino.com/paadi/access-handler v1.0.34/go.mod h1:HyMp1WvzmqLw8Ljt3r1qlF8fY+T5WFXr9Da/CTIM0H8=
gitea.tecamino.com/paadi/dbHandler v1.1.10 h1:zZQbDTJ0bu6CIW90Zms8yYIzTLHtWPNhVKRxLUXEDuE= gitea.tecamino.com/paadi/dbHandler v1.1.11 h1:hTpMWRr4dW7TkiBnEku0/3ggDC7/uP82U9paRKY/QEs=
gitea.tecamino.com/paadi/dbHandler v1.1.10/go.mod h1:y/xn/POJg1DO++67uKvnO23lJQgh+XFQq7HZCS9Getw= gitea.tecamino.com/paadi/dbHandler v1.1.11/go.mod h1:y/xn/POJg1DO++67uKvnO23lJQgh+XFQq7HZCS9Getw=
gitea.tecamino.com/paadi/memberDB v1.1.19 h1:khbhtqS7rXTuOsWwxTO6rm13mIDjnBmJaTcJY4jmpQw= gitea.tecamino.com/paadi/memberDB v1.1.27 h1:UJ/CHKc2CKd+TZ5HiGMK6p/A8wNqGUy9JjpmaPtKrEA=
gitea.tecamino.com/paadi/memberDB v1.1.19/go.mod h1:VBsORoIIhh0/RM5AvmaAjMEM2/cNaIT2TqDL1VDcov4= gitea.tecamino.com/paadi/memberDB v1.1.27/go.mod h1:uLoKel+EcuXUzxAY5ugfWh640TSomfTJR+g8Jfe8YKI=
gitea.tecamino.com/paadi/tecamino-dbm v0.1.1 h1:vAq7mwUxlxJuLzCQSDMrZCwo8ky5usWi9Qz+UP+WnkI= gitea.tecamino.com/paadi/tecamino-dbm v0.1.1 h1:vAq7mwUxlxJuLzCQSDMrZCwo8ky5usWi9Qz+UP+WnkI=
gitea.tecamino.com/paadi/tecamino-dbm v0.1.1/go.mod h1:+tmf1rjPaKEoNeUcr1vdtoFIFweNG3aUGevDAl3NMBk= gitea.tecamino.com/paadi/tecamino-dbm v0.1.1/go.mod h1:+tmf1rjPaKEoNeUcr1vdtoFIFweNG3aUGevDAl3NMBk=
gitea.tecamino.com/paadi/tecamino-logger v0.2.1 h1:sQTBKYPdzn9mmWX2JXZBtGBvNQH7cuXIwsl4TD0aMgE= gitea.tecamino.com/paadi/tecamino-logger v0.2.1 h1:sQTBKYPdzn9mmWX2JXZBtGBvNQH7cuXIwsl4TD0aMgE=

View File

@@ -158,6 +158,8 @@ func main() {
auth.POST("/events/delete/attendees", dbHandler.DeleteAttendee) auth.POST("/events/delete/attendees", dbHandler.DeleteAttendee)
auth.POST("/events/delete", dbHandler.DeleteEvent) auth.POST("/events/delete", dbHandler.DeleteEvent)
auth.POST("/report", dbHandler.GetReport)
auth.POST("/groups/add", dbHandler.NewGroup) auth.POST("/groups/add", dbHandler.NewGroup)
auth.POST("/groups/edit", dbHandler.UpdateGroup) auth.POST("/groups/edit", dbHandler.UpdateGroup)
auth.POST("/groups/delete", dbHandler.DeleteGroup) auth.POST("/groups/delete", dbHandler.DeleteGroup)

View File

@@ -1,6 +1,6 @@
{ {
"name": "lightcontrol", "name": "lightcontrol",
"version": "1.2.4", "version": "1.3.0",
"description": "A Tecamino App", "description": "A Tecamino App",
"productName": "Attendence Records", "productName": "Attendence Records",
"author": "A. Zuercher", "author": "A. Zuercher",

View File

@@ -112,7 +112,7 @@ export default defineConfig((/* ctx */) => {
//directives: [], //directives: [],
// Quasar plugins // Quasar plugins
plugins: ['Notify', 'Dialog'], plugins: ['Cookies', 'Notify', 'Dialog'],
}, },
// animations: 'all', // --- includes all animations // animations: 'all', // --- includes all animations

View File

@@ -156,3 +156,10 @@ numberOfEvents: Anzau Veranstautige
numberOfResponsibles: Anzau Veratwortläche numberOfResponsibles: Anzau Veratwortläche
numberOfGroups: Anzau Gruppe numberOfGroups: Anzau Gruppe
selectDates: Datumuswauh selectDates: Datumuswauh
apply: Awende
minimal: Minimal
maximal: Maximal
average: Durchschnitt
filterEventName: Verastautig filtere
hintFilterEventName: "*'IIgabe'* * oder % als filler vorher oder nächer"
total: Gesamt

View File

@@ -156,3 +156,10 @@ numberOfEvents: Anzahl Veranstaltungen
numberOfResponsibles: Anzahl Verantwortliche numberOfResponsibles: Anzahl Verantwortliche
numberOfGroups: Anzahl Gruppe numberOfGroups: Anzahl Gruppe
selectDates: Datumauswahl selectDates: Datumauswahl
apply: Anwenden
minimal: Minimal
maximal: Maximal
average: Durchschnitt
filterEventName: Veranstaltung filtern
hintFilterEventName: "*'Eingabe'* * oder % als Filler vorher oder nachher"
total: Gesamt

View File

@@ -156,3 +156,10 @@ numberOfEvents: Amount of Events
numberOfResponsibles: Amount of Responsibles numberOfResponsibles: Amount of Responsibles
numberOfGroups: Amount of Groups numberOfGroups: Amount of Groups
selectDates: Dateselection selectDates: Dateselection
apply: Apply
minimal: Minimal
maximal: Maximal
average: Average
filterEventName: filter Events
hintFilterEventName: "*'Input'* * or % as filler before oder after"
total: Total

View File

@@ -145,7 +145,7 @@ FridayShort: Vi
Saturday: Sábado Saturday: Sábado
SaturdayShort: SaturdayShort:
Sunday: Domingo Sunday: Domingo
Sunday: Do SundayShort: Do
currentPassword: Contraseña actual currentPassword: Contraseña actual
addFirstUser: Añadir primer usuario administrador addFirstUser: Añadir primer usuario administrador
report: Informe report: Informe
@@ -156,3 +156,10 @@ numberOfEvents: Cantidad de eventos
numberOfResponsibles: Número de responsables numberOfResponsibles: Número de responsables
numberOfGroups: Número de grupos numberOfGroups: Número de grupos
selectDates: Selección de fecha selectDates: Selección de fecha
apply: Aplicar
minimal: Mínimo
maximal: Máximo
average: Promedio
filterEventName: filtrar eventos
hintFilterEventName: "*''Entrada'* * o % como relleno antes o después"
total: Total

View File

@@ -1,5 +1,8 @@
<template> <template>
<div class="q-gutter-sm"> <div
class="q-ma-sm q-gutter-sm"
:style="{ height: props.height + 'px', width: props.width + 'px' }"
>
<h6 class="text-center text-bold q-ma-md text-primary">{{ props.title }}</h6> <h6 class="text-center text-bold q-ma-md text-primary">{{ props.title }}</h6>
<div class="row"> <div class="row">
<q-checkbox <q-checkbox
@@ -14,30 +17,30 @@
</div> </div>
<div class="row"> <div class="row">
<q-date v-model="dateRange" range flat /> <q-date v-model="dateRange" range flat />
<div>
<q-badge color="secondary"> Total dates selected: {{ filteredDates.length }} </q-badge>
</div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted } from 'vue'; import { ref, onMounted, watch } from 'vue';
import { date } from 'quasar'; import { date } from 'quasar';
import { i18n } from 'src/boot/lang'; import { i18n } from 'src/boot/lang';
const props = defineProps({ const props = defineProps({
title: String, title: String,
height: { type: Number, default: 400 },
width: { type: Number, default: 300 },
}); });
const startDate = new Date(); const startDate = new Date();
// Initial range (format: YYYY/MM/DD) // Initial range (format: YYYY-MM-DD)
const dateRange = ref(); const dateRange = ref<string | { to: string; from: string }>('');
const selectedWeekdays = ref([0, 3]); // Default to weekdays const selectedWeekdays = ref([0, 3]); // Default to weekdays
const emit = defineEmits(['update:dates']);
onMounted(() => { onMounted(() => {
dateRange.value = date.formatDate(startDate, 'YYYY/MM/DD'); dateRange.value = date.formatDate(startDate, 'YYYY-MM-DD');
}); });
const weekdayOptions = [ const weekdayOptions = [
@@ -51,13 +54,14 @@ const weekdayOptions = [
]; ];
// The Logic: Calculate all specific dates within the range that match weekdays // The Logic: Calculate all specific dates within the range that match weekdays
const filteredDates = computed(() => { watch(dateRange, () => {
if (!dateRange.value) { if (!dateRange.value) {
return []; return [];
} else if (typeof dateRange.value === 'string') { } else if (typeof dateRange.value === 'string') {
const current = new Date(dateRange.value); const current = new Date(dateRange.value);
if (current !== undefined && selectedWeekdays.value.includes(current.getDay())) { if (current !== undefined && selectedWeekdays.value.includes(current.getDay())) {
return [date.formatDate(current, 'YYYY/MM/DD')]; emit('update:dates', [date.formatDate(current, 'YYYY-MM-DD')]);
return [date.formatDate(current, 'YYYY-MM-DD')];
} }
return []; return [];
} }
@@ -69,10 +73,12 @@ const filteredDates = computed(() => {
while (current <= end) { while (current <= end) {
if (selectedWeekdays.value.includes(current.getDay())) { if (selectedWeekdays.value.includes(current.getDay())) {
result.push(date.formatDate(current, 'YYYY/MM/DD')); result.push(date.formatDate(current, 'YYYY-MM-DD'));
} }
current = date.addToDate(current, { days: 1 }); current = date.addToDate(current, { days: 1 });
} }
emit('update:dates', result);
return result; return result;
}); });
</script> </script>

View File

@@ -69,7 +69,6 @@
filled filled
emit-value emit-value
map-options map-options
option-value="name"
option-label="name" option-label="name"
v-model="localMember.group" v-model="localMember.group"
></q-select> ></q-select>
@@ -107,7 +106,7 @@
import DialogFrame from 'src/vueLib/dialog/DialogFrame.vue'; import DialogFrame from 'src/vueLib/dialog/DialogFrame.vue';
import { type PropType, ref } from 'vue'; import { type PropType, ref } from 'vue';
import { appApi } from 'src/boot/axios'; import { appApi } from 'src/boot/axios';
import type { Member } from 'src/vueLib/models/member'; import type { Member, Members } from 'src/vueLib/models/member';
import { useNotify } from 'src/vueLib/general/useNotify'; import { useNotify } from 'src/vueLib/general/useNotify';
import { i18n } from 'src/boot/lang'; import { i18n } from 'src/boot/lang';
import type { Responsibles } from 'src/vueLib/models/responsible'; import type { Responsibles } from 'src/vueLib/models/responsible';
@@ -127,7 +126,7 @@ const props = defineProps({
type: Object as PropType<Responsibles>, type: Object as PropType<Responsibles>,
}, },
group: { group: {
type: Array, type: Object as PropType<Members>,
}, },
}); });

View File

@@ -0,0 +1,53 @@
<template>
<q-card class="col-auto">
<div class="row q-col-gutter-xs">
<div v-for="opt in props.amounts" :key="opt.name">
<q-card class="q-ma-xs" flat bordered>
<q-item dense>
<q-item-section>
<q-item-label class="text-bold text-primary text-center">
{{ opt.name }}
</q-item-label>
</q-item-section>
</q-item>
<q-separator />
<q-card-section>
<div class="column justify-between items-center q-mb-xs">
<span class="text-bold text-grey-7 text-caption">{{ $t('events') }}</span>
<q-badge color="black" outline class="text-bold">{{ opt.events }}</q-badge>
</div>
<div class="column justify-between items-center q-mb-xs">
<span class="text-bold text-grey-7 text-caption">{{ $t('minimal') }}</span>
<q-badge color="secondary" outline class="text-bold">{{ opt.minimal }}</q-badge>
</div>
<div class="column justify-between items-center q-mb-xs">
<span class="text-bold text-grey-7 text-caption">{{ $t('average') }}</span>
<q-badge color="secondary" outline class="text-bold">{{ opt.average }}</q-badge>
</div>
<div class="column justify-between items-center q-mb-xs">
<span class="text-bold text-grey-7 text-caption">{{ $t('maximal') }}</span>
<q-badge color="primary" outline class="text-bold">{{ opt.maximal }}</q-badge>
</div>
</q-card-section>
</q-card>
</div>
</div>
</q-card>
</template>
<script setup lang="ts">
import type { Amount } from 'src/vueLib/models/report';
import type { PropType } from 'vue';
const props = defineProps({
amounts: {
type: Object as PropType<Amount[]>,
required: true,
},
});
</script>

View File

@@ -3,11 +3,13 @@
<q-header elevated> <q-header elevated>
<q-toolbar> <q-toolbar>
<q-img <q-img
v-if="localLogo !== undefined && localLogo !== ''"
:src="localLogo" :src="localLogo"
alt="Logo" alt="Logo"
style="width: 40px; height: 40px; background-color: var(--q-primary)" style="width: 40px; height: 40px; background-color: var(--q-primary)"
class="q-mr-sm" class="q-mr-sm"
/> />
<q-btn flat dense round icon="menu" aria-label="Menu" @click="toggleLeftDrawer" /> <q-btn flat dense round icon="menu" aria-label="Menu" @click="toggleLeftDrawer" />
<q-toolbar-title class="text-primary-text"> {{ $t(appName) }} </q-toolbar-title> <q-toolbar-title class="text-primary-text"> {{ $t(appName) }} </q-toolbar-title>
@@ -66,7 +68,7 @@
<q-item v-if="!autorized" to="/login" exact clickable v-ripple @click="closeDrawer"> <q-item v-if="!autorized" to="/login" exact clickable v-ripple @click="closeDrawer">
<q-item-section>{{ $t('login') }}</q-item-section> <q-item-section>{{ $t('login') }}</q-item-section>
</q-item> </q-item>
<!-- <q-item <q-item
v-if="autorized || user.isPermittedTo('members', 'read')" v-if="autorized || user.isPermittedTo('members', 'read')"
to="/report" to="/report"
exact exact
@@ -75,7 +77,7 @@
@click="closeDrawer" @click="closeDrawer"
> >
<q-item-section> {{ $t('report') }}</q-item-section> <q-item-section> {{ $t('report') }}</q-item-section>
</q-item> --> </q-item>
<q-item v-if="autorized" to="/stats" exact clickable v-ripple @click="closeDrawer"> <q-item v-if="autorized" to="/stats" exact clickable v-ripple @click="closeDrawer">
<q-item-section> {{ $t('stats') }}</q-item-section> <q-item-section> {{ $t('stats') }}</q-item-section>
</q-item> </q-item>

View File

@@ -24,8 +24,13 @@ export function getLocalSettings(): Settings {
db = databaseName.value; db = databaseName.value;
} }
let iconName = localStorage.getItem('icon');
if (iconName === undefined || iconName === 'undefined') {
iconName = '';
}
return <Settings>{ return <Settings>{
icon: localStorage.getItem('icon'), icon: iconName,
appName: name, appName: name,
databaseName: db, databaseName: db,
primaryColor: localStorage.getItem('primaryColor'), primaryColor: localStorage.getItem('primaryColor'),
@@ -74,8 +79,8 @@ export function getLocalLanguage(): string | null {
type pageDefault = { type pageDefault = {
page: string; page: string;
filteredColumn: string; stringValue: string;
filteredValue: string[]; filteredValues: string[];
}; };
type pageDefaults = pageDefault[]; type pageDefaults = pageDefault[];
@@ -89,7 +94,7 @@ export function setLocalPageDefaults(
) { ) {
updateOrAddObject( updateOrAddObject(
pageDefaults.value, pageDefaults.value,
{ page: page, filteredColumn: filteredColumn, filteredValue: filteredValue }, { page: page, stringValue: filteredColumn, filteredValues: filteredValue },
'page', 'page',
); );
localStorage.setItem('pageDefaults', JSON.stringify(pageDefaults.value)); localStorage.setItem('pageDefaults', JSON.stringify(pageDefaults.value));

View File

@@ -1,36 +1,233 @@
<template> <template>
<div class="rows"> <q-inner-loading
:showing="loading"
label="Please wait..."
label-class="text-teal"
label-style="font-size: 1.1em"
/>
<div class="colums">
<div class="row justify-end"> <div class="row justify-end">
<div class="column"> <q-btn-dropdown
<DateDaySelect title="hjgjh" /> ref="dropdownRef"
</div> class="q-ma-sm"
color="primary"
:label="$t('selectDates')"
@show="loadSettings"
>
<DateDaySelect @update:dates="updateReport" />
<div class="column justify-end q-pa-md">
<div class="row q-ma-md">
<q-input
class="col-7"
label-color="primary"
:label="$t('filterEventName')"
:hint="$t('hintFilterEventName')"
type="text"
v-model:model-value="filter"
></q-input>
</div>
<div class="row q-ma-md">
<q-select
class="col-7"
:label="$t('filterByColumnValue')"
dense
v-model="group"
:options="groups"
option-label="name"
option-value="id"
multiple
clearable
/>
</div>
<div class="row justify-end">
<q-btn dense class="q-ma-md" color="primary" no-caps @click="applyDateChoice">{{
$t('apply')
}}</q-btn>
</div>
</div>
</q-btn-dropdown>
</div>
</div>
<div class="row justify-center q-ma-xs">
<h3 class="col-12 text-center text-primary text-bold">{{ $t('report') }}</h3>
<ReportStat :amounts="amounts" />
</div>
<div class="row justify-center">
<div
v-if="attendees !== undefined"
:class="
nonAttendees !== undefined ? 'col-12 col-sm-5 col-md-5 q-pa-md' : 'col-12 col-md-8 col-lg-5'
"
>
<q-table
flat
dense
:no-data-label="$t('noDataAvailable')"
:loading-label="$t('loading')"
:rows-per-page-label="$t('recordsPerPage')"
:rows-per-page-options="[0]"
:title="$t('attendees')"
title-class="text-bold text-primary"
:rows="attendees"
:columns="columns"
>
</q-table>
</div>
<div
v-if="nonAttendees !== undefined"
:class="
attendees !== undefined ? 'col-12 col-sm-5 col-md-5 q-pa-md' : 'col-12 col-md-8 col-lg-5'
"
>
<q-table
flat
dense
:title="$t('noneAttendees')"
:no-data-label="$t('noDataAvailable')"
:loading-label="$t('loading')"
:rows-per-page-label="$t('recordsPerPage')"
:rows-per-page-options="[0]"
title-class="text-bold text-primary"
:rows="nonAttendees"
:columns="columns"
>
</q-table>
</div> </div>
</div> </div>
<q-btn @click="openDateSelect">Hallo</q-btn>
<DialogFrame :header-title="$t('selectDates')" :width="350" :height="500" ref="dateSelect">
<DateDaySelect :title="$t('selectDates')" />
<q-btn color="primary" no-caps>{{ $t('apply') }}</q-btn>
</DialogFrame>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { appApi } from 'src/boot/axios'; import { appApi } from 'src/boot/axios';
import DateDaySelect from 'src/components/DateDaySelect.vue'; import DateDaySelect from 'src/components/DateDaySelect.vue';
import { onMounted, ref } from 'vue'; import { computed, onMounted, ref } from 'vue';
import { useNotify } from 'src/vueLib/general/useNotify'; import { useNotify } from 'src/vueLib/general/useNotify';
import DialogFrame from 'src/vueLib/dialog/DialogFrame.vue'; import { i18n } from 'src/boot/lang';
import { databaseName } from 'src/vueLib/models/settings';
import type { Amount } from 'src/vueLib/models/report';
import ReportStat from 'src/components/ReportStat.vue';
import type { Group, Groups } from 'src/vueLib/models/group';
import { getLocalPageDefaults, setLocalPageDefaults } from 'src/localstorage/localStorage';
const dateSelect = ref(); const filter = ref<string>('');
const group = ref<Group[]>([]);
const groups = ref<Groups>([]);
const allDates = ref<string[]>([]);
const attendees = ref();
const nonAttendees = ref();
const { NotifyResponse } = useNotify(); const { NotifyResponse } = useNotify();
const dropdownRef = ref();
const loading = ref(false);
const amounts = ref<Amount[]>([]);
const columns = computed(() => [
{
name: 'firstName',
align: 'left' as const,
label: i18n.global.t('prename'),
field: 'firstName',
sortable: true,
},
{
name: 'lastName',
align: 'left' as const,
label: i18n.global.t('lastName'),
field: 'lastName',
sortable: true,
},
]);
onMounted(() => { onMounted(() => {
loading.value = true;
appApi appApi
.get('events') .post('database/open', { dbPath: databaseName.value })
.then((resp) => console.log(1, resp)) .catch((err) => NotifyResponse(err, 'error'))
.catch((err) => NotifyResponse(err, 'error'));
.finally(() => {
loading.value = false;
});
appApi
.get('/groups')
.then((resp) => (groups.value = resp.data))
.catch((err) => NotifyResponse(err, 'error'))
.finally(() => {
loading.value = false;
});
}); });
function openDateSelect() { function loadSettings() {
dateSelect.value?.open(); const settings = getLocalPageDefaults('report');
if (!settings) return;
if (settings.filteredValues.length > 0) {
group.value = JSON.parse(settings?.filteredValues[0] || '') as Groups;
}
}
function applyDateChoice() {
loading.value = true;
dropdownRef.value.hide();
const payload: { name: null | string[]; date: string[]; groupIds: number[] | null } = {
name: null,
date: allDates.value,
groupIds: null,
};
if (filter.value) {
//name is a array of string and search works as example Test*, Ge*, *st
payload.name = [filter.value];
}
if (group.value) {
// group has to be array of id numbers
payload.groupIds = group.value.map((g) => g.id);
}
payload.date = allDates.value;
setLocalPageDefaults('report', filter.value || '', [JSON.stringify(group.value || '')]);
appApi
.post('report', payload)
.then((resp) => {
attendees.value = [];
nonAttendees.value = [];
if (!resp.data) return;
if (resp.data.data === undefined) return;
const data = resp.data.data;
if (data.data === undefined) return;
if (data.attendees) {
attendees.value = data.attendees;
}
if (data.attendees) {
nonAttendees.value = data.nonAttendees;
}
const days = [
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
'Sunday',
'total',
];
amounts.value = days
.filter((day) => data.data[day]) // Only include days that exist in the response
.map((day) => ({
...data.data[day],
name: i18n.global.t(day), // Dynamically translate the name
}));
if (amounts.value.length == 2) {
amounts.value.splice(1);
}
})
.catch((err) => NotifyResponse(err, 'error'))
.finally(() => (loading.value = false));
}
function updateReport(dates: string[]) {
allDates.value = dates;
} }
</script> </script>

View File

@@ -1,6 +1,7 @@
<template> <template>
<q-select <q-select
ref="selectRef" ref="selectRef"
:label="props.label || ''"
v-model="modelValueLocal" v-model="modelValueLocal"
:options="filteredOptions" :options="filteredOptions"
:option-label="optionLabel" :option-label="optionLabel"
@@ -43,6 +44,9 @@ const props = defineProps({
type: Array as PropType<T[]>, type: Array as PropType<T[]>,
required: true, required: true,
}, },
label: {
type: String,
},
optionLabel: { optionLabel: {
type: [Function, String] as PropType<((option: T) => string) | string | undefined>, type: [Function, String] as PropType<((option: T) => string) | string | undefined>,
required: true, required: true,

View File

@@ -0,0 +1,7 @@
export type Amount = {
name: string;
events: number;
minimal: number;
average: number;
maximal: number;
};

View File

@@ -96,7 +96,7 @@ const open = async (eventArray: number, event: Event) => {
// set custom filter // set custom filter
const defaults = getLocalPageDefaults('attendance'); const defaults = getLocalPageDefaults('attendance');
setNewFilter(defaults?.filteredColumn || '', ...(defaults?.filteredValue ?? [])); setNewFilter(defaults?.stringValue || '', ...(defaults?.filteredValues ?? []));
// set amount of missing attendace // set amount of missing attendace
missingAttendanceAmount.value = filteredMembers.value.length; missingAttendanceAmount.value = filteredMembers.value.length;

View File

@@ -286,8 +286,8 @@ onMounted(() => {
localCompareMembers.value = inProps.compareMembers; localCompareMembers.value = inProps.compareMembers;
const defaults = getLocalPageDefaults(page.value); const defaults = getLocalPageDefaults(page.value);
selectedColumnFilter.value = defaults?.filteredColumn || ''; selectedColumnFilter.value = defaults?.stringValue || '';
selectedColumnOptions.value = defaults?.filteredValue ?? []; selectedColumnOptions.value = defaults?.filteredValues ?? [];
// set custom filter // set custom filter
setNewFilter(selectedColumnFilter.value, ...selectedColumnOptions.value); setNewFilter(selectedColumnFilter.value, ...selectedColumnOptions.value);