Compare commits
13 Commits
v1.3.2
...
21bbfd617e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21bbfd617e | ||
|
|
d154041df0 | ||
| 41e0b4d060 | |||
| 2f67c02be1 | |||
|
|
15f9026a5f | ||
|
|
e25bac1a1e | ||
|
|
548cd9d622 | ||
|
|
8e3e8f8bc7 | ||
|
|
e686a27bf1 | ||
|
|
ab88acd740 | ||
|
|
6392877dc1 | ||
|
|
b726eb42dc | ||
|
|
8963cba016 |
@@ -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.
|
||||
|
||||
## 🧩 Overview
|
||||
|
||||
**Memer App** provides a unified interface for handling:
|
||||
**Member App** provides a unified interface for handling:
|
||||
- Member registration and profiles
|
||||
- Event creation, attendance tracking, and reporting
|
||||
- Excursion & Impact Team management
|
||||
|
||||
@@ -3,9 +3,9 @@ module backend
|
||||
go 1.25.4
|
||||
|
||||
require (
|
||||
gitea.tecamino.com/paadi/access-handler v1.0.34
|
||||
gitea.tecamino.com/paadi/memberDB v1.1.28
|
||||
gitea.tecamino.com/paadi/tecamino-dbm v0.1.1
|
||||
gitea.tecamino.com/paadi/access-handler v1.0.48
|
||||
gitea.tecamino.com/paadi/memberDB v1.1.30
|
||||
gitea.tecamino.com/paadi/tecamino-dbm v1.0.0
|
||||
gitea.tecamino.com/paadi/tecamino-logger v0.2.1
|
||||
github.com/gin-contrib/cors v1.7.6
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
@@ -14,7 +14,7 @@ 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/loader v0.3.0 // 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.34/go.mod h1:HyMp1WvzmqLw8Ljt3r1qlF8fY+T5WFXr9Da/CTIM0H8=
|
||||
gitea.tecamino.com/paadi/dbHandler v1.1.11 h1:hTpMWRr4dW7TkiBnEku0/3ggDC7/uP82U9paRKY/QEs=
|
||||
gitea.tecamino.com/paadi/dbHandler v1.1.11/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.28/go.mod h1:uLoKel+EcuXUzxAY5ugfWh640TSomfTJR+g8Jfe8YKI=
|
||||
gitea.tecamino.com/paadi/tecamino-dbm v0.1.1 h1:vAq7mwUxlxJuLzCQSDMrZCwo8ky5usWi9Qz+UP+WnkI=
|
||||
gitea.tecamino.com/paadi/tecamino-dbm v0.1.1/go.mod h1:+tmf1rjPaKEoNeUcr1vdtoFIFweNG3aUGevDAl3NMBk=
|
||||
gitea.tecamino.com/paadi/access-handler v1.0.48 h1:PYZvOwR9HCORAFpm7Nd5ZXvWwT5w04OvbcHhVHmPJlw=
|
||||
gitea.tecamino.com/paadi/access-handler v1.0.48/go.mod h1:0kUGU4Jw2jSvopCCwecuX/2QnVKS09Ec1KQNrBXvsFs=
|
||||
gitea.tecamino.com/paadi/dbHandler v1.1.12 h1:F1ARSTUm0MZmF84FfD/g5RQNMYyDYXHYrB3cXPSi4qw=
|
||||
gitea.tecamino.com/paadi/dbHandler v1.1.12/go.mod h1:y/xn/POJg1DO++67uKvnO23lJQgh+XFQq7HZCS9Getw=
|
||||
gitea.tecamino.com/paadi/memberDB v1.1.30 h1:N+3V9A/+OAGIoJeUNVHj1qUuBcy6ADLYFIgCnp2Ggk4=
|
||||
gitea.tecamino.com/paadi/memberDB v1.1.30/go.mod h1:Q4NO1cdBm/6RLF+bP2NEzBPJURKjyIr4u3dElDXmHWI=
|
||||
gitea.tecamino.com/paadi/tecamino-dbm v1.0.0 h1:xFgcpIiQMyqbglScZBAbdOQyM+yOJ3GHMK2iX5Ep3Gg=
|
||||
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/go.mod h1:FkzRTldUBBOd/iy2upycArDftSZ5trbsX5Ira5OzJgM=
|
||||
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||
|
||||
@@ -145,6 +145,7 @@ func main() {
|
||||
|
||||
auth.GET("/users", accessHandler.GetUser)
|
||||
auth.GET("/roles", accessHandler.GetRole)
|
||||
auth.GET("/workspaces", accessHandler.GetWorkspace)
|
||||
|
||||
auth.POST("database/open", dbHandler.OpenDatabase)
|
||||
auth.POST("/members/add", dbHandler.AddNewMember)
|
||||
@@ -177,6 +178,11 @@ func main() {
|
||||
auth.POST("/users/new/password", accessHandler.ChangePassword)
|
||||
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)
|
||||
|
||||
// Serve static files
|
||||
@@ -228,4 +234,8 @@ func main() {
|
||||
if err := s.ServeHttp(env.HostUrl.GetValue(), env.HostPort.GetUIntValue()); err != nil {
|
||||
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",
|
||||
"version": "1.3.2",
|
||||
"version": "1.4.0",
|
||||
"description": "A Tecamino App",
|
||||
"productName": "Attendence Records",
|
||||
"author": "A. Zuercher",
|
||||
|
||||
@@ -197,3 +197,11 @@ calendar:
|
||||
firstDayOfWeek: 1
|
||||
format24h: true
|
||||
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
|
||||
selectRoleOptions: Wähle Rollen Optionen
|
||||
selectEventOptions: Wähle Veranstaltungs Optionen
|
||||
addNewRole: Füge neue Rolle hinzu
|
||||
addNewEvent: Füeg neue Veranstaltung hinzu
|
||||
addNewRole: Neue Rolle hinzufügen
|
||||
addNewEvent: Neue Veranstaltung hinzufügen
|
||||
veryWeak: sehr Schwach
|
||||
weak: Schwach
|
||||
fair: Ausreichend
|
||||
@@ -197,3 +197,11 @@ calendar:
|
||||
firstDayOfWeek: 1
|
||||
format24h: true
|
||||
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
|
||||
format24h: false
|
||||
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
|
||||
user: Usuario
|
||||
password: Contraseña
|
||||
isRequired: Es obligatorio
|
||||
isRequired: Obligatorio
|
||||
colors: Colores
|
||||
primaryColor: Color principal
|
||||
primaryColorText: Color del texto principal
|
||||
@@ -108,7 +108,7 @@ attendeeAdded: Asistente añadido
|
||||
attendeesAdded: Asistentes añadidos
|
||||
eventAdded: Evento añadido
|
||||
userUpdated: Usuario actualizado
|
||||
selectResponsibleOptions: Seleccionar opciones responsables
|
||||
selectResponsibleOptions: Seleccionar opciones de responsables
|
||||
addNewResponsible: Añadir responsable
|
||||
responsibleAdded: Responsable añadido
|
||||
responsiblesAdded: Responsables añadidos
|
||||
@@ -197,3 +197,11 @@ calendar:
|
||||
firstDayOfWeek: 1
|
||||
format24h: true
|
||||
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
|
||||
|
||||
@@ -17,7 +17,7 @@ export default boot(async ({ app }) => {
|
||||
useStore
|
||||
.setUser({
|
||||
id: resp.data.id,
|
||||
username: resp.data.username,
|
||||
user: resp.data.username,
|
||||
role: { role: resp.data.role, permissions: [] },
|
||||
})
|
||||
.catch((err) => console.error(err));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { boot } from 'quasar/wrappers';
|
||||
import { setQuasarInstance } from 'src/vueLib/utils/globalQ';
|
||||
import { setRouterInstance } from 'src/vueLib/utils/globalRouter';
|
||||
import { databaseName, logo, appName } from 'src/vueLib/models/settings';
|
||||
import { databaseName, logo, appName, workspace } from 'src/vueLib/models/settings';
|
||||
import { Dark } from 'quasar';
|
||||
import { getLocalDarkMode, getLocalSettings } from 'src/localstorage/localStorage';
|
||||
|
||||
@@ -20,7 +20,8 @@ export default boot(({ app, router }) => {
|
||||
if (settings.appName) {
|
||||
appName.value = settings.appName;
|
||||
}
|
||||
databaseName.value = settings.databaseName ?? databaseName.value;
|
||||
databaseName.value = settings.databaseName;
|
||||
workspace.value = settings.workspace?.uuid ?? '';
|
||||
|
||||
document.documentElement.style.setProperty('--q-primary', settings.primaryColor ?? '#1976d2');
|
||||
document.documentElement.style.setProperty(
|
||||
|
||||
@@ -74,7 +74,9 @@ function open(title: string, members: Members) {
|
||||
appApi
|
||||
.get('events')
|
||||
.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) => {
|
||||
NotifyResponse(err, 'error');
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
class="col-5 required"
|
||||
:label="$t('role')"
|
||||
filled
|
||||
option-label="role"
|
||||
option-value="role"
|
||||
emit-value
|
||||
:options="props.roles"
|
||||
:rules="[(val) => !!val || $t('roleIsRequired')]"
|
||||
v-model="role"
|
||||
@@ -66,6 +69,7 @@ const dialog = ref();
|
||||
const form = ref();
|
||||
const newUser = ref(false);
|
||||
const role = ref('');
|
||||
|
||||
const localUser = ref<User>({
|
||||
user: '',
|
||||
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,12 @@
|
||||
import { Dark } from 'quasar';
|
||||
import { appName, databaseName, type Settings } from 'src/vueLib/models/settings';
|
||||
import { appName, databaseName, workspace, type Settings } from 'src/vueLib/models/settings';
|
||||
import { ref } from 'vue';
|
||||
|
||||
export function setLocalSettings(settings: Settings) {
|
||||
localStorage.setItem('icon', settings.icon);
|
||||
localStorage.setItem('appName', settings.appName);
|
||||
localStorage.setItem('databaseName', settings.databaseName);
|
||||
localStorage.setItem('workspace', settings.workspace?.uuid || '');
|
||||
localStorage.setItem('primaryColor', settings.primaryColor);
|
||||
localStorage.setItem('primaryColorText', settings.primaryColorText);
|
||||
localStorage.setItem('secondaryColor', settings.secondaryColor);
|
||||
@@ -28,9 +29,15 @@ export function getLocalSettings(): Settings {
|
||||
iconName = '';
|
||||
}
|
||||
|
||||
let ws = localStorage.getItem('workspace');
|
||||
if (ws === undefined || ws === 'undefined') {
|
||||
ws = workspace.value || '';
|
||||
}
|
||||
|
||||
return <Settings>{
|
||||
icon: iconName,
|
||||
appName: name,
|
||||
workspace: { name: '', description: '', uuid: ws },
|
||||
databaseName: db,
|
||||
primaryColor: localStorage.getItem('primaryColor'),
|
||||
primaryColorText: localStorage.getItem('primaryColorText'),
|
||||
@@ -42,6 +49,7 @@ export function getLocalSettings(): Settings {
|
||||
export function clearLocalStorage() {
|
||||
localStorage.removeItem('icon');
|
||||
localStorage.removeItem('appName');
|
||||
localStorage.removeItem('workspace');
|
||||
localStorage.removeItem('databaseName');
|
||||
localStorage.removeItem('primaryColor');
|
||||
localStorage.removeItem('primaryColorText');
|
||||
|
||||
@@ -15,7 +15,7 @@ const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
|
||||
onMounted(() => {
|
||||
if (userStore.user?.username !== '' && userStore.user?.role.role !== '') {
|
||||
if (userStore.user?.user !== '' && userStore.user?.role.role !== '') {
|
||||
forwardToPage().catch((err) => console.error(err));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -82,8 +82,12 @@
|
||||
v-if="attendees !== undefined"
|
||||
:class="
|
||||
nonAttendees !== undefined
|
||||
? 'col-12 col-sm-5 col-md-5 q-pa-md'
|
||||
: 'col-12 col-md-8 col-lg-5'
|
||||
? 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
|
||||
@@ -103,7 +107,13 @@
|
||||
<div
|
||||
v-if="nonAttendees !== undefined"
|
||||
:class="
|
||||
attendees !== undefined ? 'col-12 col-sm-5 col-md-5 q-pa-md' : 'col-12 col-md-8 col-lg-5'
|
||||
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
|
||||
@@ -130,13 +140,14 @@ import DateDaySelect from 'src/components/DateDaySelect.vue';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useNotify } from 'src/vueLib/general/useNotify';
|
||||
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 ReportStat from 'src/components/ReportStat.vue';
|
||||
import type { Group, Groups } from 'src/vueLib/models/group';
|
||||
import { getLocalPageDefaults, setLocalPageDefaults } from 'src/localstorage/localStorage';
|
||||
import html2pdf from 'html2pdf.js';
|
||||
import type { PageDefault } from 'src/vueLib/models/pageDefaults';
|
||||
import { openDatabase } from 'src/vueLib/components/DatabaseCall';
|
||||
|
||||
const filter = ref<string>('');
|
||||
const group = ref<Group[]>([]);
|
||||
@@ -150,6 +161,7 @@ const loading = ref(false);
|
||||
const amounts = ref<Amount[]>([]);
|
||||
const reportExportRef = ref<HTMLElement | null>(null);
|
||||
const weekdays = ref<number[]>([0, 3]);
|
||||
const printing = ref<boolean>(false);
|
||||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
@@ -168,10 +180,9 @@ const columns = computed(() => [
|
||||
},
|
||||
]);
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
loading.value = true;
|
||||
appApi
|
||||
.post('database/open', { dbPath: databaseName.value })
|
||||
await openDatabase()
|
||||
.catch((err) => NotifyResponse(err, 'error'))
|
||||
|
||||
.finally(() => {
|
||||
@@ -268,10 +279,14 @@ function updateReport(dates: string[]) {
|
||||
}
|
||||
|
||||
function printReport() {
|
||||
printing.value = true;
|
||||
window.print();
|
||||
printing.value = false;
|
||||
}
|
||||
|
||||
async function downloadPDF() {
|
||||
printing.value = true;
|
||||
|
||||
const element = reportExportRef.value;
|
||||
if (!element) return;
|
||||
// Generate date string (YYYY-MM-DD)
|
||||
@@ -308,7 +323,8 @@ async function downloadPDF() {
|
||||
.save()
|
||||
.catch((error) => {
|
||||
console.error('PDF Generation failed:', error);
|
||||
});
|
||||
})
|
||||
.finally(() => (printing.value = false));
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -26,15 +26,57 @@
|
||||
</q-card>
|
||||
<q-card class="q-ma-lg">
|
||||
<p class="text-bold text-h6 text-primary q-pa-md">{{ $t('database') }}</p>
|
||||
<div class="row">
|
||||
<q-input
|
||||
<div
|
||||
v-if="localUser?.workspaces !== undefined && localUser?.workspaces.length > 0"
|
||||
class="row"
|
||||
>
|
||||
<q-select
|
||||
dense
|
||||
: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="settings.workspace"
|
||||
@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
|
||||
:label="$t('databaseName')"
|
||||
:options="databases"
|
||||
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>
|
||||
</q-card>
|
||||
<q-card class="q-ma-lg">
|
||||
@@ -129,32 +171,81 @@
|
||||
</div>
|
||||
</q-card>
|
||||
</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>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { logo, appName, databaseName } from 'src/vueLib/models/settings';
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import { logo, appName, databaseName, workspace } from 'src/vueLib/models/settings';
|
||||
import { onMounted, reactive, ref, watch } from 'vue';
|
||||
import { appApi } from 'src/boot/axios';
|
||||
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 { setLocalSettings } from 'src/localstorage/localStorage';
|
||||
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';
|
||||
|
||||
const { NotifyResponse } = useNotify();
|
||||
const colorGroup = ref(false);
|
||||
const user = useUserStore();
|
||||
|
||||
const addDatabaseRef = ref();
|
||||
const newDatabase = ref<string>('');
|
||||
const foundDatabases = ref<number>(0);
|
||||
const localUser = ref<User>();
|
||||
const databases = ref<string[]>([]);
|
||||
const settings = reactive<Settings>({
|
||||
appName: appName.value,
|
||||
icon: logo.value,
|
||||
databaseName: databaseName.value,
|
||||
workspace: { name: '', description: '', uuid: workspace.value },
|
||||
primaryColor: document.documentElement.style.getPropertyValue('--q-primary'),
|
||||
primaryColorText: document.documentElement.style.getPropertyValue('--q-primary-text'),
|
||||
secondaryColor: document.documentElement.style.getPropertyValue('--q-secondary'),
|
||||
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];
|
||||
settings.workspace = localUser.value?.settings?.workspace || null;
|
||||
|
||||
if (settings.workspace) {
|
||||
appApi
|
||||
.post('workspaces/data', settings.workspace)
|
||||
.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'));
|
||||
} else {
|
||||
settings.databaseName = '';
|
||||
}
|
||||
})
|
||||
.catch((err) => NotifyResponse(err, 'error'));
|
||||
});
|
||||
|
||||
watch(settings, (newSettings) => {
|
||||
logo.value = newSettings.icon;
|
||||
appName.value = newSettings.appName;
|
||||
@@ -172,6 +263,40 @@ function resetColors() {
|
||||
settings.secondaryColorText = '#ffffff';
|
||||
}
|
||||
|
||||
function changeWorkspace() {
|
||||
appApi
|
||||
.post('workspaces/data', settings.workspace)
|
||||
.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 (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();
|
||||
}
|
||||
|
||||
function save() {
|
||||
document.documentElement.style.setProperty('--q-primary', settings.primaryColor);
|
||||
document.documentElement.style.setProperty('--q-primary-text', settings.primaryColorText);
|
||||
@@ -179,16 +304,25 @@ function save() {
|
||||
document.documentElement.style.setProperty('--q-secondary-text', settings.secondaryColorText);
|
||||
appName.value = settings.appName;
|
||||
logo.value = settings.icon;
|
||||
if (localUser.value?.settings) {
|
||||
localUser.value.settings = settings;
|
||||
}
|
||||
|
||||
setLocalSettings(settings);
|
||||
|
||||
const tempuser = user.user;
|
||||
|
||||
if (tempuser) {
|
||||
tempuser.settings = settings;
|
||||
}
|
||||
appApi
|
||||
.post('users/update', tempuser)
|
||||
.then((resp) => NotifyResponse(resp.data.message))
|
||||
.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'));
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -52,7 +52,7 @@ import { appApi } from 'src/boot/axios';
|
||||
import { i18n } from 'src/boot/lang';
|
||||
import SiteTitle from 'src/vueLib/general/SiteTitle.vue';
|
||||
import { useNotify } from 'src/vueLib/general/useNotify';
|
||||
import { databaseName } from 'src/vueLib/models/settings';
|
||||
import { databaseName, workspace } from 'src/vueLib/models/settings';
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
const stats = ref();
|
||||
@@ -71,8 +71,10 @@ const amounts = ref<{
|
||||
const { NotifyResponse } = useNotify();
|
||||
|
||||
onMounted(async () => {
|
||||
let path = databaseName.value;
|
||||
if (workspace.value !== '') path = workspace.value + '/' + path;
|
||||
stats.value = await appApi
|
||||
.post('/stats', { database: databaseName.value })
|
||||
.post('/stats', { database: path })
|
||||
.then((resp) => {
|
||||
if ((resp.data.databaseSize as number) >= 1000000000) {
|
||||
return (resp.data.data.databaseSize / 1000000000).toFixed(2) + ' GB';
|
||||
|
||||
@@ -11,8 +11,15 @@
|
||||
align="justify"
|
||||
narrow-indicator
|
||||
>
|
||||
<q-tab name="users" no-caps :label="$t('users')" />
|
||||
<q-tab name="roles" no-caps :label="$t('roles')" />
|
||||
<q-tab name="users" icon="people" no-caps :label="$t('users')" />
|
||||
<q-tab name="roles" icon="rule" no-caps :label="$t('roles')" />
|
||||
<q-tab
|
||||
v-if="user?.user?.role.role.includes('admin')"
|
||||
name="workspaces"
|
||||
icon="workspaces"
|
||||
no-caps
|
||||
:label="$t('workspaces')"
|
||||
/>
|
||||
</q-tabs>
|
||||
<q-separator />
|
||||
|
||||
@@ -23,16 +30,24 @@
|
||||
<q-tab-panel name="roles" style="padding: 0px">
|
||||
<RoleTable />
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="workspaces" style="padding: 0px">
|
||||
<WorkspaceTable />
|
||||
</q-tab-panel>
|
||||
</q-tab-panels>
|
||||
</q-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import UserTable from 'src/vueLib/tables/users/UserTable.vue';
|
||||
import RoleTable from 'src/vueLib/tables/roles/RoleTable.vue';
|
||||
import SiteTitle from 'src/vueLib/general/SiteTitle.vue';
|
||||
import { useUserStore } from 'src/vueLib/login/userStore';
|
||||
import type { UserState } from 'src/vueLib/models/user';
|
||||
import WorkspaceTable from 'src/vueLib/tables/workspaces/WorkspaceTable.vue';
|
||||
|
||||
const tab = ref('users');
|
||||
const user = ref<UserState>();
|
||||
onMounted(() => (user.value = useUserStore()));
|
||||
</script>
|
||||
|
||||
14
src/vueLib/components/DatabaseCall.ts
Normal file
14
src/vueLib/components/DatabaseCall.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { appApi } from 'src/boot/axios';
|
||||
import { databaseName, workspace } from '../models/settings';
|
||||
|
||||
export async function openDatabase() {
|
||||
let path = databaseName.value;
|
||||
if (workspace.value !== '') {
|
||||
path = workspace.value + '/' + path;
|
||||
}
|
||||
|
||||
return appApi.post('database/open', {
|
||||
dbPath: path,
|
||||
create: true,
|
||||
});
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
<q-btn dense flat round icon="person" :color="currentUser ? 'green' : ''">
|
||||
<q-menu ref="refLoginMenu">
|
||||
<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-section class="text-primary">{{ loginText }}</q-item-section>
|
||||
</q-item>
|
||||
@@ -69,7 +69,7 @@ const darkMode = computed(() => {
|
||||
return 'dark_mode';
|
||||
});
|
||||
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']));
|
||||
|
||||
@@ -35,7 +35,7 @@ export function useLogin() {
|
||||
await userStore
|
||||
.setUser({
|
||||
id: resp.data.id,
|
||||
username: resp.data.user,
|
||||
user: resp.data.user,
|
||||
role: { role: resp.data.role, permissions: [] },
|
||||
})
|
||||
.catch((err) => NotifyResponse(err, 'error'));
|
||||
@@ -69,7 +69,7 @@ export function useLogin() {
|
||||
userStore
|
||||
.setUser({
|
||||
id: resp.data.id,
|
||||
username: resp.data.user,
|
||||
user: resp.data.user,
|
||||
role: { role: resp.data.role, permissions: [] },
|
||||
})
|
||||
.catch((err) => NotifyResponse(err, 'error'));
|
||||
|
||||
@@ -25,8 +25,17 @@ export const useUserStore = defineStore('user', {
|
||||
};
|
||||
},
|
||||
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);
|
||||
if (compareRole && permission) {
|
||||
const rolePermission = compareRole.permissions?.find((r: Permission) => r.name === name);
|
||||
if (rolePermission && rolePermission?.permission > permission?.permission) return false;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'read':
|
||||
return permission?.permission ? (permission.permission & (1 << 0)) === 1 : false;
|
||||
@@ -62,7 +71,7 @@ export const useUserStore = defineStore('user', {
|
||||
if (!this.user) return;
|
||||
if ($q) {
|
||||
$q?.notify({
|
||||
message: "user '" + this.user?.username + "' logged out",
|
||||
message: "user '" + this.user?.user + "' logged out",
|
||||
color: 'orange',
|
||||
position: 'top',
|
||||
icon: 'warning',
|
||||
@@ -80,7 +89,7 @@ export const useUserStore = defineStore('user', {
|
||||
],
|
||||
});
|
||||
} else {
|
||||
console.error("user '" + this.user?.username + "' logged out");
|
||||
console.error("user '" + this.user?.user + "' logged out");
|
||||
}
|
||||
|
||||
this.user = null;
|
||||
@@ -108,9 +117,12 @@ export const useUserStore = defineStore('user', {
|
||||
],
|
||||
});
|
||||
} 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,16 @@
|
||||
import { ref } from 'vue';
|
||||
import type { Workspace } from './workspaces';
|
||||
|
||||
export const logo = ref('');
|
||||
export const appName = ref('Attendance Records');
|
||||
export const databaseName = ref('members.dba');
|
||||
export const logo = ref<string>('');
|
||||
export const appName = ref<string>('Attendance Records');
|
||||
export const databaseName = ref<string>('members.dba');
|
||||
export const workspace = ref<string>('');
|
||||
|
||||
export type Settings = {
|
||||
appName: string;
|
||||
icon: string;
|
||||
databaseName: string;
|
||||
workspace: Workspace | null;
|
||||
primaryColor: string;
|
||||
primaryColorText: string;
|
||||
secondaryColor: string;
|
||||
@@ -19,6 +22,7 @@ export function DefaultSettings(): Settings {
|
||||
appName: 'Attendance Records',
|
||||
icon: '',
|
||||
databaseName: 'members.dba',
|
||||
workspace: { name: '', description: '', uuid: workspace.value },
|
||||
primaryColor: document.documentElement.style.getPropertyValue('--q-primary-text'),
|
||||
primaryColorText: document.documentElement.style.getPropertyValue('--q-primary'),
|
||||
secondaryColor: document.documentElement.style.getPropertyValue('--q-secondary'),
|
||||
|
||||
@@ -4,11 +4,12 @@ import type { Settings } from './settings';
|
||||
|
||||
export interface User {
|
||||
id: number;
|
||||
username: string;
|
||||
user: string;
|
||||
role: Role;
|
||||
permissions?: Permissions;
|
||||
settings?: Settings;
|
||||
newDatabase?: boolean;
|
||||
workspaces?: string[];
|
||||
}
|
||||
|
||||
export interface UserState {
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import type { Role } from './roles';
|
||||
import type { Settings } from './settings';
|
||||
import type { Workspace } from './workspaces';
|
||||
|
||||
export interface User {
|
||||
id?: number;
|
||||
user: string;
|
||||
email: string;
|
||||
role?: Role;
|
||||
roleId?: number;
|
||||
expiration?: string;
|
||||
password?: string;
|
||||
newPassword?: string;
|
||||
settings?: Settings;
|
||||
workspaces?: Workspace[];
|
||||
}
|
||||
|
||||
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[];
|
||||
@@ -123,13 +123,13 @@ import EditAllDialog from 'src/components/EventEditAllDialog.vue';
|
||||
import OkDialog from 'src/components/dialog/OkDialog.vue';
|
||||
import { useNotify } from 'src/vueLib/general/useNotify';
|
||||
import { useEventTable } from './EventsTable';
|
||||
import { databaseName } from 'src/vueLib/models/settings';
|
||||
import { useUserStore } from 'src/vueLib/login/userStore';
|
||||
import AttendeesTableDialog from '../attendees/AttendeesTableDialog.vue';
|
||||
import type { Members } from 'src/vueLib/models/member';
|
||||
import { i18n } from 'src/boot/lang';
|
||||
import SearchableInput from '../components/SearchableInput.vue';
|
||||
import TopButtonGroup from '../components/TopButtonGroup.vue';
|
||||
import { openDatabase } from 'src/vueLib/components/DatabaseCall';
|
||||
|
||||
export interface EventDialog {
|
||||
getSelected: () => Events;
|
||||
@@ -150,11 +150,10 @@ const user = useUserStore();
|
||||
const { Events, pagination, loading, columns, updateEvents } = useEventTable();
|
||||
|
||||
//load on mounting page
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
loading.value = true;
|
||||
|
||||
appApi
|
||||
.post('database/open', { dbPath: databaseName.value, create: true })
|
||||
await openDatabase()
|
||||
.then(() => {
|
||||
updateEvents();
|
||||
})
|
||||
|
||||
@@ -116,7 +116,6 @@ import DialogFrame from 'src/vueLib/dialog/DialogFrame.vue';
|
||||
import OkDialog from 'src/components/dialog/OkDialog.vue';
|
||||
import { useNotify } from 'src/vueLib/general/useNotify';
|
||||
import { useGroupTable } from './GroupTable';
|
||||
import { databaseName } from 'src/vueLib/models/settings';
|
||||
import { useUserStore } from 'src/vueLib/login/userStore';
|
||||
import { i18n } from 'src/boot/lang';
|
||||
import type { Group, Groups } from 'src/vueLib/models/group';
|
||||
@@ -124,6 +123,7 @@ import SearchableInput from '../components/SearchableInput.vue';
|
||||
import TopButtonGroup from '../components/TopButtonGroup.vue';
|
||||
import { getAllMembers } from '../members/MembersTable';
|
||||
import type { Members } from 'src/vueLib/models/member';
|
||||
import { openDatabase } from 'src/vueLib/components/DatabaseCall';
|
||||
|
||||
const { NotifyResponse } = useNotify();
|
||||
const groupDialog = ref();
|
||||
@@ -147,8 +147,7 @@ onMounted(async () => {
|
||||
|
||||
members.value = await getAllMembers();
|
||||
|
||||
appApi
|
||||
.post('database/open', { dbPath: databaseName.value, create: true })
|
||||
await openDatabase()
|
||||
.then(() => {
|
||||
updateGroups().catch((err) => {
|
||||
NotifyResponse(err, 'error');
|
||||
|
||||
@@ -206,7 +206,6 @@ import { useNotify } from 'src/vueLib/general/useNotify';
|
||||
import { useMemberTable } from './MembersTable';
|
||||
import UploadDialog from 'src/components/UploadDialog.vue';
|
||||
import AddToEvent from 'src/components/AddToEvent.vue';
|
||||
import { databaseName } from 'src/vueLib/models/settings';
|
||||
import { useUserStore } from 'src/vueLib/login/userStore';
|
||||
import { i18n } from 'src/boot/lang';
|
||||
import type { Responsible } from 'src/vueLib/models/responsible';
|
||||
@@ -216,6 +215,7 @@ import FilterSelect from '../components/FilterSelect.vue';
|
||||
import TopButtonGroup from '../components/TopButtonGroup.vue';
|
||||
import { getLocalPageDefaults, setLocalPageDefaults } from 'src/localstorage/localStorage';
|
||||
import type { PageDefault } from 'src/vueLib/models/pageDefaults';
|
||||
import { openDatabase } from 'src/vueLib/components/DatabaseCall';
|
||||
|
||||
const inProps = defineProps({
|
||||
addAttendees: { type: Boolean },
|
||||
@@ -263,7 +263,7 @@ const {
|
||||
} = useMemberTable();
|
||||
|
||||
//load on mounting page
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
page.value = 'members';
|
||||
if (inProps.addAttendees || inProps.addResponsible) {
|
||||
selectOption.value = true;
|
||||
@@ -295,8 +295,7 @@ onMounted(() => {
|
||||
// set custom filter
|
||||
setNewFilter(selectedColumnFilter.value, ...selectedColumnOptions.value);
|
||||
|
||||
appApi
|
||||
.post('database/open', { dbPath: databaseName.value, create: true })
|
||||
await openDatabase()
|
||||
.then(() => {
|
||||
updateTable().catch((err) => NotifyResponse(err, 'error'));
|
||||
})
|
||||
|
||||
@@ -108,12 +108,12 @@ import type { Members } from 'src/vueLib/models/member';
|
||||
import OkDialog from 'src/components/dialog/OkDialog.vue';
|
||||
import { useNotify } from 'src/vueLib/general/useNotify';
|
||||
import { useResponsibleTable } from './ResponsibleTable';
|
||||
import { databaseName } from 'src/vueLib/models/settings';
|
||||
import { useUserStore } from 'src/vueLib/login/userStore';
|
||||
import { i18n } from 'src/boot/lang';
|
||||
import type { Responsible, Responsibles } from 'src/vueLib/models/responsible';
|
||||
import SearchableInput from '../components/SearchableInput.vue';
|
||||
import TopButtonGroup from '../components/TopButtonGroup.vue';
|
||||
import { openDatabase } from 'src/vueLib/components/DatabaseCall';
|
||||
|
||||
const { NotifyResponse } = useNotify();
|
||||
const responsibleDialog = ref();
|
||||
@@ -129,11 +129,10 @@ const { responsibleMember, pagination, loading, columns, updateResponsibles } =
|
||||
useResponsibleTable();
|
||||
|
||||
//load on mounting page
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
loading.value = true;
|
||||
|
||||
appApi
|
||||
.post('database/open', { dbPath: databaseName.value, create: true })
|
||||
await openDatabase()
|
||||
.then(() => {
|
||||
updateResponsibles().catch((err) => {
|
||||
NotifyResponse(err, 'error');
|
||||
|
||||
@@ -68,7 +68,11 @@ export function useRoleTable() {
|
||||
.get('/login/me')
|
||||
.then((resp) => {
|
||||
userStore
|
||||
.setUser({ id: resp.data.id, username: resp.data.username, role: resp.data.role })
|
||||
.setUser({
|
||||
id: resp.data.id,
|
||||
user: resp.data.user,
|
||||
role: { role: resp.data.role, permissions: [] },
|
||||
})
|
||||
.catch((err) => NotifyResponse(err, 'error'));
|
||||
login.refresh().catch((err) => NotifyResponse(err, 'error'));
|
||||
})
|
||||
|
||||
@@ -23,17 +23,11 @@
|
||||
>
|
||||
<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-btn v-if="writePermisssion" dense flat icon="add" @click="openAllValueDialog(null)">
|
||||
<q-tooltip>{{ $t('addNewRole') }}</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
v-if="user.isPermittedTo('userSettings', 'write')"
|
||||
v-if="writePermisssion"
|
||||
dense
|
||||
flat
|
||||
style="color: grey"
|
||||
@@ -72,13 +66,9 @@
|
||||
<q-td
|
||||
:props="props"
|
||||
:disable="!autorized(props.row)"
|
||||
:style="
|
||||
autorized(props.row) && user.isPermittedTo('userSettings', 'write')
|
||||
? 'cursor: pointer'
|
||||
: ''
|
||||
"
|
||||
:style="autorized(props.row) && writePermisssion ? 'cursor: pointer' : ''"
|
||||
@click="
|
||||
autorized(props.row) && user.isPermittedTo('userSettings', 'write')
|
||||
autorized(props.row) && writePermisssion
|
||||
? openSingleValueDialog(props.col.label, props.col.name, props.row)
|
||||
: ''
|
||||
"
|
||||
@@ -89,19 +79,18 @@
|
||||
<template v-slot:body-cell-permissions="props">
|
||||
<q-td :props="props">
|
||||
<q-btn
|
||||
:disable="!autorized(props.row) || !user.isPermittedTo('userSettings', 'write')"
|
||||
:disable="
|
||||
!autorized(props.row) || !writePermisssion || user.user?.role.role === props.row.role
|
||||
"
|
||||
flat
|
||||
dense
|
||||
icon="rule"
|
||||
:color="
|
||||
autorized(props.row) && user.isPermittedTo('userSettings', 'write')
|
||||
autorized(props.row) && writePermisssion && user.user?.role.role !== props.row.role
|
||||
? 'secondary'
|
||||
: 'grey'
|
||||
"
|
||||
@click="
|
||||
user.isPermittedTo('userSettings', 'write') &&
|
||||
openAllValueDialog(props.row, 'permissions')
|
||||
"
|
||||
@click="writePermisssion && openAllValueDialog(props.row, 'permissions')"
|
||||
>
|
||||
<q-tooltip> {{ $t('permissions') }} </q-tooltip>
|
||||
</q-btn>
|
||||
@@ -145,7 +134,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
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 EditOneDialog from 'src/components/EditOneDialog.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';
|
||||
|
||||
const { NotifyResponse } = useNotify();
|
||||
const { roles, pagination, loading, columns, updateRoles } = useRoleTable();
|
||||
|
||||
const editOneDialog = ref();
|
||||
const editAllDialog = ref();
|
||||
const okDialog = ref();
|
||||
@@ -169,7 +160,7 @@ const currentUser = ref();
|
||||
const filter = ref('');
|
||||
const user = useUserStore();
|
||||
|
||||
const { roles, pagination, loading, columns, updateRoles } = useRoleTable();
|
||||
const writePermisssion = computed(() => user.isPermittedTo('userSettings', 'write'));
|
||||
|
||||
//load on mounting page
|
||||
onMounted(() => {
|
||||
@@ -234,10 +225,6 @@ function removeRole(...removeRoles: Roles) {
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.blink-yellow {
|
||||
animation: blink-yellow 1.5s step-start 6 !important;
|
||||
}
|
||||
|
||||
.bigger-table-text .q-table__middle td {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,14 @@ export function useUserTable() {
|
||||
sortable: true,
|
||||
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',
|
||||
align: 'left' as const,
|
||||
@@ -59,9 +67,9 @@ export function useUserTable() {
|
||||
const loading = ref(false);
|
||||
|
||||
//updates user list from database
|
||||
function updateUsers() {
|
||||
async function updateUsers() {
|
||||
loading.value = true;
|
||||
appApi
|
||||
await appApi
|
||||
.get('users')
|
||||
.then((resp) => {
|
||||
if (resp.data === null) {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
:loading-label="$t('loading')"
|
||||
:rows-per-page-label="$t('recordsPerPage')"
|
||||
:selected-rows-label="(val) => val + ' ' + $t('recordSelected')"
|
||||
:rows="users"
|
||||
:rows="localUsers"
|
||||
:columns="columns"
|
||||
row-key="id"
|
||||
v-model:pagination="pagination"
|
||||
@@ -23,17 +23,11 @@
|
||||
>
|
||||
<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-btn v-if="writePermission" dense flat icon="add" @click="openAllValueDialog(null)">
|
||||
<q-tooltip>{{ $t('addNewUser') }}</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
v-if="user.isPermittedTo('userSettings', 'write')"
|
||||
v-if="writePermission"
|
||||
dense
|
||||
flat
|
||||
style="color: grey"
|
||||
@@ -43,7 +37,9 @@
|
||||
<q-tooltip>{{ $t('selectUserOptions') }}</q-tooltip>
|
||||
</q-btn>
|
||||
</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-menu v-if="openSubmenu" anchor="bottom middle" self="top middle">
|
||||
<q-item
|
||||
@@ -71,13 +67,9 @@
|
||||
<template v-slot:body-cell="props">
|
||||
<q-td
|
||||
:props="props"
|
||||
:style="
|
||||
autorized(props.row) && user.isPermittedTo('userSettings', 'write')
|
||||
? 'cursor: pointer'
|
||||
: ''
|
||||
"
|
||||
:style="autorized(props.row) && writePermission ? 'cursor: pointer' : ''"
|
||||
@click="
|
||||
autorized(props.row) && user.isPermittedTo('userSettings', 'write')
|
||||
autorized(props.row) && writePermission
|
||||
? openSingleValueDialog(props.col.label, props.col.name, props.row)
|
||||
: ''
|
||||
"
|
||||
@@ -101,7 +93,11 @@
|
||||
<template v-slot:body-cell-role="props">
|
||||
<q-td :props="props">
|
||||
<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
|
||||
v-model="props.row.role"
|
||||
:options="localRoles"
|
||||
@@ -110,16 +106,25 @@
|
||||
></q-select>
|
||||
</q-td>
|
||||
</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">
|
||||
<q-td
|
||||
:props="props"
|
||||
:style="
|
||||
autorized(props.row) && user.isPermittedTo('userSettings', 'write')
|
||||
? 'cursor: pointer'
|
||||
: ''
|
||||
"
|
||||
:style="autorized(props.row) && writePermission ? 'cursor: pointer' : ''"
|
||||
@click="
|
||||
autorized(props.row) && user.isPermittedTo('userSettings', 'write')
|
||||
autorized(props.row) && writePermission
|
||||
? openSingleValueDialog(props.col.label, props.col.name, props.row)
|
||||
: ''
|
||||
"
|
||||
@@ -177,31 +182,73 @@ import { i18n } from 'src/boot/lang';
|
||||
import { useUserStore } from 'src/vueLib/login/userStore';
|
||||
import ChangePassword from 'src/vueLib/login/ChangePassword.vue';
|
||||
import SearchableInput from '../components/SearchableInput.vue';
|
||||
import { useWorkspaceTable } from '../workspaces/WorkspaceTable';
|
||||
|
||||
const { NotifyResponse } = useNotify();
|
||||
const { users, pagination, loading, columns, updateUsers } = useUserTable();
|
||||
const { updateRoles } = useRoleTable();
|
||||
const { updateWorkspaces } = useWorkspaceTable();
|
||||
const user = useUserStore();
|
||||
|
||||
const editOneDialog = ref();
|
||||
const editAllDialog = ref();
|
||||
const okDialog = ref();
|
||||
const deleteText = ref('');
|
||||
const localRoles = computed(() => {
|
||||
return roles.value.map((role) => role.role);
|
||||
|
||||
const localUsers = computed(() => {
|
||||
return users.value.filter((u) => {
|
||||
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 selected = ref<Users>([]);
|
||||
const openSubmenu = ref(false);
|
||||
const filter = ref('');
|
||||
const currentUser = ref();
|
||||
const { users, pagination, loading, columns, updateUsers } = useUserTable();
|
||||
const { updateRoles } = useRoleTable();
|
||||
const user = useUserStore();
|
||||
const changePwdDialog = ref();
|
||||
|
||||
const writePermission = computed(() => user.isPermittedTo('userSettings', 'write'));
|
||||
|
||||
//load on mounting page
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
loading.value = true;
|
||||
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
|
||||
@@ -261,7 +308,7 @@ function removeUser(...removeUsers: Users) {
|
||||
appApi
|
||||
.post('users/delete?id=' + currentUser.value.id, { ids: userIds })
|
||||
.then(() => {
|
||||
updateUsers();
|
||||
updateUsers().catch((err) => NotifyResponse(err, 'error'));
|
||||
selected.value = [];
|
||||
})
|
||||
.catch((err) => NotifyResponse(err, 'error'))
|
||||
@@ -272,6 +319,10 @@ function removeUser(...removeUsers: Users) {
|
||||
|
||||
// update role select
|
||||
function updateUser(user: User) {
|
||||
if (user.role?.id) {
|
||||
user.roleId = user.role?.id;
|
||||
}
|
||||
|
||||
appApi
|
||||
.post('/users/update', user)
|
||||
.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,
|
||||
};
|
||||
}
|
||||
226
src/vueLib/tables/workspaces/WorkspaceTable.vue
Normal file
226
src/vueLib/tables/workspaces/WorkspaceTable.vue
Normal file
@@ -0,0 +1,226 @@
|
||||
<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';
|
||||
import { workspace } from 'src/vueLib/models/settings';
|
||||
|
||||
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[] = [];
|
||||
|
||||
removeWorkspaces.forEach((workspace: Workspace) => {
|
||||
if (workspace.id === currentUser.value?.settings?.workspace) {
|
||||
NotifyResponse(i18n.global.t('notPossibleToDeleteUsedWorkspace'), 'error');
|
||||
} else if (workspace.id) {
|
||||
workspaces.push(workspace);
|
||||
}
|
||||
});
|
||||
|
||||
appApi
|
||||
.post('workspaces/delete', {
|
||||
workspaces: workspaces,
|
||||
})
|
||||
.then(() => {
|
||||
const storageWorkspace = localStorage.getItem('workspace');
|
||||
if (workspaces.some((w) => w.uuid === storageWorkspace)) {
|
||||
workspace.value = '';
|
||||
localStorage.removeItem('workspace');
|
||||
}
|
||||
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