11 Commits

Author SHA1 Message Date
Adrian Zürcher
73901335a3 new release
All checks were successful
Build Quasar SPA and Go Backend for memberApp / build-spa (push) Successful in 3m5s
Build Quasar SPA and Go Backend for memberApp / build-backend (amd64, .exe, windows) (push) Successful in 6m38s
Build Quasar SPA and Go Backend for memberApp / build-backend (amd64, , linux) (push) Successful in 6m47s
Build Quasar SPA and Go Backend for memberApp / build-backend (arm, 6, , linux) (push) Successful in 6m46s
Build Quasar SPA and Go Backend for memberApp / build-backend (arm64, , linux) (push) Successful in 6m41s
2026-02-13 20:40:21 +01:00
Adrian Zürcher
62aed501f3 fix not updating and hiding added memebers close #49 2026-02-13 20:39:16 +01:00
Adrian Zürcher
43d81dd27a commit forgotten file 2026-02-13 20:17:57 +01:00
Adrian Zürcher
ce654bbb6a fix language switching close #51 2026-02-13 20:16:17 +01:00
Adrian Zürcher
9b2b1d3ef7 fix responsible not showing close #50 2026-02-13 15:26:59 +01:00
Adrian Zürcher
f59443ce5a lift version 2026-02-13 13:10:25 +01:00
Adrian Zürcher
c60907257e minor fixes
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, , linux) (push) Successful in 3m59s
Build Quasar SPA and Go Backend for memberApp / build-backend (amd64, .exe, windows) (push) Successful in 3m45s
Build Quasar SPA and Go Backend for memberApp / build-backend (arm, 6, , linux) (push) Successful in 3m41s
Build Quasar SPA and Go Backend for memberApp / build-backend (arm64, , linux) (push) Successful in 3m38s
2026-02-13 13:08:48 +01:00
Adrian Zürcher
227c57ed41 add label 2026-02-13 13:08:39 +01:00
Adrian Zürcher
c20ce31f04 add save user day settings close #46 2026-02-13 12:28:54 +01:00
Adrian Zürcher
55b6305a5e fix translation switching close #47 close #46 2026-02-13 12:28:26 +01:00
Adrian Zürcher
0026f68320 modify localstorage close #48 2026-02-13 12:27:30 +01:00
15 changed files with 322 additions and 63 deletions

View File

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

View File

@@ -132,8 +132,8 @@ filterByColumn: Spaltenfilter
filterByColumnValue: Spaltenwerte
saveAsDefault: Aus Standard spichere
day: Tag
MondayShort: Mäntig
Monday: Mo
MondayShort: Mo
Monday: Mäntig
Tuesday: Zistig
TuesdayShort: Di
Wednesday: Mittwuch
@@ -165,3 +165,35 @@ hintFilterEventName: "*'IIgabe'* * oder % als filler vorher oder nächer"
total: Gesamt
exportPdf: PDF exportiere
print: Drucke
today: Hüt
week: Wuche
month: Monat
year: Jahr
appName: Applikationsname
calendar:
days:
- 'Suntig'
- 'Mäntig'
- 'Zistig'
- 'Mittwuch'
- 'Donstig'
- 'Fritig'
- 'Samstig'
daysShort: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa']
months:
- 'Januar'
- 'Februar'
- 'März'
- 'April'
- 'Mai'
- 'Juni'
- 'Juli'
- 'Ougust'
- 'Septämber'
- 'Oktober'
- 'Novämber'
- 'Dezämber'
monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez']
firstDayOfWeek: 1
format24h: true
pluralDay: 'Täg'

View File

@@ -165,3 +165,35 @@ hintFilterEventName: "*'Eingabe'* * oder % als Filler vorher oder nachher"
total: Gesamt
exportPdf: PDF exportieren
print: Drucken
today: Heute
week: Woche
month: Monat
year: Jahr
appName: Applikationsname
calendar:
days:
- 'Sonntag'
- 'Montag'
- 'Dienstag'
- 'Mittwoch'
- 'Donnerstag'
- 'Freitag'
- 'Samstag'
daysShort: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa']
months:
- 'Januar'
- 'Februar'
- 'März'
- 'April'
- 'Mai'
- 'Juni'
- 'Juli'
- 'August'
- 'September'
- 'Oktober'
- 'November'
- 'Dezember'
monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez']
firstDayOfWeek: 1
format24h: true
pluralDay: 'Tage'

