Compare commits
14 Commits
b726eb42dc
...
v1.4.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e460db854 | ||
|
|
fa58872840 | ||
|
|
ec5893db57 | ||
|
|
21bbfd617e | ||
|
|
d154041df0 | ||
| 41e0b4d060 | |||
| 2f67c02be1 | |||
|
|
15f9026a5f | ||
|
|
e25bac1a1e | ||
|
|
548cd9d622 | ||
|
|
8e3e8f8bc7 | ||
|
|
e686a27bf1 | ||
|
|
ab88acd740 | ||
|
|
6392877dc1 |
@@ -1,10 +1,10 @@
|
|||||||
# Memer App
|
# Member App
|
||||||
|
|
||||||
A full-stack Member Management platform designed for organizations to manage their members, events, excursions, and Impact Team activities efficiently.
|
A full-stack Member Management platform designed for organizations to manage their members, events, excursions, and Impact Team activities efficiently.
|
||||||
|
|
||||||
## 🧩 Overview
|
## 🧩 Overview
|
||||||
|
|
||||||
**Memer App** provides a unified interface for handling:
|
**Member App** provides a unified interface for handling:
|
||||||
- Member registration and profiles
|
- Member registration and profiles
|
||||||
- Event creation, attendance tracking, and reporting
|
- Event creation, attendance tracking, and reporting
|
||||||
- Excursion & Impact Team management
|
- Excursion & Impact Team management
|
||||||
|
|||||||
BIN
backend/gagag.dbaa
Normal file
BIN
backend/gagag.dbaa
Normal file
Binary file not shown.
@@ -3,9 +3,9 @@ module backend
|
|||||||
go 1.25.4
|
go 1.25.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
gitea.tecamino.com/paadi/access-handler v1.0.34
|
gitea.tecamino.com/paadi/access-handler v1.0.51
|
||||||
gitea.tecamino.com/paadi/memberDB v1.1.28
|
gitea.tecamino.com/paadi/memberDB v1.1.30
|
||||||
gitea.tecamino.com/paadi/tecamino-dbm v0.1.1
|
gitea.tecamino.com/paadi/tecamino-dbm v1.0.0
|
||||||
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
|
||||||
github.com/gin-gonic/gin v1.11.0
|
github.com/gin-gonic/gin v1.11.0
|
||||||
@@ -14,7 +14,7 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
gitea.tecamino.com/paadi/dbHandler v1.1.11 // indirect
|
gitea.tecamino.com/paadi/dbHandler v1.1.12 // 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
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
gitea.tecamino.com/paadi/access-handler v1.0.34 h1:6P65HiusSfvgv/ezOvxSahqyRJMK9UrxtGsz6loLoUk=
|
gitea.tecamino.com/paadi/access-handler v1.0.51 h1:kTPwN+0Zw/Uyfo6el1jl5ORzIrjQdC8PUlczoN9mBS4=
|
||||||
gitea.tecamino.com/paadi/access-handler v1.0.34/go.mod h1:HyMp1WvzmqLw8Ljt3r1qlF8fY+T5WFXr9Da/CTIM0H8=
|
gitea.tecamino.com/paadi/access-handler v1.0.51/go.mod h1:0kUGU4Jw2jSvopCCwecuX/2QnVKS09Ec1KQNrBXvsFs=
|
||||||
gitea.tecamino.com/paadi/dbHandler v1.1.11 h1:hTpMWRr4dW7TkiBnEku0/3ggDC7/uP82U9paRKY/QEs=
|
gitea.tecamino.com/paadi/dbHandler v1.1.12 h1:F1ARSTUm0MZmF84FfD/g5RQNMYyDYXHYrB3cXPSi4qw=
|
||||||
gitea.tecamino.com/paadi/dbHandler v1.1.11/go.mod h1:y/xn/POJg1DO++67uKvnO23lJQgh+XFQq7HZCS9Getw=
|
gitea.tecamino.com/paadi/dbHandler v1.1.12/go.mod h1:y/xn/POJg1DO++67uKvnO23lJQgh+XFQq7HZCS9Getw=
|
||||||
gitea.tecamino.com/paadi/memberDB v1.1.28 h1:QSgPFIvzWS17bAIHp01nqUG5CQuE74AckrdYg6xZljw=
|
gitea.tecamino.com/paadi/memberDB v1.1.30 h1:N+3V9A/+OAGIoJeUNVHj1qUuBcy6ADLYFIgCnp2Ggk4=
|
||||||
gitea.tecamino.com/paadi/memberDB v1.1.28/go.mod h1:uLoKel+EcuXUzxAY5ugfWh640TSomfTJR+g8Jfe8YKI=
|
gitea.tecamino.com/paadi/memberDB v1.1.30/go.mod h1:Q4NO1cdBm/6RLF+bP2NEzBPJURKjyIr4u3dElDXmHWI=
|
||||||
gitea.tecamino.com/paadi/tecamino-dbm v0.1.1 h1:vAq7mwUxlxJuLzCQSDMrZCwo8ky5usWi9Qz+UP+WnkI=
|
gitea.tecamino.com/paadi/tecamino-dbm v1.0.0 h1:xFgcpIiQMyqbglScZBAbdOQyM+yOJ3GHMK2iX5Ep3Gg=
|
||||||
gitea.tecamino.com/paadi/tecamino-dbm v0.1.1/go.mod h1:+tmf1rjPaKEoNeUcr1vdtoFIFweNG3aUGevDAl3NMBk=
|
gitea.tecamino.com/paadi/tecamino-dbm v1.0.0/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=
|
||||||
gitea.tecamino.com/paadi/tecamino-logger v0.2.1/go.mod h1:FkzRTldUBBOd/iy2upycArDftSZ5trbsX5Ira5OzJgM=
|
gitea.tecamino.com/paadi/tecamino-logger v0.2.1/go.mod h1:FkzRTldUBBOd/iy2upycArDftSZ5trbsX5Ira5OzJgM=
|
||||||
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ func main() {
|
|||||||
|
|
||||||
auth.GET("/users", accessHandler.GetUser)
|
auth.GET("/users", accessHandler.GetUser)
|
||||||
auth.GET("/roles", accessHandler.GetRole)
|
auth.GET("/roles", accessHandler.GetRole)
|
||||||
|
auth.GET("/workspaces", accessHandler.GetWorkspace)
|
||||||
|
|
||||||
auth.POST("database/open", dbHandler.OpenDatabase)
|
auth.POST("database/open", dbHandler.OpenDatabase)
|
||||||
auth.POST("/members/add", dbHandler.AddNewMember)
|
auth.POST("/members/add", dbHandler.AddNewMember)
|
||||||
@@ -177,6 +178,11 @@ func main() {
|
|||||||
auth.POST("/users/new/password", accessHandler.ChangePassword)
|
auth.POST("/users/new/password", accessHandler.ChangePassword)
|
||||||
auth.POST("/users/delete", accessHandler.DeleteUser)
|
auth.POST("/users/delete", accessHandler.DeleteUser)
|
||||||
|
|
||||||
|
auth.POST("/workspaces/add", accessHandler.AddWorkspace)
|
||||||
|
auth.POST("/workspaces/update", accessHandler.UpdateWorkspace)
|
||||||
|
auth.POST("/workspaces/data", accessHandler.ReadWorkspaceData)
|
||||||
|
auth.POST("/workspaces/delete", accessHandler.DeleteWorkspace)
|
||||||
|
|
||||||
api.POST("/login/refresh", accessHandler.Refresh)
|
api.POST("/login/refresh", accessHandler.Refresh)
|
||||||
|
|
||||||
// Serve static files
|
// Serve static files
|
||||||
@@ -228,4 +234,8 @@ func main() {
|
|||||||
if err := s.ServeHttp(env.HostUrl.GetValue(), env.HostPort.GetUIntValue()); err != nil {
|
if err := s.ServeHttp(env.HostUrl.GetValue(), env.HostPort.GetUIntValue()); err != nil {
|
||||||
logger.Error("main", "error http server "+err.Error())
|
logger.Error("main", "error http server "+err.Error())
|
||||||
}
|
}
|
||||||
|
if err := s.ServeHttp(env.HostUrl.GetValue(), env.HostPort.GetUIntValue()); err != nil {
|
||||||
|
logger.Error("main", "error http server "+err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lightcontrol",
|
"name": "lightcontrol",
|
||||||
"version": "1.3.2",
|
"version": "1.4.1",
|
||||||
"description": "A Tecamino App",
|
"description": "A Tecamino App",
|
||||||
"productName": "Attendence Records",
|
"productName": "Attendence Records",
|
||||||
"author": "A. Zuercher",
|
"author": "A. Zuercher",
|
||||||
|
|||||||
@@ -197,3 +197,11 @@ calendar:
|
|||||||
firstDayOfWeek: 1
|
firstDayOfWeek: 1
|
||||||
format24h: true
|
format24h: true
|
||||||
pluralDay: 'Täg'
|
pluralDay: 'Täg'
|
||||||
|
description: Beschribig
|
||||||
|
workspace: Workspace
|
||||||
|
workspaces: Workspaces
|
||||||
|
addNewWorkspace: Füeg neuis Workspace hinzue
|
||||||
|
saved: gspicheret
|
||||||
|
noWorkspaceFound: Kes Workspace gfunge
|
||||||
|
addNewDatabase: Nei Datenbank
|
||||||
|
fileNeedsToEndWith: Dateiname mues fougendi endig ha
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ roleIsRequired: Rolle ist erforderlich
|
|||||||
permissions: Rechte
|
permissions: Rechte
|
||||||
selectRoleOptions: Wähle Rollen Optionen
|
selectRoleOptions: Wähle Rollen Optionen
|
||||||
selectEventOptions: Wähle Veranstaltungs Optionen
|
selectEventOptions: Wähle Veranstaltungs Optionen
|
||||||
addNewRole: Füge neue Rolle hinzu
|
addNewRole: Neue Rolle hinzufügen
|
||||||
addNewEvent: Füeg neue Veranstaltung hinzu
|
addNewEvent: Neue Veranstaltung hinzufügen
|
||||||
veryWeak: sehr Schwach
|
veryWeak: sehr Schwach
|
||||||
weak: Schwach
|
weak: Schwach
|
||||||
fair: Ausreichend
|
fair: Ausreichend
|
||||||
@@ -197,3 +197,11 @@ calendar:
|
|||||||
firstDayOfWeek: 1
|
firstDayOfWeek: 1
|
||||||
format24h: true
|
format24h: true
|
||||||
pluralDay: 'Tage'
|
pluralDay: 'Tage'
|
||||||
|
description: Beschreibung
|
||||||
|
workspace: Workspace
|
||||||
|
workspaces: Workspaces
|
||||||
|
addNewWorkspace: Neues Workspace hinzufügen
|
||||||
|
saved: gespeichert
|
||||||
|
noWorkspaceFound: Kein Workspace gefunden
|
||||||
|
addNewDatabase: Neue Datenbank hinzufügen
|
||||||
|
fileNeedsToEndWith: Dateiname muss folgende Endung haben
|
||||||
|
|||||||
@@ -197,3 +197,11 @@ calendar:
|
|||||||
firstDayOfWeek: 0
|
firstDayOfWeek: 0
|
||||||
format24h: false
|
format24h: false
|
||||||
pluralDay: 'Days'
|
pluralDay: 'Days'
|
||||||
|
description: Description
|
||||||
|
workspace: Workspace
|
||||||
|
workspaces: Workspaces
|
||||||
|
addNewWorkspace: Add new Workspace
|
||||||
|
saved: saved
|
||||||
|
noWorkspaceFound: No Workspace found
|
||||||
|
addNewDatabase: Add new database
|
||||||
|
fileNeedsToEndWith: Filename must end with
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ login: Iniciar sesión
|
|||||||
logout: Cerrar sesión
|
logout: Cerrar sesión
|
||||||
user: Usuario
|
user: Usuario
|
||||||
password: Contraseña
|
password: Contraseña
|
||||||
isRequired: Es obligatorio
|
isRequired: Obligatorio
|
||||||
colors: Colores
|
colors: Colores
|
||||||
primaryColor: Color principal
|
primaryColor: Color principal
|
||||||
primaryColorText: Color del texto principal
|
primaryColorText: Color del texto principal
|
||||||
@@ -108,7 +108,7 @@ attendeeAdded: Asistente añadido
|
|||||||
attendeesAdded: Asistentes añadidos
|
attendeesAdded: Asistentes añadidos
|
||||||
eventAdded: Evento añadido
|
eventAdded: Evento añadido
|
||||||
userUpdated: Usuario actualizado
|
userUpdated: Usuario actualizado
|
||||||
selectResponsibleOptions: Seleccionar opciones responsables
|
selectResponsibleOptions: Seleccionar opciones de responsables
|
||||||
addNewResponsible: Añadir responsable
|
addNewResponsible: Añadir responsable
|
||||||
responsibleAdded: Responsable añadido
|
responsibleAdded: Responsable añadido
|
||||||
responsiblesAdded: Responsables añadidos
|
responsiblesAdded: Responsables añadidos
|
||||||
@@ -197,3 +197,11 @@ calendar:
|
|||||||
firstDayOfWeek: 1
|
firstDayOfWeek: 1
|
||||||
format24h: true
|
format24h: true
|
||||||
pluralDay: 'dias'
|
pluralDay: 'dias'
|
||||||
|
description: Descripción
|
||||||
|
workspace: Workspace
|
||||||
|
workspaces: Workspaces
|
||||||
|
addNewWorkspace: Añadir nuevo Workspace
|
||||||
|
saved: guardado
|
||||||
|
noWorkspaceFound: No se encontró Workspace
|
||||||
|
addNewDatabase: Agregar nueva base de datos
|
||||||
|
fileNeedsToEndWith: El nombre del archivo debe terminar con
|
||||||
|
|||||||
@@ -1,29 +1,35 @@
|
|||||||
import { boot } from 'quasar/wrappers';
|
import { boot } from 'quasar/wrappers';
|
||||||
import { appApi } from './axios';
|
|
||||||
import { createPinia } from 'pinia';
|
import { createPinia } from 'pinia';
|
||||||
import { useUserStore } from 'src/vueLib/login/userStore';
|
import { useUserStore } from 'src/vueLib/login/userStore';
|
||||||
import { useLogin } from 'src/vueLib/login/useLogin';
|
import { useLogin } from 'src/vueLib/login/useLogin';
|
||||||
|
import { Me, openDatabase } from 'src/vueLib/components/DatabaseCall';
|
||||||
|
|
||||||
const pinia = createPinia();
|
const pinia = createPinia();
|
||||||
|
|
||||||
export default boot(async ({ app }) => {
|
export default boot(async ({ app }) => {
|
||||||
app.use(pinia);
|
app.use(pinia);
|
||||||
const useStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
|
|
||||||
await appApi
|
const resp = await Me().catch(() =>
|
||||||
.get('/login/me')
|
login.logout().catch((err) => {
|
||||||
.then((resp) => {
|
console.error(err);
|
||||||
useStore
|
return;
|
||||||
.setUser({
|
}),
|
||||||
id: resp.data.id,
|
);
|
||||||
username: resp.data.username,
|
|
||||||
role: { role: resp.data.role, permissions: [] },
|
if (!resp) return;
|
||||||
})
|
|
||||||
.catch((err) => console.error(err));
|
await userStore
|
||||||
login.refresh().catch((err) => console.error(err));
|
.setUser({
|
||||||
|
id: resp.data.id,
|
||||||
|
user: resp.data.username,
|
||||||
|
role: { role: resp.data.role, permissions: [] },
|
||||||
|
workspaceId: resp.data.workspaceId,
|
||||||
|
settings: resp.data.settings,
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((err) => console.error(err));
|
||||||
login.logout().catch((err) => console.error(err));
|
login.refresh().catch((err) => console.error(err));
|
||||||
});
|
|
||||||
|
await openDatabase().catch((err) => console.error(err));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { boot } from 'quasar/wrappers';
|
import { boot } from 'quasar/wrappers';
|
||||||
import { setQuasarInstance } from 'src/vueLib/utils/globalQ';
|
import { setQuasarInstance } from 'src/vueLib/utils/globalQ';
|
||||||
import { setRouterInstance } from 'src/vueLib/utils/globalRouter';
|
import { setRouterInstance } from 'src/vueLib/utils/globalRouter';
|
||||||
import { databaseName, logo, appName } from 'src/vueLib/models/settings';
|
import { logo, appName } from 'src/vueLib/models/settings';
|
||||||
import { Dark } from 'quasar';
|
import { Dark } from 'quasar';
|
||||||
import { getLocalDarkMode, getLocalSettings } from 'src/localstorage/localStorage';
|
import { getLocalDarkMode, getLocalSettings } from 'src/localstorage/localStorage';
|
||||||
|
|
||||||
@@ -20,7 +20,6 @@ export default boot(({ app, router }) => {
|
|||||||
if (settings.appName) {
|
if (settings.appName) {
|
||||||
appName.value = settings.appName;
|
appName.value = settings.appName;
|
||||||
}
|
}
|
||||||
databaseName.value = settings.databaseName ?? databaseName.value;
|
|
||||||
|
|
||||||
document.documentElement.style.setProperty('--q-primary', settings.primaryColor ?? '#1976d2');
|
document.documentElement.style.setProperty('--q-primary', settings.primaryColor ?? '#1976d2');
|
||||||
document.documentElement.style.setProperty(
|
document.documentElement.style.setProperty(
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import { boot } from 'quasar/wrappers';
|
import { boot } from 'quasar/wrappers';
|
||||||
import { useUserStore } from 'src/vueLib/login/userStore';
|
import { useUserStore } from 'src/vueLib/login/userStore';
|
||||||
import { appApi } from './axios';
|
|
||||||
import { getLocalLastRoute, setLocalLastRoute } from 'src/localstorage/localStorage';
|
import { getLocalLastRoute, setLocalLastRoute } from 'src/localstorage/localStorage';
|
||||||
|
import { Me } from 'src/vueLib/components/DatabaseCall';
|
||||||
|
|
||||||
export default boot(async ({ router }) => {
|
export default boot(async ({ router }) => {
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
// load user
|
// load user
|
||||||
try {
|
try {
|
||||||
const { data } = await appApi.get('/login/me');
|
const data = await Me();
|
||||||
userStore.setFirstLogin(data.newDatabase);
|
userStore.setFirstLogin(data.newDatabase);
|
||||||
|
|
||||||
data.role.role = data.role;
|
data.role.role = data.role;
|
||||||
await userStore.setUser(data);
|
await userStore.setUser(data);
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -74,7 +74,9 @@ function open(title: string, members: Members) {
|
|||||||
appApi
|
appApi
|
||||||
.get('events')
|
.get('events')
|
||||||
.then((resp) => {
|
.then((resp) => {
|
||||||
events.value.push(...resp.data.map((e: Event) => (e.name = e.name + ' (' + e.date + ')')));
|
events.value.push(
|
||||||
|
...resp.data.map((e: Event) => ({ ...e, name: e.name + ' (' + e.date + ')' })),
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
NotifyResponse(err, 'error');
|
NotifyResponse(err, 'error');
|
||||||
@@ -109,7 +111,7 @@ async function addAttendees() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await updateAttendees(0);
|
await updateAttendees(0);
|
||||||
updateEvents();
|
await updateEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
let resolveNewEvent!: (value: Event) => void;
|
let resolveNewEvent!: (value: Event) => void;
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ const dialog = ref();
|
|||||||
const form = ref();
|
const form = ref();
|
||||||
const newUser = ref(false);
|
const newUser = ref(false);
|
||||||
const role = ref('');
|
const role = ref('');
|
||||||
|
|
||||||
const localUser = ref<User>({
|
const localUser = ref<User>({
|
||||||
user: '',
|
user: '',
|
||||||
email: '',
|
email: '',
|
||||||
|
|||||||
105
src/components/WorkspaceEditAllDialog.vue
Normal file
105
src/components/WorkspaceEditAllDialog.vue
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
<template>
|
||||||
|
<DialogFrame
|
||||||
|
ref="dialog"
|
||||||
|
:header-title="newWorkspace ? $t('addNewWorkspace') : $t('edit') + ' ' + localWorkspace.name"
|
||||||
|
:height="300"
|
||||||
|
:width="600"
|
||||||
|
>
|
||||||
|
<div class="column">
|
||||||
|
<div class="row justify-center">
|
||||||
|
<q-input
|
||||||
|
class="col-5 required"
|
||||||
|
:label="$t('workspace')"
|
||||||
|
filled
|
||||||
|
:rules="[(val) => !!val || $t('workspaceIsRequired')]"
|
||||||
|
v-model="localWorkspace.name"
|
||||||
|
autofocus
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
<div class="row justify-center">
|
||||||
|
<q-input
|
||||||
|
dense
|
||||||
|
class="col-5 required"
|
||||||
|
:label="$t('description')"
|
||||||
|
filled
|
||||||
|
v-model="localWorkspace.description"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
</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 type { Workspace } from 'src/vueLib/models/workspaces';
|
||||||
|
import { useNotify } from 'src/vueLib/general/useNotify';
|
||||||
|
import { i18n } from 'src/boot/lang';
|
||||||
|
const { NotifyResponse } = useNotify();
|
||||||
|
const dialog = ref();
|
||||||
|
const newWorkspace = ref(false);
|
||||||
|
const localWorkspace = ref<Workspace>({
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update']);
|
||||||
|
|
||||||
|
function open(workspace: Workspace | null) {
|
||||||
|
if (workspace === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workspace !== null) {
|
||||||
|
localWorkspace.value = { ...workspace };
|
||||||
|
localWorkspace.value.description = workspace.description;
|
||||||
|
newWorkspace.value = false;
|
||||||
|
} else {
|
||||||
|
localWorkspace.value = {
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
};
|
||||||
|
newWorkspace.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.value?.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
let query = 'workspaces/update?id=' + localWorkspace.value.id;
|
||||||
|
let update = true;
|
||||||
|
if (newWorkspace.value) {
|
||||||
|
query = 'workspaces/add';
|
||||||
|
update = false;
|
||||||
|
}
|
||||||
|
await appApi
|
||||||
|
.post(query, JSON.stringify(localWorkspace.value))
|
||||||
|
.then(() => {
|
||||||
|
if (update) {
|
||||||
|
NotifyResponse(
|
||||||
|
i18n.global.t('workspace') +
|
||||||
|
" '" +
|
||||||
|
localWorkspace.value.name +
|
||||||
|
"' " +
|
||||||
|
i18n.global.t('updated'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
emit('update');
|
||||||
|
dialog.value.close();
|
||||||
|
})
|
||||||
|
.catch((err) => NotifyResponse(err, 'error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.required .q-field__label::after {
|
||||||
|
content: ' *';
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
import { Dark } from 'quasar';
|
import { Dark } from 'quasar';
|
||||||
import { appName, databaseName, type Settings } from 'src/vueLib/models/settings';
|
import { appName, type Settings } from 'src/vueLib/models/settings';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
export function setLocalSettings(settings: Settings) {
|
export function setLocalSettings(settings: Settings) {
|
||||||
localStorage.setItem('icon', settings.icon);
|
if (settings.icon !== '') localStorage.setItem('icon', settings.icon);
|
||||||
localStorage.setItem('appName', settings.appName);
|
if (settings.appName !== '') localStorage.setItem('appName', settings.appName);
|
||||||
localStorage.setItem('databaseName', settings.databaseName);
|
|
||||||
localStorage.setItem('primaryColor', settings.primaryColor);
|
localStorage.setItem('primaryColor', settings.primaryColor);
|
||||||
localStorage.setItem('primaryColorText', settings.primaryColorText);
|
localStorage.setItem('primaryColorText', settings.primaryColorText);
|
||||||
localStorage.setItem('secondaryColor', settings.secondaryColor);
|
localStorage.setItem('secondaryColor', settings.secondaryColor);
|
||||||
@@ -17,12 +16,6 @@ export function getLocalSettings(): Settings {
|
|||||||
if (name === undefined || name === 'undefined') {
|
if (name === undefined || name === 'undefined') {
|
||||||
name = appName.value;
|
name = appName.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
let db = localStorage.getItem('databaseName');
|
|
||||||
if (db === undefined || db === 'undefined') {
|
|
||||||
db = databaseName.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
let iconName = localStorage.getItem('icon');
|
let iconName = localStorage.getItem('icon');
|
||||||
if (iconName === undefined || iconName === 'undefined') {
|
if (iconName === undefined || iconName === 'undefined') {
|
||||||
iconName = '';
|
iconName = '';
|
||||||
@@ -31,7 +24,6 @@ export function getLocalSettings(): Settings {
|
|||||||
return <Settings>{
|
return <Settings>{
|
||||||
icon: iconName,
|
icon: iconName,
|
||||||
appName: name,
|
appName: name,
|
||||||
databaseName: db,
|
|
||||||
primaryColor: localStorage.getItem('primaryColor'),
|
primaryColor: localStorage.getItem('primaryColor'),
|
||||||
primaryColorText: localStorage.getItem('primaryColorText'),
|
primaryColorText: localStorage.getItem('primaryColorText'),
|
||||||
secondaryColor: localStorage.getItem('secondaryColor'),
|
secondaryColor: localStorage.getItem('secondaryColor'),
|
||||||
@@ -42,7 +34,6 @@ export function getLocalSettings(): Settings {
|
|||||||
export function clearLocalStorage() {
|
export function clearLocalStorage() {
|
||||||
localStorage.removeItem('icon');
|
localStorage.removeItem('icon');
|
||||||
localStorage.removeItem('appName');
|
localStorage.removeItem('appName');
|
||||||
localStorage.removeItem('databaseName');
|
|
||||||
localStorage.removeItem('primaryColor');
|
localStorage.removeItem('primaryColor');
|
||||||
localStorage.removeItem('primaryColorText');
|
localStorage.removeItem('primaryColorText');
|
||||||
localStorage.removeItem('secondaryColor');
|
localStorage.removeItem('secondaryColor');
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const router = useRouter();
|
|||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (userStore.user?.username !== '' && userStore.user?.role.role !== '') {
|
if (userStore.user?.user !== '' && userStore.user?.role.role !== '') {
|
||||||
forwardToPage().catch((err) => console.error(err));
|
forwardToPage().catch((err) => console.error(err));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -82,8 +82,12 @@
|
|||||||
v-if="attendees !== undefined"
|
v-if="attendees !== undefined"
|
||||||
:class="
|
:class="
|
||||||
nonAttendees !== undefined
|
nonAttendees !== undefined
|
||||||
? 'col-12 col-sm-5 col-md-5 q-pa-md'
|
? printing
|
||||||
: 'col-12 col-md-8 col-lg-5'
|
? 'col-5 q-pa-md'
|
||||||
|
: 'col-12 col-sm-5 col-md-5 q-pa-md'
|
||||||
|
: printing
|
||||||
|
? 'col-5'
|
||||||
|
: 'col-12 col-md-8 col-lg-5'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<q-table
|
<q-table
|
||||||
@@ -103,7 +107,13 @@
|
|||||||
<div
|
<div
|
||||||
v-if="nonAttendees !== undefined"
|
v-if="nonAttendees !== undefined"
|
||||||
:class="
|
:class="
|
||||||
attendees !== undefined ? 'col-12 col-sm-5 col-md-5 q-pa-md' : 'col-12 col-md-8 col-lg-5'
|
attendees !== undefined
|
||||||
|
? printing
|
||||||
|
? 'col-5 q-pa-md'
|
||||||
|
: 'col-12 col-sm-5 col-md-5 q-pa-md'
|
||||||
|
: printing
|
||||||
|
? 'col-5'
|
||||||
|
: 'col-12 col-md-8 col-lg-5'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<q-table
|
<q-table
|
||||||
@@ -130,7 +140,7 @@ import DateDaySelect from 'src/components/DateDaySelect.vue';
|
|||||||
import { computed, 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 { i18n } from 'src/boot/lang';
|
import { i18n } from 'src/boot/lang';
|
||||||
import { appName, databaseName } from 'src/vueLib/models/settings';
|
import { appName } from 'src/vueLib/models/settings';
|
||||||
import type { Amount } from 'src/vueLib/models/report';
|
import type { Amount } from 'src/vueLib/models/report';
|
||||||
import ReportStat from 'src/components/ReportStat.vue';
|
import ReportStat from 'src/components/ReportStat.vue';
|
||||||
import type { Group, Groups } from 'src/vueLib/models/group';
|
import type { Group, Groups } from 'src/vueLib/models/group';
|
||||||
@@ -150,6 +160,7 @@ const loading = ref(false);
|
|||||||
const amounts = ref<Amount[]>([]);
|
const amounts = ref<Amount[]>([]);
|
||||||
const reportExportRef = ref<HTMLElement | null>(null);
|
const reportExportRef = ref<HTMLElement | null>(null);
|
||||||
const weekdays = ref<number[]>([0, 3]);
|
const weekdays = ref<number[]>([0, 3]);
|
||||||
|
const printing = ref<boolean>(false);
|
||||||
|
|
||||||
const columns = computed(() => [
|
const columns = computed(() => [
|
||||||
{
|
{
|
||||||
@@ -170,13 +181,6 @@ const columns = computed(() => [
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
appApi
|
|
||||||
.post('database/open', { dbPath: databaseName.value })
|
|
||||||
.catch((err) => NotifyResponse(err, 'error'))
|
|
||||||
|
|
||||||
.finally(() => {
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
appApi
|
appApi
|
||||||
.get('/groups')
|
.get('/groups')
|
||||||
.then((resp) => (groups.value = resp.data))
|
.then((resp) => (groups.value = resp.data))
|
||||||
@@ -268,10 +272,14 @@ function updateReport(dates: string[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function printReport() {
|
function printReport() {
|
||||||
|
printing.value = true;
|
||||||
window.print();
|
window.print();
|
||||||
|
printing.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadPDF() {
|
async function downloadPDF() {
|
||||||
|
printing.value = true;
|
||||||
|
|
||||||
const element = reportExportRef.value;
|
const element = reportExportRef.value;
|
||||||
if (!element) return;
|
if (!element) return;
|
||||||
// Generate date string (YYYY-MM-DD)
|
// Generate date string (YYYY-MM-DD)
|
||||||
@@ -308,7 +316,8 @@ async function downloadPDF() {
|
|||||||
.save()
|
.save()
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('PDF Generation failed:', error);
|
console.error('PDF Generation failed:', error);
|
||||||
});
|
})
|
||||||
|
.finally(() => (printing.value = false));
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -26,15 +26,57 @@
|
|||||||
</q-card>
|
</q-card>
|
||||||
<q-card class="q-ma-lg">
|
<q-card class="q-ma-lg">
|
||||||
<p class="text-bold text-h6 text-primary q-pa-md">{{ $t('database') }}</p>
|
<p class="text-bold text-h6 text-primary q-pa-md">{{ $t('database') }}</p>
|
||||||
<div class="row">
|
<div
|
||||||
<q-input
|
v-if="localUser?.workspaces !== undefined && localUser?.workspaces.length > 0"
|
||||||
|
class="row"
|
||||||
|
>
|
||||||
|
<q-select
|
||||||
dense
|
dense
|
||||||
:readonly="!user.isPermittedTo('settings', 'write')"
|
:readonly="!user.isPermittedTo('settings', 'write')"
|
||||||
:class="[colorGroup ? 'col-md-4' : 'col-md-12', 'q-pa-md']"
|
:class="[colorGroup ? 'col-md-4' : 'col-xs-12 col-sm-6 col-md-12', 'q-pa-md']"
|
||||||
|
filled
|
||||||
|
:label="$t('workspaces')"
|
||||||
|
:options="localUser?.workspaces"
|
||||||
|
option-label="name"
|
||||||
|
v-model="localWorkspace"
|
||||||
|
@update:model-value="changeWorkspace"
|
||||||
|
></q-select>
|
||||||
|
<q-select
|
||||||
|
dense
|
||||||
|
:readonly="!user.isPermittedTo('settings', 'write')"
|
||||||
|
:class="[colorGroup ? 'col-md-4' : 'col-xs-12 col-sm-6 col-md-12', 'q-pa-md']"
|
||||||
filled
|
filled
|
||||||
:label="$t('databaseName')"
|
:label="$t('databaseName')"
|
||||||
|
:options="databases"
|
||||||
v-model="settings.databaseName"
|
v-model="settings.databaseName"
|
||||||
></q-input>
|
@update:model-value="changeDatabase"
|
||||||
|
>
|
||||||
|
<template v-slot:option="scope">
|
||||||
|
<q-item v-bind="scope.itemProps">
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{ scope.opt }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
|
||||||
|
<q-item-section side>
|
||||||
|
<q-btn
|
||||||
|
v-if="scope.index > foundDatabases"
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
size="sm"
|
||||||
|
color="negative"
|
||||||
|
icon="cancel"
|
||||||
|
@click.stop="removeItem(scope.index)"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
</div>
|
||||||
|
<div v-else class="column items-center q-pa-lg text-center">
|
||||||
|
<q-icon name="workspaces" size="64px" color="grey-5" />
|
||||||
|
<div class="text-h6 q-mt-md text-grey-8">
|
||||||
|
{{ $t('noWorkspaceFound') }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-card>
|
</q-card>
|
||||||
<q-card class="q-ma-lg">
|
<q-card class="q-ma-lg">
|
||||||
@@ -129,36 +171,88 @@
|
|||||||
</div>
|
</div>
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
|
<DialogFrame :width="300" :header-title="$t('addNewDatabase')" ref="addDatabaseRef">
|
||||||
|
<q-input
|
||||||
|
class="q-ma-md"
|
||||||
|
autofocus
|
||||||
|
filled
|
||||||
|
:label="$t('databaseName')"
|
||||||
|
v-model:model-value="newDatabase"
|
||||||
|
@keyup.enter.stop.prevent="addNewDatabase"
|
||||||
|
/>
|
||||||
|
<div class="row justify-center">
|
||||||
|
<q-btn class="q-a-md" color="primary" :label="$t('save')" @click="addNewDatabase"></q-btn>
|
||||||
|
</div>
|
||||||
|
</DialogFrame>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { logo, appName, databaseName } from 'src/vueLib/models/settings';
|
import { logo, appName } from 'src/vueLib/models/settings';
|
||||||
import { reactive, ref, watch } from 'vue';
|
import { onMounted, reactive, ref, watch } from 'vue';
|
||||||
import { appApi } from 'src/boot/axios';
|
import { appApi } from 'src/boot/axios';
|
||||||
import { useNotify } from 'src/vueLib/general/useNotify';
|
import { useNotify } from 'src/vueLib/general/useNotify';
|
||||||
import { type Settings } from 'src/vueLib/models/settings';
|
import type { Settings } from 'src/vueLib/models/settings';
|
||||||
import { useUserStore } from 'src/vueLib/login/userStore';
|
import { useUserStore } from 'src/vueLib/login/userStore';
|
||||||
import { setLocalSettings } from 'src/localstorage/localStorage';
|
import { setLocalSettings } from 'src/localstorage/localStorage';
|
||||||
import SiteTitle from 'src/vueLib/general/SiteTitle.vue';
|
import SiteTitle from 'src/vueLib/general/SiteTitle.vue';
|
||||||
|
import { type User } from 'src/vueLib/models/user';
|
||||||
|
import { i18n } from 'src/boot/lang';
|
||||||
|
import DialogFrame from 'src/vueLib/dialog/DialogFrame.vue';
|
||||||
|
import type { Workspace } from 'src/vueLib/models/workspaces';
|
||||||
|
import { openDatabase } from '../vueLib/components/DatabaseCall';
|
||||||
|
|
||||||
const { NotifyResponse } = useNotify();
|
const { NotifyResponse } = useNotify();
|
||||||
const colorGroup = ref(false);
|
const colorGroup = ref(false);
|
||||||
const user = useUserStore();
|
const user = useUserStore();
|
||||||
|
const addDatabaseRef = ref();
|
||||||
|
const newDatabase = ref<string>('');
|
||||||
|
const foundDatabases = ref<number>(0);
|
||||||
|
const localUser = ref<User>();
|
||||||
|
const databases = ref<string[]>([]);
|
||||||
|
const localWorkspace = ref<Workspace>();
|
||||||
|
|
||||||
const settings = reactive<Settings>({
|
const settings = reactive<Settings>({
|
||||||
appName: appName.value,
|
appName: appName.value,
|
||||||
icon: logo.value,
|
icon: logo.value,
|
||||||
databaseName: databaseName.value,
|
|
||||||
primaryColor: document.documentElement.style.getPropertyValue('--q-primary'),
|
primaryColor: document.documentElement.style.getPropertyValue('--q-primary'),
|
||||||
primaryColorText: document.documentElement.style.getPropertyValue('--q-primary-text'),
|
primaryColorText: document.documentElement.style.getPropertyValue('--q-primary-text'),
|
||||||
secondaryColor: document.documentElement.style.getPropertyValue('--q-secondary'),
|
secondaryColor: document.documentElement.style.getPropertyValue('--q-secondary'),
|
||||||
secondaryColorText: document.documentElement.style.getPropertyValue('--q-secondary-text'),
|
secondaryColorText: document.documentElement.style.getPropertyValue('--q-secondary-text'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await appApi
|
||||||
|
.get('users?id=' + user.user?.id)
|
||||||
|
.then((resp) => {
|
||||||
|
if (!resp.data) return;
|
||||||
|
localUser.value = resp.data[0];
|
||||||
|
localWorkspace.value = localUser.value?.workspaces?.find(
|
||||||
|
(w) => w.id === localUser.value?.workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (localUser.value) {
|
||||||
|
settings.databaseName = localUser.value.settings?.databaseName || '';
|
||||||
|
|
||||||
|
appApi
|
||||||
|
.post('workspaces/data', localWorkspace.value)
|
||||||
|
.then((resp) => {
|
||||||
|
if (!resp.data) {
|
||||||
|
settings.databaseName = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
databases.value = [i18n.global.t('addNewDatabase')];
|
||||||
|
databases.value.push(...resp.data.data);
|
||||||
|
foundDatabases.value = resp.data.data.length;
|
||||||
|
})
|
||||||
|
.catch((err) => NotifyResponse(err, 'error'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => NotifyResponse(err, 'error'));
|
||||||
|
});
|
||||||
|
|
||||||
watch(settings, (newSettings) => {
|
watch(settings, (newSettings) => {
|
||||||
logo.value = newSettings.icon;
|
logo.value = newSettings.icon;
|
||||||
appName.value = newSettings.appName;
|
appName.value = newSettings.appName;
|
||||||
databaseName.value = newSettings.databaseName;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function resetColors() {
|
function resetColors() {
|
||||||
@@ -172,23 +266,74 @@ function resetColors() {
|
|||||||
settings.secondaryColorText = '#ffffff';
|
settings.secondaryColorText = '#ffffff';
|
||||||
}
|
}
|
||||||
|
|
||||||
function save() {
|
function changeWorkspace() {
|
||||||
|
appApi
|
||||||
|
.post('workspaces/data', localWorkspace.value)
|
||||||
|
.then((resp) => {
|
||||||
|
if (resp.data) {
|
||||||
|
databases.value = [i18n.global.t('addNewDatabase')];
|
||||||
|
databases.value.push(...resp.data.data);
|
||||||
|
foundDatabases.value = resp.data.data.length;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => NotifyResponse(err, 'error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeDatabase() {
|
||||||
|
if (settings.databaseName) {
|
||||||
|
if (databases.value.indexOf(settings.databaseName) === 0) {
|
||||||
|
addDatabaseRef.value?.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeItem(index: number) {
|
||||||
|
if (index > 0) {
|
||||||
|
databases.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function addNewDatabase() {
|
||||||
|
if (!newDatabase.value.includes('.db')) {
|
||||||
|
NotifyResponse(i18n.global.t('fileNeedsToEndWith') + ' .db', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
databases.value.push(newDatabase.value);
|
||||||
|
settings.databaseName = newDatabase.value;
|
||||||
|
addDatabaseRef.value.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function save() {
|
||||||
document.documentElement.style.setProperty('--q-primary', settings.primaryColor);
|
document.documentElement.style.setProperty('--q-primary', settings.primaryColor);
|
||||||
document.documentElement.style.setProperty('--q-primary-text', settings.primaryColorText);
|
document.documentElement.style.setProperty('--q-primary-text', settings.primaryColorText);
|
||||||
document.documentElement.style.setProperty('--q-secondary', settings.secondaryColor);
|
document.documentElement.style.setProperty('--q-secondary', settings.secondaryColor);
|
||||||
document.documentElement.style.setProperty('--q-secondary-text', settings.secondaryColorText);
|
document.documentElement.style.setProperty('--q-secondary-text', settings.secondaryColorText);
|
||||||
appName.value = settings.appName;
|
appName.value = settings.appName;
|
||||||
logo.value = settings.icon;
|
logo.value = settings.icon;
|
||||||
setLocalSettings(settings);
|
if (localUser.value?.settings) {
|
||||||
|
localUser.value.settings = settings;
|
||||||
const tempuser = user.user;
|
|
||||||
|
|
||||||
if (tempuser) {
|
|
||||||
tempuser.settings = settings;
|
|
||||||
}
|
}
|
||||||
appApi
|
|
||||||
.post('users/update', tempuser)
|
setLocalSettings(settings);
|
||||||
.then((resp) => NotifyResponse(resp.data.message))
|
if (localUser.value) {
|
||||||
|
await user.setUser(localUser.value);
|
||||||
|
localUser.value.workspaceId = localWorkspace.value?.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
await appApi
|
||||||
|
.post('users/update', localUser.value)
|
||||||
|
.then(() =>
|
||||||
|
NotifyResponse(
|
||||||
|
i18n.global.t('user') +
|
||||||
|
' ' +
|
||||||
|
localUser.value?.user +
|
||||||
|
' ' +
|
||||||
|
i18n.global.t('settings') +
|
||||||
|
' ' +
|
||||||
|
i18n.global.t('saved'),
|
||||||
|
),
|
||||||
|
)
|
||||||
.catch((err) => NotifyResponse(err, 'error'));
|
.catch((err) => NotifyResponse(err, 'error'));
|
||||||
|
|
||||||
|
await openDatabase().catch((err) => console.error(err));
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ import { appApi } from 'src/boot/axios';
|
|||||||
import { i18n } from 'src/boot/lang';
|
import { i18n } from 'src/boot/lang';
|
||||||
import SiteTitle from 'src/vueLib/general/SiteTitle.vue';
|
import SiteTitle from 'src/vueLib/general/SiteTitle.vue';
|
||||||
import { useNotify } from 'src/vueLib/general/useNotify';
|
import { useNotify } from 'src/vueLib/general/useNotify';
|
||||||
import { databaseName } from 'src/vueLib/models/settings';
|
import { useUserStore } from 'src/vueLib/login/userStore';
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
const stats = ref();
|
const stats = ref();
|
||||||
@@ -71,8 +71,13 @@ const amounts = ref<{
|
|||||||
const { NotifyResponse } = useNotify();
|
const { NotifyResponse } = useNotify();
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
const user = useUserStore().user;
|
||||||
|
const workspaceUuid = useUserStore().getWorkspaceUuid;
|
||||||
|
let path = user?.settings?.databaseName;
|
||||||
|
if (workspaceUuid !== undefined) path = workspaceUuid + '/' + path;
|
||||||
|
|
||||||
stats.value = await appApi
|
stats.value = await appApi
|
||||||
.post('/stats', { database: databaseName.value })
|
.post('/stats', { database: path })
|
||||||
.then((resp) => {
|
.then((resp) => {
|
||||||
if ((resp.data.databaseSize as number) >= 1000000000) {
|
if ((resp.data.databaseSize as number) >= 1000000000) {
|
||||||
return (resp.data.data.databaseSize / 1000000000).toFixed(2) + ' GB';
|
return (resp.data.data.databaseSize / 1000000000).toFixed(2) + ' GB';
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<q-tab
|
<q-tab
|
||||||
v-if="user?.user?.role.role.includes('admin')"
|
v-if="user?.user?.role.role.includes('admin')"
|
||||||
name="workspaces"
|
name="workspaces"
|
||||||
icon="dashboard"
|
icon="workspaces"
|
||||||
no-caps
|
no-caps
|
||||||
:label="$t('workspaces')"
|
:label="$t('workspaces')"
|
||||||
/>
|
/>
|
||||||
@@ -31,12 +31,11 @@
|
|||||||
<RoleTable />
|
<RoleTable />
|
||||||
</q-tab-panel>
|
</q-tab-panel>
|
||||||
<q-tab-panel name="workspaces" style="padding: 0px">
|
<q-tab-panel name="workspaces" style="padding: 0px">
|
||||||
<RoleTable />
|
<WorkspaceTable />
|
||||||
</q-tab-panel>
|
</q-tab-panel>
|
||||||
</q-tab-panels>
|
</q-tab-panels>
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
{{ user?.user?.role }}
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -46,6 +45,7 @@ import RoleTable from 'src/vueLib/tables/roles/RoleTable.vue';
|
|||||||
import SiteTitle from 'src/vueLib/general/SiteTitle.vue';
|
import SiteTitle from 'src/vueLib/general/SiteTitle.vue';
|
||||||
import { useUserStore } from 'src/vueLib/login/userStore';
|
import { useUserStore } from 'src/vueLib/login/userStore';
|
||||||
import type { UserState } from 'src/vueLib/models/user';
|
import type { UserState } from 'src/vueLib/models/user';
|
||||||
|
import WorkspaceTable from 'src/vueLib/tables/workspaces/WorkspaceTable.vue';
|
||||||
|
|
||||||
const tab = ref('users');
|
const tab = ref('users');
|
||||||
const user = ref<UserState>();
|
const user = ref<UserState>();
|
||||||
|
|||||||
43
src/vueLib/components/DatabaseCall.ts
Normal file
43
src/vueLib/components/DatabaseCall.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { appApi } from 'src/boot/axios';
|
||||||
|
import { useUserStore } from '../login/userStore';
|
||||||
|
import type { User } from '../models/users';
|
||||||
|
import { useNotify } from '../general/useNotify';
|
||||||
|
|
||||||
|
export async function openDatabase() {
|
||||||
|
const user = useUserStore().user;
|
||||||
|
|
||||||
|
if (!user) return;
|
||||||
|
const tempUser = (await getUser(user?.id)) as User | void;
|
||||||
|
|
||||||
|
if (!tempUser) return;
|
||||||
|
let path = '';
|
||||||
|
|
||||||
|
path = tempUser.settings?.databaseName || '';
|
||||||
|
|
||||||
|
if (tempUser.workspaces) {
|
||||||
|
path = tempUser.workspaces.find((w) => w.id === tempUser.workspaceId)?.uuid || '';
|
||||||
|
path = path + '/' + tempUser.settings?.databaseName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return appApi.post('database/open', {
|
||||||
|
dbPath: path,
|
||||||
|
create: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getUser(id: number): Promise<User | null> {
|
||||||
|
const { NotifyResponse } = useNotify();
|
||||||
|
return appApi
|
||||||
|
.get(`/users?id=${id}`)
|
||||||
|
.then((resp) => {
|
||||||
|
return resp.data[0];
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
NotifyResponse(err, 'error');
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function Me() {
|
||||||
|
return appApi.get('/login/me');
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<q-btn dense flat round icon="person" :color="currentUser ? 'green' : ''">
|
<q-btn dense flat round icon="person" :color="currentUser ? 'green' : ''">
|
||||||
<q-menu ref="refLoginMenu">
|
<q-menu ref="refLoginMenu">
|
||||||
<q-list style="min-width: 120px">
|
<q-list style="min-width: 120px">
|
||||||
<q-item v-if="user.user" class="text-primary">{{ currentUser?.username }}</q-item>
|
<q-item v-if="user.user" class="text-primary">{{ currentUser?.user }}</q-item>
|
||||||
<q-item v-if="showLogin" clickable v-close-popup @click="openLogin">
|
<q-item v-if="showLogin" clickable v-close-popup @click="openLogin">
|
||||||
<q-item-section class="text-primary">{{ loginText }}</q-item-section>
|
<q-item-section class="text-primary">{{ loginText }}</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
@@ -69,7 +69,7 @@ const darkMode = computed(() => {
|
|||||||
return 'dark_mode';
|
return 'dark_mode';
|
||||||
});
|
});
|
||||||
const showLogin = computed(
|
const showLogin = computed(
|
||||||
() => (route.path !== '/' && route.path !== '/login') || currentUser.value?.username === '',
|
() => (route.path !== '/' && route.path !== '/login') || currentUser.value?.user === '',
|
||||||
);
|
);
|
||||||
|
|
||||||
const autorized = computed(() => !!user.isAuthorizedAs(['admin']));
|
const autorized = computed(() => !!user.isAuthorizedAs(['admin']));
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { Settings } from '../models/settings';
|
|||||||
import { appName, logo } from '../models/settings';
|
import { appName, logo } from '../models/settings';
|
||||||
import { clearLocalStorage, setLocalSettings } from 'src/localstorage/localStorage';
|
import { clearLocalStorage, setLocalSettings } from 'src/localstorage/localStorage';
|
||||||
import { routerInstance } from 'src/router';
|
import { routerInstance } from 'src/router';
|
||||||
|
import { Me } from '../components/DatabaseCall';
|
||||||
|
|
||||||
const refreshTime = 10000;
|
const refreshTime = 10000;
|
||||||
let intervalId: ReturnType<typeof setInterval> | null = null;
|
let intervalId: ReturnType<typeof setInterval> | null = null;
|
||||||
@@ -31,12 +32,14 @@ export function useLogin() {
|
|||||||
setLocalSettings(sets);
|
setLocalSettings(sets);
|
||||||
});
|
});
|
||||||
|
|
||||||
const resp = await appApi.get('/login/me');
|
const resp = await Me();
|
||||||
|
|
||||||
await userStore
|
await userStore
|
||||||
.setUser({
|
.setUser({
|
||||||
id: resp.data.id,
|
id: resp.data.id,
|
||||||
username: resp.data.user,
|
user: resp.data.user,
|
||||||
role: { role: resp.data.role, permissions: [] },
|
role: { role: resp.data.role, permissions: [] },
|
||||||
|
workspaceId: resp.data.workspace,
|
||||||
})
|
})
|
||||||
.catch((err) => NotifyResponse(err, 'error'));
|
.catch((err) => NotifyResponse(err, 'error'));
|
||||||
|
|
||||||
@@ -60,31 +63,31 @@ export function useLogin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function refresh() {
|
async function refresh() {
|
||||||
await appApi
|
await appApi.post('login/refresh', {}, { withCredentials: true }).catch(() => {
|
||||||
.post('login/refresh', {}, { withCredentials: true })
|
userStore.clearUser();
|
||||||
.then(() => {
|
return;
|
||||||
appApi
|
});
|
||||||
.get('/login/me')
|
|
||||||
.then((resp) => {
|
const resp = await Me();
|
||||||
userStore
|
if (!resp) {
|
||||||
.setUser({
|
stopRefreshInterval();
|
||||||
id: resp.data.id,
|
return false;
|
||||||
username: resp.data.user,
|
}
|
||||||
role: { role: resp.data.role, permissions: [] },
|
userStore
|
||||||
})
|
.setUser({
|
||||||
.catch((err) => NotifyResponse(err, 'error'));
|
id: resp.data.id,
|
||||||
if (!intervalId) {
|
user: resp.data.user,
|
||||||
startRefreshInterval();
|
role: { role: resp.data.role, permissions: [] },
|
||||||
}
|
workspaceId: resp.data.workspace,
|
||||||
return true;
|
settings: resp.data.settings,
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((err) => NotifyResponse(err, 'error'));
|
||||||
userStore.clearUser();
|
|
||||||
});
|
if (!intervalId) {
|
||||||
stopRefreshInterval();
|
startRefreshInterval();
|
||||||
return false;
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function startRefreshInterval() {
|
function startRefreshInterval() {
|
||||||
|
|||||||
@@ -25,8 +25,17 @@ export const useUserStore = defineStore('user', {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
isPermittedTo: (state: UserState) => {
|
isPermittedTo: (state: UserState) => {
|
||||||
return (name: string, type: 'read' | 'write' | 'delete' | 'import' | 'export'): boolean => {
|
return (
|
||||||
|
name: string,
|
||||||
|
type: 'read' | 'write' | 'delete' | 'import' | 'export',
|
||||||
|
compareRole?: Role,
|
||||||
|
): boolean => {
|
||||||
const permission = state.user?.permissions?.find((r: Permission) => r.name === name);
|
const permission = state.user?.permissions?.find((r: Permission) => r.name === name);
|
||||||
|
if (compareRole && permission) {
|
||||||
|
const rolePermission = compareRole.permissions?.find((r: Permission) => r.name === name);
|
||||||
|
if (rolePermission && rolePermission?.permission > permission?.permission) return false;
|
||||||
|
}
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'read':
|
case 'read':
|
||||||
return permission?.permission ? (permission.permission & (1 << 0)) === 1 : false;
|
return permission?.permission ? (permission.permission & (1 << 0)) === 1 : false;
|
||||||
@@ -41,6 +50,9 @@ export const useUserStore = defineStore('user', {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
getWorkspaceUuid: (state: UserState) => {
|
||||||
|
return state.user?.workspaces?.find((w) => w.id === state.user?.workspaceId)?.uuid;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setFirstLogin(b: boolean) {
|
setFirstLogin(b: boolean) {
|
||||||
@@ -62,7 +74,7 @@ export const useUserStore = defineStore('user', {
|
|||||||
if (!this.user) return;
|
if (!this.user) return;
|
||||||
if ($q) {
|
if ($q) {
|
||||||
$q?.notify({
|
$q?.notify({
|
||||||
message: "user '" + this.user?.username + "' logged out",
|
message: "user '" + this.user?.user + "' logged out",
|
||||||
color: 'orange',
|
color: 'orange',
|
||||||
position: 'top',
|
position: 'top',
|
||||||
icon: 'warning',
|
icon: 'warning',
|
||||||
@@ -80,7 +92,7 @@ export const useUserStore = defineStore('user', {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.error("user '" + this.user?.username + "' logged out");
|
console.error("user '" + this.user?.user + "' logged out");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.user = null;
|
this.user = null;
|
||||||
@@ -108,9 +120,12 @@ export const useUserStore = defineStore('user', {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.error("user '" + this.user?.username + "' logged out");
|
console.error("user '" + this.user?.user + "' logged out");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
isAdmin() {
|
||||||
|
return this.user?.role.role.includes('admin');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
export const logo = ref('');
|
export const logo = ref<string>('');
|
||||||
export const appName = ref('Attendance Records');
|
export const appName = ref<string>('Attendance Records');
|
||||||
export const databaseName = ref('members.dba');
|
|
||||||
|
|
||||||
export type Settings = {
|
export type Settings = {
|
||||||
appName: string;
|
appName: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
databaseName: string;
|
databaseName?: string;
|
||||||
primaryColor: string;
|
primaryColor: string;
|
||||||
primaryColorText: string;
|
primaryColorText: string;
|
||||||
secondaryColor: string;
|
secondaryColor: string;
|
||||||
@@ -18,7 +17,6 @@ export function DefaultSettings(): Settings {
|
|||||||
return {
|
return {
|
||||||
appName: 'Attendance Records',
|
appName: 'Attendance Records',
|
||||||
icon: '',
|
icon: '',
|
||||||
databaseName: 'members.dba',
|
|
||||||
primaryColor: document.documentElement.style.getPropertyValue('--q-primary-text'),
|
primaryColor: document.documentElement.style.getPropertyValue('--q-primary-text'),
|
||||||
primaryColorText: document.documentElement.style.getPropertyValue('--q-primary'),
|
primaryColorText: document.documentElement.style.getPropertyValue('--q-primary'),
|
||||||
secondaryColor: document.documentElement.style.getPropertyValue('--q-secondary'),
|
secondaryColor: document.documentElement.style.getPropertyValue('--q-secondary'),
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import type { Permissions } from '../checkboxes/permissions';
|
import type { Permissions } from '../checkboxes/permissions';
|
||||||
import type { Role } from './roles';
|
import type { Role } from './roles';
|
||||||
import type { Settings } from './settings';
|
import type { Settings } from './settings';
|
||||||
|
import type { Workspaces } from './workspaces';
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
id: number;
|
id: number;
|
||||||
username: string;
|
user: string;
|
||||||
role: Role;
|
role: Role;
|
||||||
permissions?: Permissions;
|
permissions?: Permissions;
|
||||||
settings?: Settings;
|
settings?: Settings;
|
||||||
newDatabase?: boolean;
|
newDatabase?: boolean;
|
||||||
|
workspaceId?: number | undefined;
|
||||||
|
workspaces?: Workspaces;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserState {
|
export interface UserState {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { Role } from './roles';
|
import type { Role } from './roles';
|
||||||
import type { Settings } from './settings';
|
import type { Settings } from './settings';
|
||||||
|
import type { Workspace } from './workspaces';
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
id?: number;
|
id?: number;
|
||||||
@@ -11,6 +12,8 @@ export interface User {
|
|||||||
password?: string;
|
password?: string;
|
||||||
newPassword?: string;
|
newPassword?: string;
|
||||||
settings?: Settings;
|
settings?: Settings;
|
||||||
|
workspaceId?: number;
|
||||||
|
workspaces?: Workspace[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Users = User[];
|
export type Users = User[];
|
||||||
|
|||||||
8
src/vueLib/models/workspaces.ts
Normal file
8
src/vueLib/models/workspaces.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export interface Workspace {
|
||||||
|
id?: number;
|
||||||
|
name: string;
|
||||||
|
uuid?: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Workspaces = Workspace[];
|
||||||
@@ -63,10 +63,10 @@ export function useEventTable() {
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
//updates Event list from database
|
//updates Event list from database
|
||||||
function updateEvents() {
|
async function updateEvents() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
appApi
|
await appApi
|
||||||
.get('events')
|
.get('events')
|
||||||
.then((resp) => {
|
.then((resp) => {
|
||||||
if (resp.data === null) {
|
if (resp.data === null) {
|
||||||
|
|||||||
@@ -116,14 +116,13 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { appApi } from 'src/boot/axios';
|
import { appApi } from 'src/boot/axios';
|
||||||
import { ref, onMounted } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import type { Event, Events } from 'src/vueLib/models/event';
|
import type { Event, Events } from 'src/vueLib/models/event';
|
||||||
import EditOneDialog from 'src/components/EditOneDialog.vue';
|
import EditOneDialog from 'src/components/EditOneDialog.vue';
|
||||||
import EditAllDialog from 'src/components/EventEditAllDialog.vue';
|
import EditAllDialog from 'src/components/EventEditAllDialog.vue';
|
||||||
import OkDialog from 'src/components/dialog/OkDialog.vue';
|
import OkDialog from 'src/components/dialog/OkDialog.vue';
|
||||||
import { useNotify } from 'src/vueLib/general/useNotify';
|
import { useNotify } from 'src/vueLib/general/useNotify';
|
||||||
import { useEventTable } from './EventsTable';
|
import { useEventTable } from './EventsTable';
|
||||||
import { databaseName } from 'src/vueLib/models/settings';
|
|
||||||
import { useUserStore } from 'src/vueLib/login/userStore';
|
import { useUserStore } from 'src/vueLib/login/userStore';
|
||||||
import AttendeesTableDialog from '../attendees/AttendeesTableDialog.vue';
|
import AttendeesTableDialog from '../attendees/AttendeesTableDialog.vue';
|
||||||
import type { Members } from 'src/vueLib/models/member';
|
import type { Members } from 'src/vueLib/models/member';
|
||||||
@@ -149,20 +148,8 @@ const user = useUserStore();
|
|||||||
|
|
||||||
const { Events, pagination, loading, columns, updateEvents } = useEventTable();
|
const { Events, pagination, loading, columns, updateEvents } = useEventTable();
|
||||||
|
|
||||||
//load on mounting page
|
onMounted(async () => {
|
||||||
onMounted(() => {
|
await updateEvents();
|
||||||
loading.value = true;
|
|
||||||
|
|
||||||
appApi
|
|
||||||
.post('database/open', { dbPath: databaseName.value, create: true })
|
|
||||||
.then(() => {
|
|
||||||
updateEvents();
|
|
||||||
})
|
|
||||||
.catch((err) => NotifyResponse(err, 'error'))
|
|
||||||
|
|
||||||
.finally(() => {
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// opens dialog for all Event values
|
// opens dialog for all Event values
|
||||||
@@ -194,17 +181,17 @@ function openAttendees(eventArray: number, attendees: Members | null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//remove Event from database
|
//remove Event from database
|
||||||
function removeEvent(...removeEvents: Events) {
|
async function removeEvent(...removeEvents: Events) {
|
||||||
const EventIds: number[] = [];
|
const EventIds: number[] = [];
|
||||||
|
|
||||||
removeEvents.forEach((Event: Event) => {
|
removeEvents.forEach((Event: Event) => {
|
||||||
EventIds.push(Event.id);
|
EventIds.push(Event.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
appApi
|
await appApi
|
||||||
.post('events/delete', { ids: EventIds })
|
.post('events/delete', { ids: EventIds })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
updateEvents();
|
updateEvents().catch((err) => NotifyResponse(err, 'error'));
|
||||||
selected.value = [];
|
selected.value = [];
|
||||||
})
|
})
|
||||||
.catch((err) => NotifyResponse(err, 'error'))
|
.catch((err) => NotifyResponse(err, 'error'))
|
||||||
|
|||||||
@@ -116,7 +116,6 @@ import DialogFrame from 'src/vueLib/dialog/DialogFrame.vue';
|
|||||||
import OkDialog from 'src/components/dialog/OkDialog.vue';
|
import OkDialog from 'src/components/dialog/OkDialog.vue';
|
||||||
import { useNotify } from 'src/vueLib/general/useNotify';
|
import { useNotify } from 'src/vueLib/general/useNotify';
|
||||||
import { useGroupTable } from './GroupTable';
|
import { useGroupTable } from './GroupTable';
|
||||||
import { databaseName } from 'src/vueLib/models/settings';
|
|
||||||
import { useUserStore } from 'src/vueLib/login/userStore';
|
import { useUserStore } from 'src/vueLib/login/userStore';
|
||||||
import { i18n } from 'src/boot/lang';
|
import { i18n } from 'src/boot/lang';
|
||||||
import type { Group, Groups } from 'src/vueLib/models/group';
|
import type { Group, Groups } from 'src/vueLib/models/group';
|
||||||
@@ -146,19 +145,8 @@ onMounted(async () => {
|
|||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
members.value = await getAllMembers();
|
members.value = await getAllMembers();
|
||||||
|
await updateGroups();
|
||||||
appApi
|
loading.value = false;
|
||||||
.post('database/open', { dbPath: databaseName.value, create: true })
|
|
||||||
.then(() => {
|
|
||||||
updateGroups().catch((err) => {
|
|
||||||
NotifyResponse(err, 'error');
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((err) => NotifyResponse(err, 'error'))
|
|
||||||
|
|
||||||
.finally(() => {
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//opens dialog for one value
|
//opens dialog for one value
|
||||||
|
|||||||
@@ -206,7 +206,6 @@ import { useNotify } from 'src/vueLib/general/useNotify';
|
|||||||
import { useMemberTable } from './MembersTable';
|
import { useMemberTable } from './MembersTable';
|
||||||
import UploadDialog from 'src/components/UploadDialog.vue';
|
import UploadDialog from 'src/components/UploadDialog.vue';
|
||||||
import AddToEvent from 'src/components/AddToEvent.vue';
|
import AddToEvent from 'src/components/AddToEvent.vue';
|
||||||
import { databaseName } from 'src/vueLib/models/settings';
|
|
||||||
import { useUserStore } from 'src/vueLib/login/userStore';
|
import { useUserStore } from 'src/vueLib/login/userStore';
|
||||||
import { i18n } from 'src/boot/lang';
|
import { i18n } from 'src/boot/lang';
|
||||||
import type { Responsible } from 'src/vueLib/models/responsible';
|
import type { Responsible } from 'src/vueLib/models/responsible';
|
||||||
@@ -263,7 +262,7 @@ const {
|
|||||||
} = useMemberTable();
|
} = useMemberTable();
|
||||||
|
|
||||||
//load on mounting page
|
//load on mounting page
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
page.value = 'members';
|
page.value = 'members';
|
||||||
if (inProps.addAttendees || inProps.addResponsible) {
|
if (inProps.addAttendees || inProps.addResponsible) {
|
||||||
selectOption.value = true;
|
selectOption.value = true;
|
||||||
@@ -295,16 +294,10 @@ onMounted(() => {
|
|||||||
// set custom filter
|
// set custom filter
|
||||||
setNewFilter(selectedColumnFilter.value, ...selectedColumnOptions.value);
|
setNewFilter(selectedColumnFilter.value, ...selectedColumnOptions.value);
|
||||||
|
|
||||||
appApi
|
await updateMembers(localCompareMembers.value, inProps.addResponsible).catch((err) =>
|
||||||
.post('database/open', { dbPath: databaseName.value, create: true })
|
NotifyResponse(err, 'error'),
|
||||||
.then(() => {
|
);
|
||||||
updateTable().catch((err) => NotifyResponse(err, 'error'));
|
loading.value = false;
|
||||||
})
|
|
||||||
.catch((err) => NotifyResponse(err, 'error'))
|
|
||||||
|
|
||||||
.finally(() => {
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
async function updateTable(add?: Members) {
|
async function updateTable(add?: Members) {
|
||||||
|
|||||||
@@ -101,14 +101,13 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { appApi } from 'src/boot/axios';
|
import { appApi } from 'src/boot/axios';
|
||||||
import { ref, onMounted } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import DialogFrame from 'src/vueLib/dialog/DialogFrame.vue';
|
import DialogFrame from 'src/vueLib/dialog/DialogFrame.vue';
|
||||||
import MembersTable from '../members/MembersTable.vue';
|
import MembersTable from '../members/MembersTable.vue';
|
||||||
import type { Members } from 'src/vueLib/models/member';
|
import type { Members } from 'src/vueLib/models/member';
|
||||||
import OkDialog from 'src/components/dialog/OkDialog.vue';
|
import OkDialog from 'src/components/dialog/OkDialog.vue';
|
||||||
import { useNotify } from 'src/vueLib/general/useNotify';
|
import { useNotify } from 'src/vueLib/general/useNotify';
|
||||||
import { useResponsibleTable } from './ResponsibleTable';
|
import { useResponsibleTable } from './ResponsibleTable';
|
||||||
import { databaseName } from 'src/vueLib/models/settings';
|
|
||||||
import { useUserStore } from 'src/vueLib/login/userStore';
|
import { useUserStore } from 'src/vueLib/login/userStore';
|
||||||
import { i18n } from 'src/boot/lang';
|
import { i18n } from 'src/boot/lang';
|
||||||
import type { Responsible, Responsibles } from 'src/vueLib/models/responsible';
|
import type { Responsible, Responsibles } from 'src/vueLib/models/responsible';
|
||||||
@@ -128,22 +127,8 @@ const user = useUserStore();
|
|||||||
const { responsibleMember, pagination, loading, columns, updateResponsibles } =
|
const { responsibleMember, pagination, loading, columns, updateResponsibles } =
|
||||||
useResponsibleTable();
|
useResponsibleTable();
|
||||||
|
|
||||||
//load on mounting page
|
onMounted(async () => {
|
||||||
onMounted(() => {
|
await updateResponsibles();
|
||||||
loading.value = true;
|
|
||||||
|
|
||||||
appApi
|
|
||||||
.post('database/open', { dbPath: databaseName.value, create: true })
|
|
||||||
.then(() => {
|
|
||||||
updateResponsibles().catch((err) => {
|
|
||||||
NotifyResponse(err, 'error');
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((err) => NotifyResponse(err, 'error'))
|
|
||||||
|
|
||||||
.finally(() => {
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//opens dialog for one value
|
//opens dialog for one value
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { i18n } from 'boot/lang';
|
|||||||
import type { Roles } from 'src/vueLib/models/roles';
|
import type { Roles } from 'src/vueLib/models/roles';
|
||||||
import { useUserStore } from 'src/vueLib/login/userStore';
|
import { useUserStore } from 'src/vueLib/login/userStore';
|
||||||
import { useLogin } from 'src/vueLib/login/useLogin';
|
import { useLogin } from 'src/vueLib/login/useLogin';
|
||||||
|
import { Me } from 'src/vueLib/components/DatabaseCall';
|
||||||
|
|
||||||
export const roles = ref<Roles>([]);
|
export const roles = ref<Roles>([]);
|
||||||
|
|
||||||
@@ -64,17 +65,27 @@ export function useRoleTable() {
|
|||||||
.finally(() => {
|
.finally(() => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
});
|
});
|
||||||
await appApi
|
|
||||||
.get('/login/me')
|
const resp = await Me().catch(() => {
|
||||||
.then((resp) => {
|
login.logout().catch((err) => {
|
||||||
userStore
|
NotifyResponse(err, 'error');
|
||||||
.setUser({ id: resp.data.id, username: resp.data.username, role: resp.data.role })
|
return;
|
||||||
.catch((err) => NotifyResponse(err, 'error'));
|
|
||||||
login.refresh().catch((err) => NotifyResponse(err, 'error'));
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
login.logout().catch((err) => NotifyResponse(err, 'error'));
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!resp) return;
|
||||||
|
|
||||||
|
await userStore
|
||||||
|
.setUser({
|
||||||
|
id: resp.data.id,
|
||||||
|
user: resp.data.user,
|
||||||
|
role: { role: resp.data.role, permissions: [] },
|
||||||
|
workspaceId: resp.data.workspaceId,
|
||||||
|
settings: resp.data.settings,
|
||||||
|
})
|
||||||
|
.catch((err) => NotifyResponse(err, 'error'));
|
||||||
|
|
||||||
|
login.refresh().catch((err) => NotifyResponse(err, 'error'));
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
roles,
|
roles,
|
||||||
|
|||||||
@@ -23,17 +23,11 @@
|
|||||||
>
|
>
|
||||||
<template v-slot:top-left>
|
<template v-slot:top-left>
|
||||||
<q-btn-group push flat style="color: grey">
|
<q-btn-group push flat style="color: grey">
|
||||||
<q-btn
|
<q-btn v-if="writePermisssion" dense flat icon="add" @click="openAllValueDialog(null)">
|
||||||
v-if="user.isPermittedTo('userSettings', 'write')"
|
|
||||||
dense
|
|
||||||
flat
|
|
||||||
icon="add"
|
|
||||||
@click="openAllValueDialog(null)"
|
|
||||||
>
|
|
||||||
<q-tooltip>{{ $t('addNewRole') }}</q-tooltip>
|
<q-tooltip>{{ $t('addNewRole') }}</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="user.isPermittedTo('userSettings', 'write')"
|
v-if="writePermisssion"
|
||||||
dense
|
dense
|
||||||
flat
|
flat
|
||||||
style="color: grey"
|
style="color: grey"
|
||||||
@@ -72,13 +66,9 @@
|
|||||||
<q-td
|
<q-td
|
||||||
:props="props"
|
:props="props"
|
||||||
:disable="!autorized(props.row)"
|
:disable="!autorized(props.row)"
|
||||||
:style="
|
:style="autorized(props.row) && writePermisssion ? 'cursor: pointer' : ''"
|
||||||
autorized(props.row) && user.isPermittedTo('userSettings', 'write')
|
|
||||||
? 'cursor: pointer'
|
|
||||||
: ''
|
|
||||||
"
|
|
||||||
@click="
|
@click="
|
||||||
autorized(props.row) && user.isPermittedTo('userSettings', 'write')
|
autorized(props.row) && writePermisssion
|
||||||
? openSingleValueDialog(props.col.label, props.col.name, props.row)
|
? openSingleValueDialog(props.col.label, props.col.name, props.row)
|
||||||
: ''
|
: ''
|
||||||
"
|
"
|
||||||
@@ -89,19 +79,18 @@
|
|||||||
<template v-slot:body-cell-permissions="props">
|
<template v-slot:body-cell-permissions="props">
|
||||||
<q-td :props="props">
|
<q-td :props="props">
|
||||||
<q-btn
|
<q-btn
|
||||||
:disable="!autorized(props.row) || !user.isPermittedTo('userSettings', 'write')"
|
:disable="
|
||||||
|
!autorized(props.row) || !writePermisssion || user.user?.role.role === props.row.role
|
||||||
|
"
|
||||||
flat
|
flat
|
||||||
dense
|
dense
|
||||||
icon="rule"
|
icon="rule"
|
||||||
:color="
|
:color="
|
||||||
autorized(props.row) && user.isPermittedTo('userSettings', 'write')
|
autorized(props.row) && writePermisssion && user.user?.role.role !== props.row.role
|
||||||
? 'secondary'
|
? 'secondary'
|
||||||
: 'grey'
|
: 'grey'
|
||||||
"
|
"
|
||||||
@click="
|
@click="writePermisssion && openAllValueDialog(props.row, 'permissions')"
|
||||||
user.isPermittedTo('userSettings', 'write') &&
|
|
||||||
openAllValueDialog(props.row, 'permissions')
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<q-tooltip> {{ $t('permissions') }} </q-tooltip>
|
<q-tooltip> {{ $t('permissions') }} </q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
@@ -145,7 +134,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { appApi } from 'src/boot/axios';
|
import { appApi } from 'src/boot/axios';
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted, computed } from 'vue';
|
||||||
import type { Roles, Role } from 'src/vueLib/models/roles';
|
import type { Roles, Role } from 'src/vueLib/models/roles';
|
||||||
import EditOneDialog from 'src/components/EditOneDialog.vue';
|
import EditOneDialog from 'src/components/EditOneDialog.vue';
|
||||||
import EditAllDialog from 'src/components/RoleEditAllDialog.vue';
|
import EditAllDialog from 'src/components/RoleEditAllDialog.vue';
|
||||||
@@ -158,6 +147,8 @@ import { useUserStore } from 'src/vueLib/login/userStore';
|
|||||||
import SearchableInput from '../components/SearchableInput.vue';
|
import SearchableInput from '../components/SearchableInput.vue';
|
||||||
|
|
||||||
const { NotifyResponse } = useNotify();
|
const { NotifyResponse } = useNotify();
|
||||||
|
const { roles, pagination, loading, columns, updateRoles } = useRoleTable();
|
||||||
|
|
||||||
const editOneDialog = ref();
|
const editOneDialog = ref();
|
||||||
const editAllDialog = ref();
|
const editAllDialog = ref();
|
||||||
const okDialog = ref();
|
const okDialog = ref();
|
||||||
@@ -169,7 +160,7 @@ const currentUser = ref();
|
|||||||
const filter = ref('');
|
const filter = ref('');
|
||||||
const user = useUserStore();
|
const user = useUserStore();
|
||||||
|
|
||||||
const { roles, pagination, loading, columns, updateRoles } = useRoleTable();
|
const writePermisssion = computed(() => user.isPermittedTo('userSettings', 'write'));
|
||||||
|
|
||||||
//load on mounting page
|
//load on mounting page
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -234,10 +225,6 @@ function removeRole(...removeRoles: Roles) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.blink-yellow {
|
|
||||||
animation: blink-yellow 1.5s step-start 6 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bigger-table-text .q-table__middle td {
|
.bigger-table-text .q-table__middle td {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,14 @@ export function useUserTable() {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
style: 'width: 120px; max-width: 120px;',
|
style: 'width: 120px; max-width: 120px;',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'workspaces',
|
||||||
|
align: 'left' as const,
|
||||||
|
label: i18n.global.t('workspaces'),
|
||||||
|
field: 'workspaces',
|
||||||
|
sortable: true,
|
||||||
|
style: 'width: 120px; max-width: 120px;',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'expiration',
|
name: 'expiration',
|
||||||
align: 'left' as const,
|
align: 'left' as const,
|
||||||
@@ -59,9 +67,9 @@ export function useUserTable() {
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
//updates user list from database
|
//updates user list from database
|
||||||
function updateUsers() {
|
async function updateUsers() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
appApi
|
await appApi
|
||||||
.get('users')
|
.get('users')
|
||||||
.then((resp) => {
|
.then((resp) => {
|
||||||
if (resp.data === null) {
|
if (resp.data === null) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
:loading-label="$t('loading')"
|
:loading-label="$t('loading')"
|
||||||
:rows-per-page-label="$t('recordsPerPage')"
|
:rows-per-page-label="$t('recordsPerPage')"
|
||||||
:selected-rows-label="(val) => val + ' ' + $t('recordSelected')"
|
:selected-rows-label="(val) => val + ' ' + $t('recordSelected')"
|
||||||
:rows="users"
|
:rows="localUsers"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
row-key="id"
|
row-key="id"
|
||||||
v-model:pagination="pagination"
|
v-model:pagination="pagination"
|
||||||
@@ -23,17 +23,11 @@
|
|||||||
>
|
>
|
||||||
<template v-slot:top-left>
|
<template v-slot:top-left>
|
||||||
<q-btn-group push flat style="color: grey">
|
<q-btn-group push flat style="color: grey">
|
||||||
<q-btn
|
<q-btn v-if="writePermission" dense flat icon="add" @click="openAllValueDialog(null)">
|
||||||
v-if="user.isPermittedTo('userSettings', 'write')"
|
|
||||||
dense
|
|
||||||
flat
|
|
||||||
icon="add"
|
|
||||||
@click="openAllValueDialog(null)"
|
|
||||||
>
|
|
||||||
<q-tooltip>{{ $t('addNewUser') }}</q-tooltip>
|
<q-tooltip>{{ $t('addNewUser') }}</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="user.isPermittedTo('userSettings', 'write')"
|
v-if="writePermission"
|
||||||
dense
|
dense
|
||||||
flat
|
flat
|
||||||
style="color: grey"
|
style="color: grey"
|
||||||
@@ -43,7 +37,9 @@
|
|||||||
<q-tooltip>{{ $t('selectUserOptions') }}</q-tooltip>
|
<q-tooltip>{{ $t('selectUserOptions') }}</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</q-btn-group>
|
</q-btn-group>
|
||||||
<div v-if="selectOption && selected.length > 0">
|
<div
|
||||||
|
v-if="selectOption && selected.length > 0 && user.isPermittedTo('userSettings', 'delete')"
|
||||||
|
>
|
||||||
<q-btn flat dense icon="more_vert" @click="openSubmenu = true" />
|
<q-btn flat dense icon="more_vert" @click="openSubmenu = true" />
|
||||||
<q-menu v-if="openSubmenu" anchor="bottom middle" self="top middle">
|
<q-menu v-if="openSubmenu" anchor="bottom middle" self="top middle">
|
||||||
<q-item
|
<q-item
|
||||||
@@ -71,13 +67,9 @@
|
|||||||
<template v-slot:body-cell="props">
|
<template v-slot:body-cell="props">
|
||||||
<q-td
|
<q-td
|
||||||
:props="props"
|
:props="props"
|
||||||
:style="
|
:style="autorized(props.row) && writePermission ? 'cursor: pointer' : ''"
|
||||||
autorized(props.row) && user.isPermittedTo('userSettings', 'write')
|
|
||||||
? 'cursor: pointer'
|
|
||||||
: ''
|
|
||||||
"
|
|
||||||
@click="
|
@click="
|
||||||
autorized(props.row) && user.isPermittedTo('userSettings', 'write')
|
autorized(props.row) && writePermission
|
||||||
? openSingleValueDialog(props.col.label, props.col.name, props.row)
|
? openSingleValueDialog(props.col.label, props.col.name, props.row)
|
||||||
: ''
|
: ''
|
||||||
"
|
"
|
||||||
@@ -101,7 +93,11 @@
|
|||||||
<template v-slot:body-cell-role="props">
|
<template v-slot:body-cell-role="props">
|
||||||
<q-td :props="props">
|
<q-td :props="props">
|
||||||
<q-select
|
<q-select
|
||||||
:readonly="!user.isPermittedTo('userSettings', 'write') || !autorized(props.row)"
|
:readonly="
|
||||||
|
user.user?.id === props.row.id ||
|
||||||
|
!user.isPermittedTo('userSettings', 'write', props.row.role) ||
|
||||||
|
!autorized(props.row)
|
||||||
|
"
|
||||||
dense
|
dense
|
||||||
v-model="props.row.role"
|
v-model="props.row.role"
|
||||||
:options="localRoles"
|
:options="localRoles"
|
||||||
@@ -110,16 +106,25 @@
|
|||||||
></q-select>
|
></q-select>
|
||||||
</q-td>
|
</q-td>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-slot:body-cell-workspaces="props">
|
||||||
|
<q-td :props="props">
|
||||||
|
<q-select
|
||||||
|
:readonly="props.row.id === user.user?.id || !autorized(props.row) || !writePermission"
|
||||||
|
dense
|
||||||
|
v-model="props.row.workspaces"
|
||||||
|
:options="localWorkspaces"
|
||||||
|
option-label="name"
|
||||||
|
multiple
|
||||||
|
@update:model-value="updateUser(props.row)"
|
||||||
|
></q-select>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
<template v-slot:body-cell-expiration="props">
|
<template v-slot:body-cell-expiration="props">
|
||||||
<q-td
|
<q-td
|
||||||
:props="props"
|
:props="props"
|
||||||
:style="
|
:style="autorized(props.row) && writePermission ? 'cursor: pointer' : ''"
|
||||||
autorized(props.row) && user.isPermittedTo('userSettings', 'write')
|
|
||||||
? 'cursor: pointer'
|
|
||||||
: ''
|
|
||||||
"
|
|
||||||
@click="
|
@click="
|
||||||
autorized(props.row) && user.isPermittedTo('userSettings', 'write')
|
autorized(props.row) && writePermission
|
||||||
? openSingleValueDialog(props.col.label, props.col.name, props.row)
|
? openSingleValueDialog(props.col.label, props.col.name, props.row)
|
||||||
: ''
|
: ''
|
||||||
"
|
"
|
||||||
@@ -148,7 +153,7 @@
|
|||||||
query-id
|
query-id
|
||||||
v-on:update="(val) => updateUser(val)"
|
v-on:update="(val) => updateUser(val)"
|
||||||
></EditOneDialog>
|
></EditOneDialog>
|
||||||
<EditAllDialog ref="editAllDialog" :roles="roles" v-on:update="updateUsers"></EditAllDialog>
|
<EditAllDialog ref="editAllDialog" :roles="localRoles" v-on:update="updateUsers"></EditAllDialog>
|
||||||
<OkDialog
|
<OkDialog
|
||||||
ref="okDialog"
|
ref="okDialog"
|
||||||
:dialog-label="$t('delete')"
|
:dialog-label="$t('delete')"
|
||||||
@@ -177,34 +182,73 @@ import { i18n } from 'src/boot/lang';
|
|||||||
import { useUserStore } from 'src/vueLib/login/userStore';
|
import { useUserStore } from 'src/vueLib/login/userStore';
|
||||||
import ChangePassword from 'src/vueLib/login/ChangePassword.vue';
|
import ChangePassword from 'src/vueLib/login/ChangePassword.vue';
|
||||||
import SearchableInput from '../components/SearchableInput.vue';
|
import SearchableInput from '../components/SearchableInput.vue';
|
||||||
|
import { useWorkspaceTable } from '../workspaces/WorkspaceTable';
|
||||||
|
|
||||||
const { NotifyResponse } = useNotify();
|
const { NotifyResponse } = useNotify();
|
||||||
|
const { users, pagination, loading, columns, updateUsers } = useUserTable();
|
||||||
|
const { updateRoles } = useRoleTable();
|
||||||
|
const { updateWorkspaces } = useWorkspaceTable();
|
||||||
|
const user = useUserStore();
|
||||||
|
|
||||||
const editOneDialog = ref();
|
const editOneDialog = ref();
|
||||||
const editAllDialog = ref();
|
const editAllDialog = ref();
|
||||||
const okDialog = ref();
|
const okDialog = ref();
|
||||||
const deleteText = ref('');
|
const deleteText = ref('');
|
||||||
const localRoles = computed(() => {
|
|
||||||
return roles.value.filter((role) => {
|
const localUsers = computed(() => {
|
||||||
if (user.user?.role.role.includes('admin') || !user.user?.role.role.includes('admin'))
|
return users.value.filter((u) => {
|
||||||
return role;
|
if (user.isAdmin() || user.user?.id === u.id) return user;
|
||||||
|
const roleP = u.role?.permissions.find((p) => p.name === 'userSettings')?.permission;
|
||||||
|
const userP = user.user?.permissions?.find((p) => p.name === 'userSettings')?.permission;
|
||||||
|
const notInWorkspace = u.workspaces?.some((w) => currentUser.value.workspaces.includes(w.name));
|
||||||
|
|
||||||
|
if (
|
||||||
|
roleP === undefined ||
|
||||||
|
userP === undefined ||
|
||||||
|
(!notInWorkspace && u.workspaces?.length !== 0)
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
if (userP > roleP) return user;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const localRoles = computed(() => {
|
||||||
|
return roles.value.filter((role) => {
|
||||||
|
const roleP = role.permissions.find((p) => p.name === 'userSettings')?.permission;
|
||||||
|
const userP = user.user?.permissions?.find((p) => p.name === 'userSettings')?.permission;
|
||||||
|
if (roleP === undefined || userP === undefined) return;
|
||||||
|
if (userP < roleP) return;
|
||||||
|
if (user.isAdmin() || !role.role.includes('admin')) return role;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const localWorkspaces = computed(() => {
|
||||||
|
return users.value.filter((u) => u.id === user.user?.id)[0]?.workspaces;
|
||||||
|
});
|
||||||
|
|
||||||
const selectOption = ref(false);
|
const selectOption = ref(false);
|
||||||
const selected = ref<Users>([]);
|
const selected = ref<Users>([]);
|
||||||
const openSubmenu = ref(false);
|
const openSubmenu = ref(false);
|
||||||
const filter = ref('');
|
const filter = ref('');
|
||||||
const currentUser = ref();
|
const currentUser = ref();
|
||||||
const { users, pagination, loading, columns, updateUsers } = useUserTable();
|
|
||||||
const { updateRoles } = useRoleTable();
|
|
||||||
const user = useUserStore();
|
|
||||||
const changePwdDialog = ref();
|
const changePwdDialog = ref();
|
||||||
|
|
||||||
|
const writePermission = computed(() => user.isPermittedTo('userSettings', 'write'));
|
||||||
|
|
||||||
//load on mounting page
|
//load on mounting page
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
currentUser.value = user.user;
|
currentUser.value = user.user;
|
||||||
updateUsers();
|
|
||||||
updateRoles().catch((err) => NotifyResponse(err, 'error'));
|
await updateUsers().finally(() => {
|
||||||
|
const targetUser = users.value.find((u) => u.id === currentUser.value.id);
|
||||||
|
if (targetUser && targetUser.workspaces) {
|
||||||
|
currentUser.value.workspaces = targetUser.workspaces.map((ws) => ws.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await updateRoles().catch((err) => NotifyResponse(err, 'error'));
|
||||||
|
await updateWorkspaces().catch((err) => NotifyResponse(err, 'error'));
|
||||||
|
// get workspaces of current user
|
||||||
});
|
});
|
||||||
|
|
||||||
//check authorization
|
//check authorization
|
||||||
@@ -264,7 +308,7 @@ function removeUser(...removeUsers: Users) {
|
|||||||
appApi
|
appApi
|
||||||
.post('users/delete?id=' + currentUser.value.id, { ids: userIds })
|
.post('users/delete?id=' + currentUser.value.id, { ids: userIds })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
updateUsers();
|
updateUsers().catch((err) => NotifyResponse(err, 'error'));
|
||||||
selected.value = [];
|
selected.value = [];
|
||||||
})
|
})
|
||||||
.catch((err) => NotifyResponse(err, 'error'))
|
.catch((err) => NotifyResponse(err, 'error'))
|
||||||
@@ -278,6 +322,7 @@ function updateUser(user: User) {
|
|||||||
if (user.role?.id) {
|
if (user.role?.id) {
|
||||||
user.roleId = user.role?.id;
|
user.roleId = user.role?.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
appApi
|
appApi
|
||||||
.post('/users/update', user)
|
.post('/users/update', user)
|
||||||
.then(() => NotifyResponse(i18n.global.t('userUpdated')))
|
.then(() => NotifyResponse(i18n.global.t('userUpdated')))
|
||||||
|
|||||||
78
src/vueLib/tables/workspaces/WorkspaceTable.ts
Normal file
78
src/vueLib/tables/workspaces/WorkspaceTable.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { appApi } from 'src/boot/axios';
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { useNotify } from 'src/vueLib/general/useNotify';
|
||||||
|
import { i18n } from 'boot/lang';
|
||||||
|
import type { Workspaces } from 'src/vueLib/models/workspaces';
|
||||||
|
// import { useUserStore } from 'src/vueLib/login/userStore';
|
||||||
|
// import { useLogin } from 'src/vueLib/login/useLogin';
|
||||||
|
|
||||||
|
export const workspaces = ref<Workspaces>([]);
|
||||||
|
|
||||||
|
export function useWorkspaceTable() {
|
||||||
|
const pagination = ref({
|
||||||
|
sortBy: 'name',
|
||||||
|
descending: false,
|
||||||
|
page: 1,
|
||||||
|
rowsPerPage: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
const columns = computed(() => [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
align: 'left' as const,
|
||||||
|
label: i18n.global.t('name'),
|
||||||
|
field: 'name',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'description',
|
||||||
|
align: 'left' as const,
|
||||||
|
label: i18n.global.t('description'),
|
||||||
|
field: 'description',
|
||||||
|
style: 'width: 120px; max-width: 120px;',
|
||||||
|
},
|
||||||
|
{ name: 'option', align: 'center' as const, label: '', field: 'option', icon: 'option' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { NotifyResponse } = useNotify();
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
// const userStore = useUserStore();
|
||||||
|
// const login = useLogin();
|
||||||
|
|
||||||
|
//updates user list from database
|
||||||
|
async function updateWorkspaces() {
|
||||||
|
loading.value = true;
|
||||||
|
await appApi
|
||||||
|
.get('workspaces?id=0')
|
||||||
|
.then((resp) => {
|
||||||
|
if (resp.data === null) {
|
||||||
|
workspaces.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
workspaces.value = resp.data as Workspaces;
|
||||||
|
|
||||||
|
if (workspaces.value === null) {
|
||||||
|
workspaces.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
.catch((err) => {
|
||||||
|
NotifyResponse(err, 'error');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
await appApi
|
||||||
|
.post('users/update', { id: 1, workspaces: workspaces.value })
|
||||||
|
.catch((err) => NotifyResponse(err, 'error'));
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
workspaces,
|
||||||
|
pagination,
|
||||||
|
columns,
|
||||||
|
loading,
|
||||||
|
updateWorkspaces,
|
||||||
|
};
|
||||||
|
}
|
||||||
223
src/vueLib/tables/workspaces/WorkspaceTable.vue
Normal file
223
src/vueLib/tables/workspaces/WorkspaceTable.vue
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
<template>
|
||||||
|
<q-table
|
||||||
|
flat
|
||||||
|
bordered
|
||||||
|
ref="tableRef"
|
||||||
|
title="Workspaces"
|
||||||
|
title-class="text-bold text-blue-9"
|
||||||
|
:no-data-label="$t('noDataAvailable')"
|
||||||
|
:loading-label="$t('loading')"
|
||||||
|
:rows-per-page-label="$t('recordsPerPage')"
|
||||||
|
:selected-rows-label="(val) => val + ' ' + $t('recordSelected')"
|
||||||
|
:rows="workspaces"
|
||||||
|
:columns="columns"
|
||||||
|
row-key="id"
|
||||||
|
v-model:pagination="pagination"
|
||||||
|
:loading="loading"
|
||||||
|
:filter="filter"
|
||||||
|
:selection="selectOption ? 'multiple' : 'none'"
|
||||||
|
v-model:selected="selected"
|
||||||
|
binary-state-sort
|
||||||
|
dense
|
||||||
|
class="bigger-table-text"
|
||||||
|
>
|
||||||
|
<template v-slot:top-left>
|
||||||
|
<q-btn-group push flat style="color: grey">
|
||||||
|
<q-btn
|
||||||
|
v-if="user.isPermittedTo('userSettings', 'write')"
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
icon="add"
|
||||||
|
@click="openAllValueDialog(null)"
|
||||||
|
>
|
||||||
|
<q-tooltip>{{ $t('addNewWorkspace') }}</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn
|
||||||
|
v-if="user.isPermittedTo('userSettings', 'write')"
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
style="color: grey"
|
||||||
|
:icon="selectOption ? 'check_box' : 'check_box_outline_blank'"
|
||||||
|
@click="selectOption = !selectOption"
|
||||||
|
>
|
||||||
|
<q-tooltip>{{ $t('selectWorkspaceOptions') }}</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</q-btn-group>
|
||||||
|
<div v-if="selectOption && selected.length > 0">
|
||||||
|
<q-btn flat dense icon="more_vert" @click="openSubmenu = true" />
|
||||||
|
<q-menu v-if="openSubmenu" anchor="bottom middle" self="top middle">
|
||||||
|
<q-item
|
||||||
|
clickable
|
||||||
|
v-close-popup
|
||||||
|
@click="openRemoveDialog(...selected)"
|
||||||
|
class="text-negative"
|
||||||
|
>{{ $t('delete') }}</q-item
|
||||||
|
>
|
||||||
|
</q-menu>
|
||||||
|
</div>
|
||||||
|
<div v-if="selectOption && selected.length > 0" class="q-ml-md text-weight-bold">
|
||||||
|
{{ $t('selected') }}: {{ selected.length }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- top right of table-->
|
||||||
|
|
||||||
|
<template v-slot:top-right>
|
||||||
|
<SearchableInput v-model="filter" :placeholder="$t('search')" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- table body content-->
|
||||||
|
|
||||||
|
<template v-slot:body-cell="props">
|
||||||
|
<q-td
|
||||||
|
:props="props"
|
||||||
|
:style="user.isPermittedTo('userSettings', 'write') ? 'cursor: pointer' : ''"
|
||||||
|
@click="
|
||||||
|
user.isPermittedTo('userSettings', 'write')
|
||||||
|
? openSingleValueDialog(props.col.label, props.col.name, props.row)
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ props.value }}
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
<template v-slot:body-cell-option="props">
|
||||||
|
<q-td :props="props">
|
||||||
|
<q-btn
|
||||||
|
v-if="user.isPermittedTo('userSettings', 'delete')"
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
icon="delete"
|
||||||
|
color="negative"
|
||||||
|
@click="openRemoveDialog(props.row)"
|
||||||
|
>
|
||||||
|
<q-tooltip> {{ $t('delete') }} </q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
<EditOneDialog
|
||||||
|
ref="editOneDialog"
|
||||||
|
endpoint="workspaces/update"
|
||||||
|
query-id
|
||||||
|
v-on:update="updateWorkspaces"
|
||||||
|
></EditOneDialog>
|
||||||
|
<EditAllDialog ref="editAllDialog" v-on:update="updateWorkspaces"></EditAllDialog>
|
||||||
|
<OkDialog
|
||||||
|
ref="okDialog"
|
||||||
|
:dialog-label="$t('delete')"
|
||||||
|
:text="$t('doYouWantToDelete') + ' ' + deleteText"
|
||||||
|
label-color="red"
|
||||||
|
:button-cancel-label="$t('cancel')"
|
||||||
|
:button-ok-label="$t('confirm')"
|
||||||
|
:button-ok-flat="false"
|
||||||
|
button-ok-color="red"
|
||||||
|
v-on:update-confirm="(val) => removeWorkspace(...val)"
|
||||||
|
></OkDialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { appApi } from 'src/boot/axios';
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import type { Workspaces, Workspace } from 'src/vueLib/models/workspaces';
|
||||||
|
import EditOneDialog from 'src/components/EditOneDialog.vue';
|
||||||
|
import EditAllDialog from 'src/components/WorkspaceEditAllDialog.vue';
|
||||||
|
import OkDialog from 'src/components/dialog/OkDialog.vue';
|
||||||
|
import { useNotify } from 'src/vueLib/general/useNotify';
|
||||||
|
import { useWorkspaceTable } from './WorkspaceTable';
|
||||||
|
import { i18n } from 'src/boot/lang';
|
||||||
|
import { QTable } from 'quasar';
|
||||||
|
import { useUserStore } from 'src/vueLib/login/userStore';
|
||||||
|
import SearchableInput from '../components/SearchableInput.vue';
|
||||||
|
import { type User } from 'src/vueLib/models/user';
|
||||||
|
|
||||||
|
const { NotifyResponse } = useNotify();
|
||||||
|
const editOneDialog = ref();
|
||||||
|
const editAllDialog = ref();
|
||||||
|
const okDialog = ref();
|
||||||
|
const deleteText = ref('');
|
||||||
|
const selectOption = ref(false);
|
||||||
|
const selected = ref<Workspaces>([]);
|
||||||
|
const openSubmenu = ref(false);
|
||||||
|
const currentUser = ref<User>();
|
||||||
|
const filter = ref('');
|
||||||
|
const user = useUserStore();
|
||||||
|
|
||||||
|
const { workspaces, pagination, loading, columns, updateWorkspaces } = useWorkspaceTable();
|
||||||
|
|
||||||
|
//load on mounting page
|
||||||
|
onMounted(() => {
|
||||||
|
loading.value = true;
|
||||||
|
if (user.user) {
|
||||||
|
currentUser.value = user.user;
|
||||||
|
}
|
||||||
|
updateWorkspaces().catch((err) => NotifyResponse(err, 'error'));
|
||||||
|
});
|
||||||
|
|
||||||
|
// opens dialog for all workspace values
|
||||||
|
function openSingleValueDialog(label: string, field: string, workspace: Workspace) {
|
||||||
|
editOneDialog.value?.open(label, field, workspace);
|
||||||
|
}
|
||||||
|
|
||||||
|
//opens dialog for one value
|
||||||
|
function openAllValueDialog(workspace: Workspace | null, typ?: 'permissions') {
|
||||||
|
editAllDialog.value?.open(workspace, typ);
|
||||||
|
}
|
||||||
|
|
||||||
|
//opens remove dialog
|
||||||
|
function openRemoveDialog(...workspaces: Workspaces) {
|
||||||
|
if (workspaces.length === 1) {
|
||||||
|
deleteText.value = "'" + workspaces[0]?.name + "'";
|
||||||
|
} else {
|
||||||
|
deleteText.value = String(workspaces.length) + ' ' + i18n.global.t('workspaces');
|
||||||
|
}
|
||||||
|
okDialog.value?.open(workspaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
//remove workspace from database
|
||||||
|
function removeWorkspace(...removeWorkspaces: Workspaces) {
|
||||||
|
const workspaces: Workspace[] = [];
|
||||||
|
|
||||||
|
const user = useUserStore().user;
|
||||||
|
const usedWorkspaceId = user?.workspaces?.find((w) => w.uuid === user.workspaceId)?.id;
|
||||||
|
|
||||||
|
removeWorkspaces.forEach((workspace: Workspace) => {
|
||||||
|
if (workspace.id === usedWorkspaceId) {
|
||||||
|
NotifyResponse(i18n.global.t('notPossibleToDeleteUsedWorkspace'), 'error');
|
||||||
|
} else if (workspace.id) {
|
||||||
|
workspaces.push(workspace);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
appApi
|
||||||
|
.post('workspaces/delete', {
|
||||||
|
workspaces: workspaces,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
updateWorkspaces().catch((err) => NotifyResponse(err, 'error'));
|
||||||
|
if (workspaces.length === 1) {
|
||||||
|
NotifyResponse("'" + workspaces[0]?.name + "' " + i18n.global.t('deleted'), 'warning');
|
||||||
|
} else {
|
||||||
|
NotifyResponse(i18n.global.t('deleteWorkspaces'), 'warning');
|
||||||
|
}
|
||||||
|
selected.value = [];
|
||||||
|
})
|
||||||
|
.catch((err) => NotifyResponse(err, 'error'))
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false;
|
||||||
|
selectOption.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.bigger-table-text .q-table__middle td {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bigger-table-text .q-table__top,
|
||||||
|
.bigger-table-text .q-table__bottom,
|
||||||
|
.bigger-table-text th {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user