9 Commits

Author SHA1 Message Date
Adrian Zürcher
87297a3b97 add tooltip 2025-11-12 10:22:22 +01:00
Adrian Zürcher
b12574994c make drawer size change for smaller screens 2025-11-12 10:22:12 +01:00
Adrian Zürcher
66bb7c1942 fix darkmode for dialog and add tooltip translation close #20 2025-11-12 10:21:42 +01:00
Adrian Zürcher
f8b79de6a2 make app name changable and move settings parameter to settings close #19 2025-11-12 10:20:44 +01:00
Adrian Zürcher
c7fe7490f1 change to new logo with icongenie form quasar close #17 2025-11-12 10:18:29 +01:00
Adrian Zürcher
334c14a307 add login logout translation 2025-11-11 16:05:25 +01:00
Adrian Zürcher
b9f009162d remove console logs 2025-11-11 16:05:07 +01:00
Adrian Zürcher
b844f487bc make darkmode persistent close #15 2025-11-11 16:04:55 +01:00
Adrian Zürcher
829dc074e2 change to personal favicon (could be improved) close #14 2025-11-11 16:04:22 +01:00
23 changed files with 103 additions and 50 deletions

View File

@@ -1,19 +1,22 @@
<!DOCTYPE html>
<!doctype html>
<html>
<head>
<title><%= productName %></title>
<meta charset="utf-8">
<meta name="description" content="<%= productDescription %>">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>">
<meta charset="utf-8" />
<meta name="description" content="<%= productDescription %>" />
<meta name="format-detection" content="telephone=no" />
<meta name="msapplication-tap-highlight" content="no" />
<meta
name="viewport"
content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>"
/>
<link rel="icon" type="image/png" sizes="128x128" href="icons/favicon-128x128.png">
<link rel="icon" type="image/png" sizes="96x96" href="icons/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png">
<link rel="icon" type="image/ico" href="favicon.ico">
<link rel="icon" type="image/png" sizes="128x128" href="icons/favicon-128x128.png" />
<link rel="icon" type="image/png" sizes="96x96" href="icons/favicon-96x96.png" />
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png" />
<link rel="icon" type="image/ico" href="favicon.ico" />
</head>
<body>
<!-- quasar:entry-point -->

View File

@@ -2,7 +2,7 @@
"name": "lightcontrol",
"version": "1.0.8",
"description": "A Tecamino App",
"productName": "Member Database",
"productName": "Attendence Records",
"author": "A. Zuercher",
"type": "module",
"private": true,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 859 B

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@@ -32,6 +32,7 @@ settings: Iistellige
databaseName: Datebank Name
token: Schlüssu
login: Amäude
logout: Abmäude
user: Benutzer
password: Passwort
isRequired: isch erforderlich
@@ -115,3 +116,5 @@ expiration: Ablauf
never: Nie
responsibles: Verantwortliche
comment: Bemerkung
dark_mode: Dunkel-Modus
light_mode: Hell-Modus

View File

@@ -32,6 +32,7 @@ settings: Einstellungen
databaseName: Datenbank Name
token: Schlüssel
login: Anmelden
logout: Abmelden
user: Benutzer
password: Passwort
isRequired: ist erforderlich
@@ -115,3 +116,5 @@ expiration: Ablauf
never: Nie
responsibles: Verantwortliche
comment: Bemerkung
dark_mode: Dunkel-Modus
light_mode: Hell-Modus

View File

@@ -32,6 +32,7 @@ settings: Settings
databaseName: Database Name
token: Token
login: Login
logout: Logout
user: User
password: Password
isRequired: is required
@@ -115,3 +116,5 @@ expiration: Expiration
never: Never
responsibles: Responsibles
comment: Comment
dark_mode: Dark-Mode
light_mode: Light-Mode

View File