View File

@@ -165,3 +165,35 @@ hintFilterEventName: "*'Input'* * or % as filler before oder after"
total: Total
exportPdf: export PDF
print: Print
today: Today
week: Week
month: Month
year: Year
appName: Applicationname
calendar:
days:
- 'Sunday'
- 'Monday'
- 'Tuesday'
- 'Wednesday'
- 'Thursday'
- 'Friday'
- 'Saturday'
daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thur', 'Fri', 'Sat']
months:
- 'January'
- 'February'
- 'March'
- 'April'
- 'May'
- 'June'
- 'July'
- 'August'
- 'September'
- 'October'
- 'November'
- 'December'
monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
firstDayOfWeek: 0
format24h: false
pluralDay: 'Days'

View File

@@ -165,3 +165,35 @@ hintFilterEventName: "*''Entrada'* * o % como relleno antes o después"
total: Total
exportPdf: exportar PDF
print: Imprimir
today: Hoy
week: Semana
month: Mes
year: Año
appName: Nombre de la aplicación
calendar:
days:
- 'Domingo'
- 'Lunes'
- 'Martes'
- 'Miércoles'
- 'Jueves'
- 'Viernes'
- 'Sábado'
daysShort: ['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb']
months:
- 'Enero'
- 'Febrero'
- 'Marzo'
- 'Abril'
- 'Mayo'
- 'Junio'
- 'Julio'
- 'Agosto'
- 'Septiembre'
- 'Octubre'
- 'Noviembre'
- 'Diciembre'
monthsShort: ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic']
firstDayOfWeek: 1
format24h: true
pluralDay: 'dias'

View File

@@ -14,29 +14,50 @@
:val="opt.value"
:label="opt.label"
/>
<q-tabs
v-model="activeTab"
dense
class="text-primary"
active-color="primary"
indicator-color="primary"
align="justify"
narrow-indicator
@update:model-value="onTabChange"
>
<q-tab no-caps name="today" :label="$t('today')" />
<q-tab no-caps name="week" :label="$t('week')" />
<q-tab no-caps name="month" :label="$t('month')" />
<q-tab no-caps name="year" :label="$t('year')" />
</q-tabs>
</div>
<div class="row">
<q-date v-model="dateRange" range flat />
<q-date :locale="calendarLanguage" v-model="dateRange" range flat />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue';
import { ref, onMounted, watch, type PropType, computed } from 'vue';
import { date } from 'quasar';
import { i18n } from 'src/boot/lang';
import type { QDateLocale } from 'src/vueLib/models/qDateLocale';
const props = defineProps({
title: String,
height: { type: Number, default: 400 },
width: { type: Number, default: 300 },
});
const weekdays = defineModel('weekdays', {
type: Array as PropType<number[]>,
default: () => [0, 3],
});
const startDate = new Date();
const activeTab = ref('');
// Initial range (format: YYYY-MM-DD)
const dateRange = ref<string | { to: string; from: string }>('');
const selectedWeekdays = ref([0, 3]); // Default to weekdays
const selectedWeekdays = ref(weekdays); // Default to weekdays
const emit = defineEmits(['update:dates']);
onMounted(() => {
@@ -53,6 +74,28 @@ const weekdayOptions = [
{ label: i18n.global.t('SundayShort'), value: 0 },
];
const calendarLanguage = computed(() => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
const localeData = i18n.global.tm('calendar') as unknown as QDateLocale;
return {
days: localeData.days,
daysShort: localeData.daysShort,
months: localeData.months,
monthsShort: localeData.monthsShort,
firstDayOfWeek: localeData.firstDayOfWeek,
format24h: localeData.format24h,
pluralDay: localeData.pluralDay,
};
});
const onTabChange = (val: 'today' | 'week' | 'month' | 'year') => {
if (val) setRange(val);
// Optional: Reset tab to empty so user can click the same tab again later
setTimeout(() => {
activeTab.value = '';
}, 500);
};
// The Logic: Calculate all specific dates within the range that match weekdays
watch(dateRange, () => {
if (!dateRange.value) {
@@ -81,4 +124,81 @@ watch(dateRange, () => {
emit('update:dates', result);
return result;
});
/**
* Logic for 1 Week / 1 Month / 1 Year
*/
const setRange = (type: 'today' | 'week' | 'month' | 'year') => {
const anchor =
typeof dateRange.value === 'string'
? new Date(dateRange.value)
: new Date(dateRange.value.from);
let from: Date;
let to: Date;
if (type === 'today') {
const now = new Date();
dateRange.value = date.formatDate(date.startOfDate(now, 'day'), 'YYYY-MM-DD');
return;
} else if (type === 'week') {
// getDay() returns 0 for Sunday.
// We calculate how many days to subtract to get to Monday (1).
const day = anchor.getDay();
const diffToMonday = day === 0 ? -6 : 1 - day;
from = date.addToDate(anchor, { days: diffToMonday });
from = date.startOfDate(from, 'day'); // Reset time to 00:00
to = date.addToDate(from, { days: 6 });
to = date.endOfDate(to, 'day'); // Set time to 23:59
} else if (type === 'month') {
// 'month' is a valid unit for startOfDate
from = date.startOfDate(anchor, 'month');
to = date.endOfDate(anchor, 'month');
} else {
// 'year' is a valid unit for startOfDate
from = date.startOfDate(anchor, 'year');
to = date.endOfDate(anchor, 'year');
}
dateRange.value = {
from: date.formatDate(from, 'YYYY/MM/DD'),
to: date.formatDate(to, 'YYYY/MM/DD'),
};
};
watch(
[dateRange, selectedWeekdays],
() => {
if (!dateRange.value) {
emit('update:dates', []);
return;
}
let start: Date, end: Date;
if (typeof dateRange.value === 'string') {
start = new Date(dateRange.value);
end = new Date(dateRange.value);
} else {
start = new Date(dateRange.value.from);
end = new Date(dateRange.value.to);
}
const result = [];
let current = start;
while (current <= end) {
if (selectedWeekdays.value.includes(current.getDay())) {
result.push(date.formatDate(current, 'YYYY-MM-DD'));
}
current = date.addToDate(current, { days: 1 });
}
emit('update:dates', result);
},
{ deep: true },
);
</script>

View File

@@ -77,7 +77,7 @@
:label="$t('responsible')"
filled
:options="props.responsibles"
:option-label="(opt) => opt.firstName + ' ' + opt.lastName"
:option-label="(opt) => opt.member.firstName + ' ' + opt.member.lastName"
v-model="localMember.responsible"
></q-select>
<q-input

View File

@@ -6,7 +6,7 @@
<q-item dense>
<q-item-section>
<q-item-label class="text-bold text-primary text-center">
{{ opt.name }}
{{ $t(opt.name) }}
</q-item-label>
</q-item-section>
</q-item>
@@ -46,7 +46,7 @@ import type { PropType } from 'vue';
const props = defineProps({
amounts: {
type: Object as PropType<Amount[]>,
type: Array as PropType<Amount[]>,
required: true,
},
});

View File

@@ -1,6 +1,5 @@
import { Dark } from 'quasar';
import { appName, databaseName, type Settings } from 'src/vueLib/models/settings';
import { updateOrAddObject } from 'src/vueLib/utils/utils';
import { ref } from 'vue';
export function setLocalSettings(settings: Settings) {
@@ -79,30 +78,19 @@ export function getLocalLanguage(): string | null {
type pageDefault = {
page: string;
stringValue: string;
filteredValues: string[];
data: unknown;
};
type pageDefaults = pageDefault[];
const pageDefaults = ref<pageDefaults>([]);
export function setLocalPageDefaults(
page: string,
filteredColumn?: string,
filteredValue?: string[],
) {
updateOrAddObject(
pageDefaults.value,
{ page: page, stringValue: filteredColumn, filteredValues: filteredValue },
'page',
);
localStorage.setItem('pageDefaults', JSON.stringify(pageDefaults.value));
export function setLocalPageDefaults(page: string, data: unknown) {
localStorage.setItem(page + 'Defaults', JSON.stringify(data));
}
export function getLocalPageDefaults(page: string): pageDefault | null {
const defaults = localStorage.getItem('pageDefaults');
export function getLocalPageDefaults(page: string): unknown {
const defaults = localStorage.getItem(page + 'Defaults');
if (!defaults) return null;
pageDefaults.value = JSON.parse(defaults);
return pageDefaults.value.find((e) => e.page === page) || null;
return JSON.parse(defaults);
}

View File

@@ -16,7 +16,7 @@
:label="$t('selectDates')"
@show="loadSettings"
>
<DateDaySelect @update:dates="updateReport" />
<DateDaySelect @update:dates="updateReport" v-model:weekdays="weekdays" />
<div class="column justify-end q-pa-md">
<div class="row q-ma-md">
<q-input
@@ -136,6 +136,7 @@ import ReportStat from 'src/components/ReportStat.vue';
import type { Group, Groups } from 'src/vueLib/models/group';
import { getLocalPageDefaults, setLocalPageDefaults } from 'src/localstorage/localStorage';
import html2pdf from 'html2pdf.js';
import type { PageDefault } from 'src/vueLib/models/pageDefaults';
const filter = ref<string>('');
const group = ref<Group[]>([]);
@@ -148,6 +149,7 @@ const dropdownRef = ref();
const loading = ref(false);
const amounts = ref<Amount[]>([]);
const reportExportRef = ref<HTMLElement | null>(null);
const weekdays = ref<number[]>([0, 3]);
const columns = computed(() => [
{
@@ -186,10 +188,13 @@ onMounted(() => {
});
function loadSettings() {
const settings = getLocalPageDefaults('report');
const settings = getLocalPageDefaults('report') as PageDefault;
if (!settings) return;
if (settings.filteredValues.length > 0) {
group.value = JSON.parse(settings?.filteredValues[0] || '') as Groups;
if (settings.groups) {
group.value = settings.groups;
}
if (settings.weekdays) {
weekdays.value = settings.weekdays;
}
}
function applyDateChoice() {
@@ -214,7 +219,7 @@ function applyDateChoice() {
}
payload.date = allDates.value;
setLocalPageDefaults('report', filter.value || '', [JSON.stringify(group.value || '')]);
setLocalPageDefaults('report', <PageDefault>{ groups: group.value, weekdays: weekdays.value });
appApi
.post('report', payload)
@@ -248,7 +253,7 @@ function applyDateChoice() {
.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
name: day, // Dynamically translate the name
}));
if (amounts.value.length == 2) {
amounts.value.splice(1);

View File

@@ -7,25 +7,17 @@
<p class="text-bold text-h6 text-primary q-pa-md">{{ $t('general') }}</p>
<div class="row">
<q-input
dense
:readonly="!user.isPermittedTo('settings', 'write')"
:class="[
colorGroup ? 'col-md-4' : 'col-md-3',
colorGroup ? 'col-md-6' : 'col-md-6',
colorGroup ? 'col-md-4' : 'col-md-12',
'q-pa-md',
]"
:class="[colorGroup ? ' col-md-4' : 'col-md-12', 'q-pa-md']"
filled
:label="$t('appName')"
v-model="settings.appName"
></q-input>
<q-input
dense
:readonly="!user.isPermittedTo('settings', 'write')"
:class="[
colorGroup ? 'col-md-4' : 'col-md-3',
colorGroup ? 'col-md-6' : 'col-md-6',
colorGroup ? 'col-md-4' : 'col-md-12',
'q-pa-md',
]"
:class="[colorGroup ? 'col-md-4' : 'col-md-12', 'q-pa-md']"
filled
:label="$t('icon')"
v-model="settings.icon"
@@ -36,13 +28,9 @@
<p class="text-bold text-h6 text-primary q-pa-md">{{ $t('database') }}</p>
<div class="row">
<q-input
dense
:readonly="!user.isPermittedTo('settings', 'write')"
:class="[
colorGroup ? 'col-md-4' : 'col-md-3',
colorGroup ? 'col-md-6' : 'col-md-6',
colorGroup ? 'col-md-4' : 'col-md-12',
'q-pa-md',
]"
:class="[colorGroup ? 'col-md-4' : 'col-md-12', 'q-pa-md']"
filled
:label="$t('databaseName')"
v-model="settings.databaseName"

View File

@@ -0,0 +1,8 @@
import type { Group } from './group';
export type PageDefault = {
groups?: Group[];
selectedColumnFilter?: string;
selectedColumnOptions?: string[];
weekdays?: number[];
};

View File

@@ -0,0 +1,9 @@
export interface QDateLocale {
days: string[];
daysShort: string[];
months: string[];
monthsShort: string[];
firstDayOfWeek: number;
format24h: boolean;
pluralDay: string;
}

View File

@@ -66,6 +66,7 @@ import { ref } from 'vue';
import { useAttendeesTable } from './AttendeesTable';
import { useMemberTable } from '../members/MembersTable';
import { getLocalPageDefaults } from 'src/localstorage/localStorage';
import type { PageDefault } from 'src/vueLib/models/pageDefaults';
//use constants and function of imports
const { attendees, updateAttendees } = useAttendeesTable();
@@ -95,8 +96,10 @@ const open = async (eventArray: number, event: Event) => {
await updateMembers(event.attendees);
// set custom filter
const defaults = getLocalPageDefaults('attendance');
setNewFilter(defaults?.stringValue || '', ...(defaults?.filteredValues ?? []));
const settings = getLocalPageDefaults('attendance') as PageDefault;
if (settings) {
setNewFilter(settings.selectedColumnFilter || '', ...(settings.selectedColumnOptions ?? []));
}
// set amount of missing attendace
missingAttendanceAmount.value = filteredMembers.value.length;

View File

@@ -209,12 +209,13 @@ import AddToEvent from 'src/components/AddToEvent.vue';
import { databaseName } from 'src/vueLib/models/settings';
import { useUserStore } from 'src/vueLib/login/userStore';
import { i18n } from 'src/boot/lang';
import { getLocalPageDefaults, setLocalPageDefaults } from 'src/localstorage/localStorage';
import type { Responsible } from 'src/vueLib/models/responsible';
import type { QTableColumn } from 'quasar';
import SearchableInput from '../components/SearchableInput.vue';
import FilterSelect from '../components/FilterSelect.vue';
import TopButtonGroup from '../components/TopButtonGroup.vue';
import { getLocalPageDefaults, setLocalPageDefaults } from 'src/localstorage/localStorage';
import type { PageDefault } from 'src/vueLib/models/pageDefaults';
const inProps = defineProps({
addAttendees: { type: Boolean },
@@ -285,9 +286,11 @@ onMounted(() => {
loading.value = true;
localCompareMembers.value = inProps.compareMembers;
const defaults = getLocalPageDefaults(page.value);
selectedColumnFilter.value = defaults?.stringValue || '';
selectedColumnOptions.value = defaults?.filteredValues ?? [];
const settings = getLocalPageDefaults(page.value) as PageDefault;
if (settings) {
selectedColumnFilter.value = settings.selectedColumnFilter || '';
selectedColumnOptions.value = settings.selectedColumnOptions || [];
}
// set custom filter
setNewFilter(selectedColumnFilter.value, ...selectedColumnOptions.value);
@@ -304,8 +307,11 @@ onMounted(() => {
});
});
async function updateTable() {
async function updateTable(add?: Members) {
localCompareMembers.value = inProps.compareMembers;
if (add) {
localCompareMembers.value?.push(...add);
}
await updateMembers(localCompareMembers.value, inProps.addResponsible).catch((err) =>
NotifyResponse(err, 'error'),
);
@@ -369,7 +375,11 @@ function setColumnOptions(columnName: string) {
async function filterMembers() {
setNewFilter(selectedColumnFilter.value, ...(selectedColumnOptions.value || []));
setLocalPageDefaults(page.value, selectedColumnFilter.value, selectedColumnOptions.value || []);
const settings = <PageDefault>{
selectedColumnFilter: selectedColumnFilter.value,
selectedColumnOptions: selectedColumnOptions.value,
};
setLocalPageDefaults(page.value, settings);
await updateTable();
}
@@ -445,15 +455,15 @@ async function addMemberTo() {
})
.catch((err) => {
NotifyResponse(err, 'error');
})
.finally(() => (selected.value = []));
});
if (inProps.addAttendees) {
await updateMemberLastVisit(selected.value);
} else {
await updateTable();
await updateTable(selected.value);
emit('update-event', filteredMembers.value.length);
}
selected.value = [];
}
async function updateMemberLastVisit(members: Members) {