@@ -1,15 +1,18 @@
import { boot } from 'quasar/wrappers';
import { setQuasarInstance } from 'src/vueLib/utils/globalQ';
import { setRouterInstance } from 'src/vueLib/utils/globalRouter';
import { databaseName } from 'src/vueLib/tables/members/MembersTable';
import { Logo } from 'src/vueLib/models/logo';
import { databaseName, logo, appName } from 'src/vueLib/models/settings';
import { Dark } from 'quasar';
export default boot(({ app, router }) => {
setRouterInstance(router); // store router for global access
const $q = app.config.globalProperties.$q;
setQuasarInstance($q);
Logo.value = localStorage.getItem('icon') ?? Logo.value;
Dark.set(localStorage.getItem('mode') === 'true');
logo.value = localStorage.getItem('icon') ?? logo.value;
appName.value = localStorage.getItem('appName') ?? appName.value;
databaseName.value = localStorage.getItem('databaseName') ?? databaseName.value;
let primaryColor = localStorage.getItem('primaryColor');
if (primaryColor == null || primaryColor === 'undefined' || primaryColor.trim() === '') {

View File

@@ -10,7 +10,7 @@
/>
<q-btn flat dense round icon="menu" aria-label="Menu" @click="toggleLeftDrawer" />
<q-toolbar-title class="text-primary-text"> {{ productName }} </q-toolbar-title>
<q-toolbar-title class="text-primary-text"> {{ appName }} </q-toolbar-title>
<div>Version {{ version }}</div>
<q-btn dense icon="refresh" square class="q-px-md q-ml-md" @click="refresh" />
@@ -18,7 +18,7 @@
</q-toolbar>
</q-header>
<q-drawer v-model="leftDrawerOpen" bordered>
<q-drawer v-model="leftDrawerOpen" bordered :width="drawerWidth" :overlay="$q.screen.lt.sm">
<q-list>
<q-item v-if="!autorized" to="/login" exact clickable v-ripple @click="closeDrawer">
<q-item-section>{{ $t('login') }}</q-item-section>
@@ -63,12 +63,13 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { version, productName } from '../../package.json';
import { version } from '../../package.json';
import LoginMenu from 'src/vueLib/login/LoginMenu.vue';
import { useUserStore } from 'src/vueLib/login/userStore';
import { Logo } from 'src/vueLib/models/logo';
import { logo, appName } from 'src/vueLib/models/settings';
import { useQuasar } from 'quasar';
const localLogo = ref(Logo);
const localLogo = ref(logo);
const leftDrawerOpen = ref(false);
const user = useUserStore();
@@ -87,4 +88,10 @@ function closeDrawer() {
function refresh() {
window.location.reload();
}
// Change width based on screen size
const drawerWidth = computed(() => {
const $q = useQuasar();
return $q.screen.lt.sm ? 220 : 300; // phone: 220px, desktop: 300px
});
</script>

View File

@@ -7,6 +7,18 @@
<q-card class="q-ma-lg">
<p class="text-bold text-h6 text-primary q-pa-md">{{ $t('general') }}</p>
<div class="row">
<q-input
: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',
]"
filled
:label="$t('appName')"
v-model="settings.appName"
></q-input>
<q-input
:readonly="!user.isPermittedTo('settings', 'write')"
:class="[
@@ -133,8 +145,7 @@
</template>
<script setup lang="ts">
import { databaseName } from 'src/vueLib/tables/members/MembersTable';
import { Logo } from 'src/vueLib/models/logo';
import { logo, appName, databaseName } from 'src/vueLib/models/settings';
import { reactive, ref, watch } from 'vue';
import { appApi } from 'src/boot/axios';
import { useNotify } from 'src/vueLib/general/useNotify';
@@ -146,7 +157,8 @@ const colorGroup = ref(false);
const user = useUserStore();
const settings = reactive<Settings>({
icon: Logo.value,
appName: appName.value,
icon: logo.value,
databaseName: databaseName.value,
primaryColor: document.documentElement.style.getPropertyValue('--q-primary'),
primaryColorText: document.documentElement.style.getPropertyValue('--q-primary-text'),
@@ -155,7 +167,8 @@ const settings = reactive<Settings>({
});
watch(settings, (newSettings) => {
Logo.value = newSettings.icon;
logo.value = newSettings.icon;
appName.value = newSettings.appName;
databaseName.value = newSettings.databaseName;
});
@@ -175,8 +188,10 @@ function save() {
document.documentElement.style.setProperty('--q-primary-text', settings.primaryColorText);
document.documentElement.style.setProperty('--q-secondary', settings.secondaryColor);
document.documentElement.style.setProperty('--q-secondary-text', settings.secondaryColorText);
Logo.value = settings.icon;
appName.value = settings.appName;
logo.value = settings.icon;
localStorage.setItem('icon', settings.icon);
localStorage.setItem('appName', settings.appName);
localStorage.setItem('databaseName', settings.databaseName);
localStorage.setItem('primaryColor', settings.primaryColor);
localStorage.setItem('primaryColorText', settings.primaryColorText);

View File

@@ -7,10 +7,10 @@
:no-refocus="!minMaxState"
:seamless="!minMaxState"
>
<q-card class="layout" :style="cardStyle">
<q-card class="layout bg-surface text-on-surface" :style="cardStyle">
<!-- Draggable Header -->
<div
class="dialog-header row items-center justify-between bg-grey-1"
class="dialog-header row items-center justify-between bg-transparent"
v-touch-pan.mouse.prevent.stop="handlePan"
>
<div v-if="headerTitle" class="text-left text-bold text-caption q-mx-sm">
@@ -139,13 +139,11 @@ const cardStyle = computed(() => {
display: flex;
flex-direction: column;
border-radius: 10px;
background-color: white;
}
/* Draggable header */
.dialog-header {
padding: 8px 0;
background: #f5f5f5;
cursor: move;
user-select: none;
}

View File

@@ -3,7 +3,7 @@
<q-form ref="refForm">
<q-item-section class="q-gutter-md q-pa-md">
<q-card :class="['q-gutter-xs q-items-center q-pa-lg', { shake: shake }]">
<div class="text-h5 text-primary text-center">{{ productName }}</div>
<div class="text-h5 text-primary text-center">{{ appName }}</div>
<q-input
ref="refUserInput"
dense
@@ -47,7 +47,7 @@
</template>
<script setup lang="ts">
import { productName } from '../../../package.json';
import { appName } from '../models/settings';
import { ref } from 'vue';
import { useNotify } from '../general/useNotify';
import { useLogin } from './useLogin';

View File

@@ -8,7 +8,9 @@
<q-item-section class="text-primary">{{ loginText }}</q-item-section>
</q-item>
<q-item>
<q-btn flat :icon="Dark.mode ? 'light_mode' : 'dark_mode'" @click="Dark.toggle"></q-btn>
<q-btn flat :icon="darkMode" @click="toggleDarkMode"
><q-tooltip>{{ $t(darkMode) }}</q-tooltip></q-btn
>
</q-item>
<q-item>
<q-select
@@ -18,7 +20,8 @@
dense
v-model="langSelected"
:options="langSelection"
></q-select>
><q-tooltip>{{ $t('language') }}</q-tooltip></q-select
>
</q-item>
<q-item
v-if="
@@ -54,21 +57,34 @@ import { useLogin } from './useLogin';
const userLogin = useLogin();
const route = useRoute();
const refLoginDialog = ref();
const user = useUserStore();
const { NotifyResponse } = useNotify();
const currentUser = computed(() => user.user);
const darkMode = computed(() => {
if (Dark.mode) {
return 'light_mode';
}
return 'dark_mode';
});
const showLogin = computed(
() => (route.path !== '/' && route.path !== '/login') || currentUser.value?.username === '',
);
const user = useUserStore();
const autorized = computed(() => !!user.isAuthorizedAs(['admin']));
const { NotifyResponse } = useNotify();
const currentUser = computed(() => user.user);
// switch between logged in or logged out text
const loginText = computed(() => {
return currentUser.value ? 'Logout' : 'Login';
return currentUser.value ? i18n.global.t('logout') : i18n.global.t('login');
});
const refLoginDialog = ref();
//switch between dark and light mode and save it in localStorage
function toggleDarkMode() {
Dark.toggle();
localStorage.setItem('mode', String(Dark.mode));
}
// opens login page if no user is logged in otherwise it serves as logout
function openLogin() {
if (currentUser.value) {
userLogin.logout().catch((err) => NotifyResponse(err, 'error'));

View File

@@ -2,7 +2,7 @@ import { appApi } from 'src/boot/axios';
import { useUserStore } from './userStore';
import { useNotify } from '../general/useNotify';
import type { Settings } from '../models/settings';
import { Logo } from '../models/logo';
import { appName, logo } from '../models/settings';
const refreshTime = 10000;
let intervalId: ReturnType<typeof setInterval> | null = null;
@@ -16,7 +16,8 @@ export function useLogin() {
await appApi.post('/login', { user, password }).then((resp) => {
const sets = resp.data.settings as Settings;
Logo.value = sets.icon;
logo.value = sets.icon;
appName.value = sets.appName;
document.documentElement.style.setProperty('--q-primary', sets.primaryColor);
document.documentElement.style.setProperty('--q-primary-text', sets.primaryColorText);
document.documentElement.style.setProperty('--q-secondary', sets.secondaryColor);

View File

@@ -1,3 +0,0 @@
import { ref } from 'vue';
export const Logo = ref('');

View File

@@ -1,4 +1,11 @@
import { ref } from 'vue';
export const logo = ref('');
export const appName = ref('Attendance Records');
export const databaseName = ref('members.dba');
export type Settings = {
appName: string;
icon: string;
databaseName: string;
primaryColor: string;
@@ -9,6 +16,7 @@ export type Settings = {
export function DefaultSettings(): Settings {
return {
appName: 'Attendance Records',
icon: '',
databaseName: 'members.dba',
primaryColor: document.documentElement.style.getPropertyValue('--q-primary-text'),

View File

@@ -153,7 +153,7 @@ import EditAllDialog from 'src/components/EventEditAllDialog.vue';
import OkDialog from 'src/components/dialog/OkDialog.vue';
import { useNotify } from 'src/vueLib/general/useNotify';
import { useEventTable } from './EventsTable';
import { databaseName } from '../members/MembersTable';
import { databaseName } from 'src/vueLib/models/settings';
import { useUserStore } from 'src/vueLib/login/userStore';
import AttendeesTable from '../attendees/AttendeesTable.vue';
import type { Members } from 'src/vueLib/models/member';

View File

@@ -5,8 +5,6 @@ import { useNotify } from 'src/vueLib/general/useNotify';
import { i18n } from 'boot/lang';
import { useResponsibleTable } from '../responsible/ResponsibleTable';
export const databaseName = ref('members.dba');
export function useMemberTable() {
const members = ref<Members>([]);
const { responsibles, updateResponsibles } = useResponsibleTable();
@@ -232,7 +230,6 @@ export function useMemberTable() {
})
.finally(() => {
loading.value = false;
console.log(4545, members.value.length);
//filter same members out so list is shorter
if (filter) {
members.value = members.value.filter(
@@ -245,7 +242,6 @@ export function useMemberTable() {
}),
);
}
console.log(4546, members.value.length);
});
}

View File

@@ -201,7 +201,7 @@ import { useNotify } from 'src/vueLib/general/useNotify';
import { useMemberTable } from './MembersTable';
import UploadDialog from 'src/components/UploadDialog.vue';
import AddToEvent from 'src/components/AddToEvent.vue';
import { databaseName } from './MembersTable';
import { databaseName } from 'src/vueLib/models/settings';
import { useUserStore } from 'src/vueLib/login/userStore';
import { i18n } from 'src/boot/lang';

View File

@@ -136,7 +136,7 @@ import type { Member, Members } from 'src/vueLib/models/member';
import OkDialog from 'src/components/dialog/OkDialog.vue';
import { useNotify } from 'src/vueLib/general/useNotify';
import { useResponsibleTable } from './ResponsibleTable';
import { databaseName } from '../members/MembersTable';
import { databaseName } from 'src/vueLib/models/settings';
import { useUserStore } from 'src/vueLib/login/userStore';
import { i18n } from 'src/boot/lang';