patches v1.0.2 #37
|
@ -1,4 +1,5 @@
|
|||
VITE_SERVER_ADDRESS = backend_url #ohne pfad
|
||||
VITE_APP_NAME_OVERWRITE = Mitgliederverwaltung # overwrites FF Admin
|
||||
VITE_IMPRINT_LINK = https://mywebsite-imprint-url
|
||||
VITE_PRIVACY_LINK = https://mywebsite-privacy-url
|
||||
VITE_CUSTOM_LOGIN_MESSAGE = betrieben von xy
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
VITE_SERVER_ADDRESS = __SERVERADDRESS__
|
||||
VITE_APP_NAME_OVERWRITE = __APPNAMEOVERWRITE__
|
||||
VITE_IMPRINT_LINK = __IMPRINTLINK__
|
||||
VITE_PRIVACY_LINK = __PRIVACYLINK__
|
||||
VITE_CUSTOM_LOGIN_MESSAGE = __CUSTOMLOGINMESSAGE__
|
|
@ -31,12 +31,14 @@ services:
|
|||
restart: unless-stopped
|
||||
|
||||
#environment:
|
||||
# - SERVERADDRESS=<backend_url (https://... | http://...)> # wichtig: ohne pfad
|
||||
# - SERVERADDRESS=<backend_url (https://... | http://...)> # wichtig: ohne Pfad
|
||||
# - APPNAMEOVERWRITE=Mitgliederverwaltung # ersetzt den Namen FF-Admin auf der Login-Seite und sonstigen Positionen in der Oberfläche
|
||||
# - IMPRINTLINK=https://mywebsite-imprint-url
|
||||
# - PRIVACYLINK=https://mywebsite-privacy-url
|
||||
# - CUSTOMLOGINMESSAGE=betrieben von xy
|
||||
#volumes:
|
||||
# - <volume|local path>/myfavicon.png:/usr/share/nginx/html/favicon.png
|
||||
# - <volume|local path>/myfavicon.ico:/usr/share/nginx/html/favicon.ico # 48x48 px Auflösung
|
||||
# - <volume|local path>/myfavicon.png:/usr/share/nginx/html/favicon.png # 512x512 px Auflösung - wird als pwa Icon genutzt
|
||||
# - <volume|local path>/mylogo.png:/usr/share/nginx/html/Logo.png
|
||||
```
|
||||
|
||||
|
@ -62,7 +64,7 @@ npm run start
|
|||
|
||||
### Konfiguration
|
||||
|
||||
Ein eigenes favicon und Logo kann über ein volume ausgetauscht werden.
|
||||
Ein eigenes Favicon und Logo kann über das verwenden Volume ausgetauscht werden. Es dürfen jedoch nur einzelne Dateien ausgetauscht werden.
|
||||
|
||||
## Einrichtung
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# FF Admin
|
||||
|
||||
## FF Admin ist eine Verwaltungsoberfläche für die Feuerwehr:
|
||||
## FF Admin ist eine Verwaltungsoberfläche für die Feuerwehr oder andere Vereine:
|
||||
|
||||
FF Admin bietet folgende Module:
|
||||
- Mitgliederverwaltung
|
||||
|
@ -23,3 +23,6 @@ FF Admin ist in Verein, Wehr, Einstellungen und Nutzerverwaltung getrennt.
|
|||
Die den Modulen zugrunde liegenden Daten können in den Einstellungen gesetzt werden.
|
||||
|
||||
Fast alle Daten lassen sich einstellen, damit es keine Einschränkungen in der Auswahl von Werten... gibt. Diese Modularität muss allerdings bei einigen Modulen gesondert eingestellt werden.
|
||||
|
||||
## Verwendung
|
||||
Damit FF Admin auch für andere Vereine genutzt werden kann, muss keine erweiterte Konfiguration vorgenommen werden. Am besten ist es alle nicht benötigten Module in der Berechtigungsverwaltung zu deaktivieren. So wird normalerweise der Abschnitt Wehr nicht außerhalb der Feuerwehr benötigt. So müssen hier lediglich keine Berechtigungen vergeben werden und das Modul ist außer für Administratoren oder Owner nicht sichtbar.
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
#!/bin/sh
|
||||
|
||||
keys="SERVERADDRESS IMPRINTLINK PRIVACYLINK CUSTOMLOGINMESSAGE"
|
||||
keys="SERVERADDRESS APPNAMEOVERWRITE IMPRINTLINK PRIVACYLINK CUSTOMLOGINMESSAGE"
|
||||
files="/usr/share/nginx/html/assets/config-*.js /usr/share/nginx/html/manifest.webmanifest"
|
||||
|
||||
# Replace env vars in files served by NGINX
|
||||
for file in /usr/share/nginx/html/assets/config-*.js
|
||||
for file in $files
|
||||
do
|
||||
echo "Processing $file ...";
|
||||
for key in $keys
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Mitgliederverwaltung</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||
"format": "prettier --write src/",
|
||||
"bnp": "npm run build-only && npm run preview",
|
||||
"generate-pwa-assets": "pwa-assets-generator --preset minimal-2023 public/CM.svg"
|
||||
"generate-pwa-assets": "pwa-assets-generator --preset minimal-2023 public/fw-wappen.png"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"id": "0",
|
||||
"name": "administration-db",
|
||||
"createdAt": "2025-01-08T15:50:20.331Z",
|
||||
"updatedAt": "2025-01-08T15:50:20.331Z",
|
||||
"createdAt": "2025-01-12T13:30:56.612Z",
|
||||
"updatedAt": "2025-01-12T13:30:56.612Z",
|
||||
"databaseType": "mariadb",
|
||||
"tables": [
|
||||
{
|
||||
|
@ -1291,6 +1291,18 @@
|
|||
},
|
||||
{
|
||||
"id": "101",
|
||||
"name": "postalCode",
|
||||
"type": {
|
||||
"name": "varchar",
|
||||
"id": "varchar"
|
||||
},
|
||||
"unique": false,
|
||||
"nullable": true,
|
||||
"primaryKey": false,
|
||||
"createdAt": 1736688552836
|
||||
},
|
||||
{
|
||||
"id": "102",
|
||||
"name": "city",
|
||||
"type": {
|
||||
"id": "varchar",
|
||||
|
@ -1305,7 +1317,7 @@
|
|||
"createdAt": 1734524896260
|
||||
},
|
||||
{
|
||||
"id": "102",
|
||||
"id": "103",
|
||||
"name": "street",
|
||||
"type": {
|
||||
"id": "varchar",
|
||||
|
@ -1320,7 +1332,7 @@
|
|||
"createdAt": 1734524896260
|
||||
},
|
||||
{
|
||||
"id": "103",
|
||||
"id": "104",
|
||||
"name": "streetNumber",
|
||||
"type": {
|
||||
"id": "int",
|
||||
|
@ -1333,7 +1345,7 @@
|
|||
"createdAt": 1734524896260
|
||||
},
|
||||
{
|
||||
"id": "104",
|
||||
"id": "105",
|
||||
"name": "streetNumberAddition",
|
||||
"type": {
|
||||
"id": "varchar",
|
||||
|
@ -1348,7 +1360,7 @@
|
|||
"createdAt": 1734524896260
|
||||
},
|
||||
{
|
||||
"id": "105",
|
||||
"id": "106",
|
||||
"name": "typeId",
|
||||
"type": {
|
||||
"id": "int",
|
||||
|
@ -1360,7 +1372,7 @@
|
|||
"createdAt": 1734524896260
|
||||
},
|
||||
{
|
||||
"id": "106",
|
||||
"id": "107",
|
||||
"name": "memberId",
|
||||
"type": {
|
||||
"id": "int",
|
||||
|
@ -1372,7 +1384,7 @@
|
|||
"createdAt": 1734524896260
|
||||
},
|
||||
{
|
||||
"id": "107",
|
||||
"id": "108",
|
||||
"name": "isSMSAlarming",
|
||||
"type": {
|
||||
"id": "tinyint",
|
||||
|
@ -1387,7 +1399,7 @@
|
|||
],
|
||||
"indexes": [
|
||||
{
|
||||
"id": "108",
|
||||
"id": "109",
|
||||
"name": "PRIMARY",
|
||||
"unique": true,
|
||||
"fieldIds": [
|
||||
|
@ -1396,20 +1408,20 @@
|
|||
"createdAt": 1734524896260
|
||||
},
|
||||
{
|
||||
"id": "109",
|
||||
"id": "110",
|
||||
"name": "FK_21994db635b47e07f45b2686a51",
|
||||
"unique": false,
|
||||
"fieldIds": [
|
||||
"105"
|
||||
"106"
|
||||
],
|
||||
"createdAt": 1734524896260
|
||||
},
|
||||
{
|
||||
"id": "110",
|
||||
"id": "111",
|
||||
"name": "FK_fc5f59e5c9aafdedd25ed8ed36e",
|
||||
"unique": false,
|
||||
"fieldIds": [
|
||||
"106"
|
||||
"107"
|
||||
],
|
||||
"createdAt": 1734524896260
|
||||
}
|
||||
|
@ -1421,14 +1433,14 @@
|
|||
"diagramId": "7gb18czobyir"
|
||||
},
|
||||
{
|
||||
"id": "111",
|
||||
"id": "112",
|
||||
"name": "member_qualifications",
|
||||
"schema": "administration",
|
||||
"x": -250.37357560579426,
|
||||
"y": 82.72883357238302,
|
||||
"fields": [
|
||||
{
|
||||
"id": "112",
|
||||
"id": "113",
|
||||
"name": "id",
|
||||
"type": {
|
||||
"id": "int",
|
||||
|
@ -1440,7 +1452,7 @@
|
|||
"createdAt": 1734524896259
|
||||
},
|
||||
{
|
||||
"id": "113",
|
||||
"id": "114",
|
||||
"name": "note",
|
||||
"type": {
|
||||
"id": "varchar",
|
||||
|
@ -1455,7 +1467,7 @@
|
|||
"createdAt": 1734524896259
|
||||
},
|
||||
{
|
||||
"id": "114",
|
||||
"id": "115",
|
||||
"name": "start",
|
||||
"type": {
|
||||
"id": "date",
|
||||
|
@ -1467,7 +1479,7 @@
|
|||
"createdAt": 1734524896259
|
||||
},
|
||||
{
|
||||
"id": "115",
|
||||
"id": "116",
|
||||
"name": "end",
|
||||
"type": {
|
||||
"id": "date",
|
||||
|
@ -1480,7 +1492,7 @@
|
|||
"createdAt": 1734524896259
|
||||
},
|
||||
{
|
||||
"id": "116",
|
||||
"id": "117",
|
||||
"name": "terminationReason",
|
||||
"type": {
|
||||
"id": "varchar",
|
||||
|
@ -1495,7 +1507,7 @@
|
|||
"createdAt": 1734524896259
|
||||
},
|
||||
{
|
||||
"id": "117",
|
||||
"id": "118",
|
||||
"name": "memberId",
|
||||
"type": {
|
||||
"id": "int",
|
||||
|
@ -1507,7 +1519,7 @@
|
|||
"createdAt": 1734524896259
|
||||
},
|
||||
{
|
||||
"id": "118",
|
||||
"id": "119",
|
||||
"name": "qualificationId",
|
||||
"type": {
|
||||
"id": "int",
|
||||
|
@ -1521,31 +1533,31 @@
|
|||
],
|
||||
"indexes": [
|
||||
{
|
||||
"id": "119",
|
||||
"id": "120",
|
||||
"name": "PRIMARY",
|
||||
"unique": true,
|
||||
"fieldIds": [
|
||||
"112"
|
||||
],
|
||||
"createdAt": 1734524896259
|
||||
},
|
||||
{
|
||||
"id": "120",
|
||||
"name": "FK_98b70e687c35709d2f01b3d7d74",
|
||||
"unique": false,
|
||||
"fieldIds": [
|
||||
"117"
|
||||
"113"
|
||||
],
|
||||
"createdAt": 1734524896259
|
||||
},
|
||||
{
|
||||
"id": "121",
|
||||
"name": "FK_dbebe53df1caa0b6715a220b0ea",
|
||||
"name": "FK_98b70e687c35709d2f01b3d7d74",
|
||||
"unique": false,
|
||||
"fieldIds": [
|
||||
"118"
|
||||
],
|
||||
"createdAt": 1734524896259
|
||||
},
|
||||
{
|
||||
"id": "122",
|
||||
"name": "FK_dbebe53df1caa0b6715a220b0ea",
|
||||
"unique": false,
|
||||
"fieldIds": [
|
||||
"119"
|
||||
],
|
||||
"createdAt": 1734524896259
|
||||
}
|
||||
],
|
||||
"color": "#ff6b8a",
|
||||
|
@ -1555,14 +1567,14 @@
|
|||
"diagramId": "7gb18czobyir"
|
||||
},
|
||||
{
|
||||
"id": "122",
|
||||
"id": "123",
|
||||
"name": "executive_position",
|
||||
"schema": "administration",
|
||||
"x": -542.0601569820527,
|
||||
"y": 474.7348899814151,
|
||||
"fields": [
|
||||
{
|
||||
"id": "123",
|
||||
"id": "124",
|
||||
"name": "id",
|
||||
"type": {
|
||||
"id": "int",
|
||||
|
@ -1574,7 +1586,7 @@
|
|||
"createdAt": 1734524896260
|
||||
},
|
||||
{
|
||||
"id": "124",
|
||||
"id": "125",
|
||||
"name": "position",
|
||||
"type": {
|
||||
"id": "varchar",
|
||||
|
@ -1590,11 +1602,11 @@
|
|||
],
|
||||
"indexes": [
|
||||
{
|
||||
"id": "125",
|
||||
"id": "126",
|
||||
"name": "PRIMARY",
|
||||
"unique": true,
|
||||
"fieldIds": [
|
||||
"123"
|
||||
"124"
|
||||
],
|
||||
"createdAt": 1734524896260
|
||||
}
|
||||
|
@ -1606,14 +1618,14 @@
|
|||
"diagramId": "7gb18czobyir"
|
||||
},
|
||||
{
|
||||
"id": "126",
|
||||
"id": "127",
|
||||
"name": "qualification",
|
||||
"schema": "administration",
|
||||
"x": -568.0578068648438,
|
||||
"y": 192.56221408776412,
|
||||
"fields": [
|
||||
{
|
||||
"id": "127",
|
||||
"id": "128",
|
||||
"name": "id",
|
||||
"type": {
|
||||
"id": "int",
|
||||
|
@ -1625,7 +1637,7 @@
|
|||
"createdAt": 1734524896260
|
||||
},
|
||||
{
|
||||
"id": "128",
|
||||
"id": "129",
|
||||
"name": "qualification",
|
||||
"type": {
|
||||
"id": "varchar",
|
||||
|
@ -1639,7 +1651,7 @@
|
|||
"createdAt": 1734524896260
|
||||
},
|
||||
{
|
||||
"id": "129",
|
||||
"id": "130",
|
||||
"name": "description",
|
||||
"type": {
|
||||
"id": "varchar",
|
||||
|
@ -1656,11 +1668,11 @@
|
|||
],
|
||||
"indexes": [
|
||||
{
|
||||
"id": "130",
|
||||
"id": "131",
|
||||
"name": "PRIMARY",
|
||||
"unique": true,
|
||||
"fieldIds": [
|
||||
"127"
|
||||
"128"
|
||||
],
|
||||
"createdAt": 1734524896260
|
||||
}
|
||||
|
@ -1674,28 +1686,14 @@
|
|||
],
|
||||
"relationships": [
|
||||
{
|
||||
"id": "131",
|
||||
"id": "132",
|
||||
"name": "FK_1fd52c8f109123e5a2c67dc2c83",
|
||||
"sourceSchema": "administration",
|
||||
"targetSchema": "administration",
|
||||
"sourceTableId": "1",
|
||||
"targetTableId": "122",
|
||||
"targetTableId": "123",
|
||||
"sourceFieldId": "7",
|
||||
"targetFieldId": "123",
|
||||
"sourceCardinality": "many",
|
||||
"targetCardinality": "one",
|
||||
"createdAt": 1734524896262,
|
||||
"diagramId": "7gb18czobyir"
|
||||
},
|
||||
{
|
||||
"id": "132",
|
||||
"name": "FK_21994db635b47e07f45b2686a51",
|
||||
"sourceSchema": "administration",
|
||||
"targetSchema": "administration",
|
||||
"sourceTableId": "96",
|
||||
"targetTableId": "71",
|
||||
"sourceFieldId": "105",
|
||||
"targetFieldId": "72",
|
||||
"targetFieldId": "124",
|
||||
"sourceCardinality": "many",
|
||||
"targetCardinality": "one",
|
||||
"createdAt": 1734524896262,
|
||||
|
@ -1703,6 +1701,20 @@
|
|||
},
|
||||
{
|
||||
"id": "133",
|
||||
"name": "FK_21994db635b47e07f45b2686a51",
|
||||
"sourceSchema": "administration",
|
||||
"targetSchema": "administration",
|
||||
"sourceTableId": "96",
|
||||
"targetTableId": "71",
|
||||
"sourceFieldId": "106",
|
||||
"targetFieldId": "72",
|
||||
"sourceCardinality": "many",
|
||||
"targetCardinality": "one",
|
||||
"createdAt": 1734524896262,
|
||||
"diagramId": "7gb18czobyir"
|
||||
},
|
||||
{
|
||||
"id": "134",
|
||||
"name": "FK_2912b056a5d0b7977360a986164",
|
||||
"sourceSchema": "administration",
|
||||
"targetSchema": "administration",
|
||||
|
@ -1716,7 +1728,7 @@
|
|||
"diagramId": "7gb18czobyir"
|
||||
},
|
||||
{
|
||||
"id": "134",
|
||||
"id": "135",
|
||||
"name": "FK_3b4b41597707b13086e71727422",
|
||||
"sourceSchema": "administration",
|
||||
"targetSchema": "administration",
|
||||
|
@ -1730,13 +1742,13 @@
|
|||
"diagramId": "7gb18czobyir"
|
||||
},
|
||||
{
|
||||
"id": "135",
|
||||
"id": "136",
|
||||
"name": "FK_98b70e687c35709d2f01b3d7d74",
|
||||
"sourceSchema": "administration",
|
||||
"targetSchema": "administration",
|
||||
"sourceTableId": "111",
|
||||
"sourceTableId": "112",
|
||||
"targetTableId": "60",
|
||||
"sourceFieldId": "117",
|
||||
"sourceFieldId": "118",
|
||||
"targetFieldId": "61",
|
||||
"sourceCardinality": "many",
|
||||
"targetCardinality": "one",
|
||||
|
@ -1744,7 +1756,7 @@
|
|||
"diagramId": "7gb18czobyir"
|
||||
},
|
||||
{
|
||||
"id": "136",
|
||||
"id": "137",
|
||||
"name": "FK_a47e04bfd3671d8a375d1896d25",
|
||||
"sourceSchema": "administration",
|
||||
"targetSchema": "administration",
|
||||
|
@ -1758,7 +1770,7 @@
|
|||
"diagramId": "7gb18czobyir"
|
||||
},
|
||||
{
|
||||
"id": "137",
|
||||
"id": "138",
|
||||
"name": "FK_ba47b44c2ddf34c1bcc75df6675",
|
||||
"sourceSchema": "administration",
|
||||
"targetSchema": "administration",
|
||||
|
@ -1772,21 +1784,21 @@
|
|||
"diagramId": "7gb18czobyir"
|
||||
},
|
||||
{
|
||||
"id": "138",
|
||||
"id": "139",
|
||||
"name": "FK_dbebe53df1caa0b6715a220b0ea",
|
||||
"sourceSchema": "administration",
|
||||
"targetSchema": "administration",
|
||||
"sourceTableId": "111",
|
||||
"targetTableId": "126",
|
||||
"sourceFieldId": "118",
|
||||
"targetFieldId": "127",
|
||||
"sourceTableId": "112",
|
||||
"targetTableId": "127",
|
||||
"sourceFieldId": "119",
|
||||
"targetFieldId": "128",
|
||||
"sourceCardinality": "many",
|
||||
"targetCardinality": "one",
|
||||
"createdAt": 1734524896262,
|
||||
"diagramId": "7gb18czobyir"
|
||||
},
|
||||
{
|
||||
"id": "139",
|
||||
"id": "140",
|
||||
"name": "FK_e9fd4d37c4ac0fb08bd6eeeda3c",
|
||||
"sourceSchema": "administration",
|
||||
"targetSchema": "administration",
|
||||
|
@ -1800,13 +1812,13 @@
|
|||
"diagramId": "7gb18czobyir"
|
||||
},
|
||||
{
|
||||
"id": "140",
|
||||
"id": "141",
|
||||
"name": "FK_fc5f59e5c9aafdedd25ed8ed36e",
|
||||
"sourceSchema": "administration",
|
||||
"targetSchema": "administration",
|
||||
"sourceTableId": "96",
|
||||
"targetTableId": "60",
|
||||
"sourceFieldId": "106",
|
||||
"sourceFieldId": "107",
|
||||
"targetFieldId": "61",
|
||||
"sourceCardinality": "many",
|
||||
"targetCardinality": "one",
|
||||
|
@ -1816,7 +1828,7 @@
|
|||
],
|
||||
"dependencies": [
|
||||
{
|
||||
"id": "141",
|
||||
"id": "142",
|
||||
"schema": "administration",
|
||||
"tableId": "60",
|
||||
"dependentSchema": "administration",
|
||||
|
@ -1825,36 +1837,27 @@
|
|||
"diagramId": "7gb18czobyir"
|
||||
},
|
||||
{
|
||||
"id": "142",
|
||||
"id": "143",
|
||||
"schema": "administration",
|
||||
"tableId": "126",
|
||||
"tableId": "127",
|
||||
"dependentSchema": "administration",
|
||||
"dependentTableId": "86",
|
||||
"createdAt": 1734524897266,
|
||||
"diagramId": "7gb18czobyir"
|
||||
},
|
||||
{
|
||||
"id": "143",
|
||||
"id": "144",
|
||||
"schema": "administration",
|
||||
"tableId": "111",
|
||||
"tableId": "112",
|
||||
"dependentSchema": "administration",
|
||||
"dependentTableId": "86",
|
||||
"createdAt": 1734524897267,
|
||||
"diagramId": "7gb18czobyir"
|
||||
},
|
||||
{
|
||||
"id": "144",
|
||||
"schema": "administration",
|
||||
"tableId": "60",
|
||||
"dependentSchema": "administration",
|
||||
"dependentTableId": "11",
|
||||
"createdAt": 1734524897283,
|
||||
"diagramId": "7gb18czobyir"
|
||||
},
|
||||
{
|
||||
"id": "145",
|
||||
"schema": "administration",
|
||||
"tableId": "1",
|
||||
"tableId": "60",
|
||||
"dependentSchema": "administration",
|
||||
"dependentTableId": "11",
|
||||
"createdAt": 1734524897283,
|
||||
|
@ -1863,6 +1866,15 @@
|
|||
{
|
||||
"id": "146",
|
||||
"schema": "administration",
|
||||
"tableId": "1",
|
||||
"dependentSchema": "administration",
|
||||
"dependentTableId": "11",
|
||||
"createdAt": 1734524897283,
|
||||
"diagramId": "7gb18czobyir"
|
||||
},
|
||||
{
|
||||
"id": "147",
|
||||
"schema": "administration",
|
||||
"tableId": "60",
|
||||
"dependentSchema": "administration",
|
||||
"dependentTableId": "21",
|
||||
|
@ -1870,7 +1882,7 @@
|
|||
"diagramId": "7gb18czobyir"
|
||||
},
|
||||
{
|
||||
"id": "147",
|
||||
"id": "148",
|
||||
"schema": "administration",
|
||||
"tableId": "56",
|
||||
"dependentSchema": "administration",
|
||||
|
@ -1879,7 +1891,7 @@
|
|||
"diagramId": "7gb18czobyir"
|
||||
},
|
||||
{
|
||||
"id": "148",
|
||||
"id": "149",
|
||||
"schema": "administration",
|
||||
"tableId": "35",
|
||||
"dependentSchema": "administration",
|
||||
|
@ -1888,16 +1900,16 @@
|
|||
"diagramId": "7gb18czobyir"
|
||||
},
|
||||
{
|
||||
"id": "149",
|
||||
"id": "150",
|
||||
"schema": "administration",
|
||||
"tableId": "122",
|
||||
"tableId": "123",
|
||||
"dependentSchema": "administration",
|
||||
"dependentTableId": "11",
|
||||
"createdAt": 1734524897283,
|
||||
"diagramId": "7gb18czobyir"
|
||||
},
|
||||
{
|
||||
"id": "150",
|
||||
"id": "151",
|
||||
"schema": "administration",
|
||||
"tableId": "60",
|
||||
"dependentSchema": "administration",
|
||||
|
|
Before Width: | Height: | Size: 647 KiB After Width: | Height: | Size: 650 KiB |
Before Width: | Height: | Size: 11 MiB After Width: | Height: | Size: 11 MiB |
BIN
public/favicon.ico
Normal file
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 516 KiB After Width: | Height: | Size: 16 KiB |
BIN
public/fw-wappen.png
Normal file
After Width: | Height: | Size: 516 KiB |
|
@ -5,10 +5,16 @@
|
|||
<a v-if="config.privacy_link" :href="config.privacy_link" target="_blank">Impressum</a>
|
||||
</div>
|
||||
<p v-if="config.custom_login_message">{{ config.custom_login_message }}</p>
|
||||
<a href="https://jk-effects.com" target="_blank"> © Admin-Portal by JK Effects </a>
|
||||
<p>
|
||||
©
|
||||
<a href="https://forgejo.jk-effects.cloud/Ehrenamt/ff-admin" target="_blank">Admin-Portal</a>
|
||||
by
|
||||
<a href="https://jk-effects.com" target="_blank">JK Effects</a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { config } from '../config'
|
||||
import { config } from '@/config'
|
||||
</script>
|
|
@ -2,7 +2,7 @@
|
|||
<header class="flex flex-row h-16 min-h-16 justify-between p-3 md:px-5 bg-white shadow-sm">
|
||||
<RouterLink to="/" class="flex flex-row gap-2 align-bottom w-fit h-full">
|
||||
<img src="/Logo.png" alt="LOGO" class="h-full w-auto" />
|
||||
<h1 v-if="false" class="font-bold text-3xl w-fit whitespace-nowrap">FF Admin</h1>
|
||||
<h1 v-if="false" class="font-bold text-3xl w-fit whitespace-nowrap">{{config.app_name_overwrite || "FF Admin"}}</h1>
|
||||
</RouterLink>
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
<div v-if="authCheck" class="hidden md:flex flex-row gap-2 h-full align-middle">
|
||||
|
@ -30,6 +30,7 @@ import { useAuthStore } from "@/stores/auth";
|
|||
import { useNavigationStore } from "@/stores/admin/navigation";
|
||||
import TopLevelLink from "./admin/TopLevelLink.vue";
|
||||
import UserMenu from "./UserMenu.vue";
|
||||
import { config } from "@/config"
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
|
|
@ -113,10 +113,10 @@ export default defineComponent({
|
|||
},
|
||||
methods: {
|
||||
...mapActions(useNotificationStore, ["revoke"]),
|
||||
close(id: number) {
|
||||
close(id: string) {
|
||||
this.revoke(id);
|
||||
},
|
||||
hovering(id: number, value: boolean, timeout?: number) {
|
||||
hovering(id: string, value: boolean, timeout?: number) {
|
||||
if (value) {
|
||||
clearTimeout(this.timeouts[id]);
|
||||
} else {
|
||||
|
|
|
@ -159,7 +159,7 @@ const loadPage = (newPage: number | ".") => {
|
|||
if (pageEnd > entryCount.value) pageEnd = entryCount.value;
|
||||
|
||||
let loadedElementCount = filterData(props.items, searchString.value, pageStart, pageEnd).length;
|
||||
console.log(loadedElementCount, props.maxEntriesPerPage, pageStart, pageEnd)
|
||||
|
||||
if (loadedElementCount < props.maxEntriesPerPage && (pageEnd != props.totalCount || loadedElementCount == 0))
|
||||
emit("loadData", pageStart, props.maxEntriesPerPage, searchString.value);
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ export default defineComponent({
|
|||
default: "LINK",
|
||||
},
|
||||
link: {
|
||||
type: Object as PropType<string | { name: string }>,
|
||||
type: Object as PropType<string | { name: string, params?:{[key:string]:string} }>,
|
||||
default: "/",
|
||||
},
|
||||
active: {
|
||||
|
|
|
@ -124,11 +124,7 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button
|
||||
primary-outline
|
||||
@click="closeModal"
|
||||
:disabled="status != null && status != 'loading' && status?.status != 'failed'"
|
||||
>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
|
@ -194,6 +190,7 @@ export default defineComponent({
|
|||
location: formData.location.value,
|
||||
allDay: this.allDay,
|
||||
};
|
||||
this.status = "loading";
|
||||
this.createCalendar(createCalendar)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -38,7 +38,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">abbrechen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -78,6 +80,7 @@ export default defineComponent({
|
|||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useCalendarStore, ["deleteCalendar"]),
|
||||
triggerDelete() {
|
||||
this.status = "loading";
|
||||
this.deleteCalendar(this.data)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -166,11 +166,7 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button
|
||||
primary-outline
|
||||
@click="closeModal"
|
||||
:disabled="status != null && status != 'loading' && status?.status != 'failed'"
|
||||
>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
abbrechen / schließen
|
||||
</button>
|
||||
</div>
|
||||
|
@ -264,6 +260,7 @@ export default defineComponent({
|
|||
location: formData.location.value,
|
||||
allDay: this.calendar.allDay,
|
||||
};
|
||||
this.status = "loading";
|
||||
this.updateCalendar(updateCalendar)
|
||||
.then(() => {
|
||||
this.fetchItem();
|
||||
|
|
|
@ -133,6 +133,7 @@ export default defineComponent({
|
|||
birthdate: formData.birthdate.value,
|
||||
internalId: formData.internalId.value,
|
||||
};
|
||||
this.status = "loading";
|
||||
this.createMember(createMember)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -72,6 +72,7 @@ export default defineComponent({
|
|||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useMemberStore, ["deleteMember"]),
|
||||
triggerDelete() {
|
||||
this.status = "loading";
|
||||
this.deleteMember(this.data)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -140,6 +140,7 @@ export default defineComponent({
|
|||
given: formData.given.checked,
|
||||
awardId: this.selectedAward.id,
|
||||
};
|
||||
this.status = "loading";
|
||||
this.createMemberAward(createMemberAward)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -65,6 +65,7 @@ export default defineComponent({
|
|||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useMemberAwardStore, ["deleteMemberAward"]),
|
||||
triggerDelete() {
|
||||
this.status = "loading";
|
||||
this.deleteMemberAward(this.data)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -83,7 +83,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">schließen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
schließen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -167,6 +169,7 @@ export default defineComponent({
|
|||
given: formData.given.checked,
|
||||
awardId: this.memberAward.awardId,
|
||||
};
|
||||
this.status = "loading";
|
||||
this.updateMemberAward(updateMemberAward)
|
||||
.then(() => {
|
||||
this.fetchItem();
|
||||
|
|
|
@ -166,7 +166,7 @@ export default defineComponent({
|
|||
preferred: formData.preferred.checked,
|
||||
mobile: formData.mobile?.value,
|
||||
email: formData.email?.value,
|
||||
postalCode: formData.postalCode.value,
|
||||
postalCode: formData.postalCode?.value,
|
||||
city: formData.city?.value,
|
||||
street: formData.street?.value,
|
||||
streetNumber: formData.streetNumber?.value,
|
||||
|
@ -175,6 +175,7 @@ export default defineComponent({
|
|||
isSMSAlarming: formData.isSMSAlarming?.checked,
|
||||
typeId: this.selectedCommunicationType.id,
|
||||
};
|
||||
this.status = "loading";
|
||||
this.createCommunication(createCommunication)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -68,6 +68,7 @@ export default defineComponent({
|
|||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useCommunicationStore, ["deleteCommunication"]),
|
||||
triggerDelete() {
|
||||
this.status = "loading";
|
||||
this.deleteCommunication(this.data)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -62,7 +62,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">schließen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
schließen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -144,6 +146,7 @@ export default defineComponent({
|
|||
isNewsletterMain: formData.isNewsletterMain.checked,
|
||||
isSMSAlarming: formData.isSMSAlarming?.checked,
|
||||
};
|
||||
this.status = "loading";
|
||||
this.updateCommunication(updateCommunication)
|
||||
.then(() => {
|
||||
this.fetchItem();
|
||||
|
|
|
@ -141,6 +141,7 @@ export default defineComponent({
|
|||
note: formData.note.value,
|
||||
executivePositionId: this.selectedExecutivePosition.id,
|
||||
};
|
||||
this.status = "loading";
|
||||
this.createMemberExecutivePosition(createMemberExecutivePosition)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -65,6 +65,7 @@ export default defineComponent({
|
|||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useMemberExecutivePositionStore, ["deleteMemberExecutivePosition"]),
|
||||
triggerDelete() {
|
||||
this.status = "loading";
|
||||
this.deleteMemberExecutivePosition(this.data)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -89,7 +89,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">schließen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
schließen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -176,6 +178,7 @@ export default defineComponent({
|
|||
note: formData.note.value,
|
||||
executivePositionId: this.memberExecutivePosition.executivePositionId,
|
||||
};
|
||||
this.status = "loading";
|
||||
this.updateMemberExecutivePosition(updateMemberExecutivePosition)
|
||||
.then(() => {
|
||||
this.fetchItem();
|
||||
|
|
|
@ -148,6 +148,7 @@ export default defineComponent({
|
|||
note: formData.note.value,
|
||||
qualificationId: this.selectedQualification.id,
|
||||
};
|
||||
this.status = "loading";
|
||||
this.createMemberQualification(createMemberQualification)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -69,6 +69,7 @@ export default defineComponent({
|
|||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useMemberQualificationStore, ["deleteMemberQualification"]),
|
||||
triggerDelete() {
|
||||
this.status = "loading";
|
||||
this.deleteMemberQualification(this.data)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -90,7 +90,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">schließen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
schließen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -175,6 +177,7 @@ export default defineComponent({
|
|||
terminationReason: formData.terminationReason.value,
|
||||
qualificationId: this.memberQualification.qualificationId,
|
||||
};
|
||||
this.status = "loading";
|
||||
this.updateMemberQualification(updateMemberQualification)
|
||||
.then(() => {
|
||||
this.fetchItem();
|
||||
|
|
|
@ -131,6 +131,7 @@ export default defineComponent({
|
|||
start: formData.start.value,
|
||||
statusId: this.selectedStatus.id,
|
||||
};
|
||||
this.status = "loading";
|
||||
this.createMembership(createMember)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -67,6 +67,7 @@ export default defineComponent({
|
|||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useMembershipStore, ["deleteMembership"]),
|
||||
triggerDelete() {
|
||||
this.status = "loading";
|
||||
this.deleteMembership(this.data)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -86,7 +86,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">schließen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
schließen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -170,6 +172,7 @@ export default defineComponent({
|
|||
terminationReason: formData.terminationReason.value,
|
||||
statusId: this.membership.statusId,
|
||||
};
|
||||
this.status = "loading";
|
||||
this.updateMembership(updateMembership)
|
||||
.then(() => {
|
||||
this.fetchItem();
|
||||
|
|
|
@ -61,6 +61,7 @@ export default defineComponent({
|
|||
let createNewsletter: CreateNewsletterViewModel = {
|
||||
title: formData.title.value,
|
||||
};
|
||||
this.status = "loading";
|
||||
this.createNewsletter(createNewsletter)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
<template>
|
||||
<div class="w-full md:max-w-md">
|
||||
<div class="flex flex-col items-center">
|
||||
<p class="text-xl font-medium">Newsletter Mail-Versand Logs</p>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div class="h-96 overflow-y-scroll">
|
||||
<p v-for="entry in mailSourceMessages">
|
||||
{{ entry }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal">
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import { useModalStore } from "@/stores/modal";
|
||||
import { useNewsletterPrintoutStore } from "@/stores/admin/club/newsletter/newsletterPrintout";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
};
|
||||
},
|
||||
computed:{
|
||||
...mapState(useNewsletterPrintoutStore, ["mailSourceMessages"])
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useModalStore, ["closeModal"]),
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,50 @@
|
|||
<template>
|
||||
<div class="w-full md:max-w-md">
|
||||
<div class="flex flex-col items-center">
|
||||
<p class="text-xl font-medium">Newsletter Druck-Prozess Logs</p>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div class="flex flex-col gap-2 h-96 overflow-y-scroll">
|
||||
<div
|
||||
v-for="entry in pdfSourceMessages"
|
||||
class="flex flex-row gap-2 border border-gray-200 rounded-md p-1 items-center"
|
||||
>
|
||||
<SuccessCheckmark v-if="entry.factor == 'success'" class="w-5 h-5" />
|
||||
<InformationCircleIcon v-else-if="entry.factor == 'info'" class="w-5 h-5 min-h-5 min-w-5 text-gray-500" />
|
||||
<FailureXMark v-else-if="entry.factor == 'failed'" class="w-5 h-5" />
|
||||
<p>{{ entry.iteration }}/{{ entry.total }}: {{ entry.msg }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal">abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import { useModalStore } from "@/stores/modal";
|
||||
import { useNewsletterPrintoutStore } from "@/stores/admin/club/newsletter/newsletterPrintout";
|
||||
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
|
||||
import { InformationCircleIcon } from "@heroicons/vue/24/solid";
|
||||
import FailureXMark from "@/components/FailureXMark.vue";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
...mapState(useNewsletterPrintoutStore, ["pdfSourceMessages"]),
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useModalStore, ["closeModal"]),
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -64,6 +64,7 @@ export default defineComponent({
|
|||
title: formData.title.value,
|
||||
date: formData.date.value,
|
||||
};
|
||||
this.status = "loading";
|
||||
this.createProtocol(createProtocol)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -19,7 +19,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">abbrechen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -57,6 +59,7 @@ export default defineComponent({
|
|||
let createAward: CreateAwardViewModel = {
|
||||
award: formData.award.value,
|
||||
};
|
||||
this.status = "loading";
|
||||
this.createAward(createAward)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">abbrechen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -57,6 +59,7 @@ export default defineComponent({
|
|||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useAwardStore, ["deleteAward"]),
|
||||
triggerDelete() {
|
||||
this.status = "loading";
|
||||
this.deleteAward(this.data)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -32,11 +32,7 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button
|
||||
primary-outline
|
||||
@click="closeModal"
|
||||
:disabled="status != null && status != 'loading' && status?.status != 'failed'"
|
||||
>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
|
@ -82,6 +78,7 @@ export default defineComponent({
|
|||
nscdr: formData.nscdr.checked,
|
||||
passphrase: formData.passphrase.value,
|
||||
};
|
||||
this.status = "loading";
|
||||
this.createCalendarType(createCalendarType)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">abbrechen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -56,6 +58,7 @@ export default defineComponent({
|
|||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useCalendarTypeStore, ["deleteCalendarType"]),
|
||||
triggerDelete() {
|
||||
this.status = "loading";
|
||||
this.deleteCalendarType(this.data)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -65,7 +65,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">abbrechen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -114,6 +116,7 @@ export default defineComponent({
|
|||
type: formData.communicationType.value,
|
||||
fields: this.selectedFields,
|
||||
};
|
||||
this.status = "loading";
|
||||
this.createCommunicationType(createCommunicationType)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">abbrechen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -56,6 +58,7 @@ export default defineComponent({
|
|||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useCommunicationTypeStore, ["deleteCommunicationType"]),
|
||||
triggerDelete() {
|
||||
this.status = "loading";
|
||||
this.deleteCommunicationType(this.data)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -19,7 +19,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">abbrechen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -57,6 +59,7 @@ export default defineComponent({
|
|||
let createExecutivePosition: CreateExecutivePositionViewModel = {
|
||||
position: formData.executivePosition.value,
|
||||
};
|
||||
this.status = "loading";
|
||||
this.createExecutivePosition(createExecutivePosition)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">abbrechen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -57,6 +59,7 @@ export default defineComponent({
|
|||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useExecutivePositionStore, ["deleteExecutivePosition"]),
|
||||
triggerDelete() {
|
||||
this.status = "loading";
|
||||
this.deleteExecutivePosition(this.data)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -19,7 +19,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">abbrechen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -57,6 +59,7 @@ export default defineComponent({
|
|||
let createMembershipStatus: CreateMembershipStatusViewModel = {
|
||||
status: formData.membershipStatus.value,
|
||||
};
|
||||
this.status = "loading";
|
||||
this.createMembershipStatus(createMembershipStatus)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">abbrechen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -57,6 +59,7 @@ export default defineComponent({
|
|||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useMembershipStatusStore, ["deleteMembershipStatus"]),
|
||||
triggerDelete() {
|
||||
this.status = "loading";
|
||||
this.deleteMembershipStatus(this.data)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -23,7 +23,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">abbrechen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -62,6 +64,7 @@ export default defineComponent({
|
|||
qualification: formData.qualification.value,
|
||||
description: formData.description.value,
|
||||
};
|
||||
this.status = "loading";
|
||||
this.createQualification(createQualification)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">abbrechen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -57,6 +59,7 @@ export default defineComponent({
|
|||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useQualificationStore, ["deleteQualification"]),
|
||||
triggerDelete() {
|
||||
this.status = "loading";
|
||||
this.deleteQualification(this.data)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -19,7 +19,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">abbrechen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -62,6 +64,7 @@ export default defineComponent({
|
|||
title: formData.title.value,
|
||||
query: this.query ?? "",
|
||||
};
|
||||
this.status = "loading";
|
||||
this.createQueryStore(createAward)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">abbrechen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -56,6 +58,7 @@ export default defineComponent({
|
|||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useQueryStoreStore, ["deleteQueryStore"]),
|
||||
triggerDelete() {
|
||||
this.status = "loading";
|
||||
this.deleteQueryStore(this.data)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -21,7 +21,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">abbrechen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -73,6 +75,7 @@ export default defineComponent({
|
|||
id: this.data,
|
||||
query: this.query ?? "",
|
||||
};
|
||||
this.status = "loading";
|
||||
this.updateActiveQueryStore(updateQuery)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -23,7 +23,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">abbrechen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -62,6 +64,7 @@ export default defineComponent({
|
|||
template: formData.template.value,
|
||||
description: formData.description.value,
|
||||
};
|
||||
this.status = "loading";
|
||||
this.createTemplate(createTemplate)
|
||||
.then((res) => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">abbrechen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -57,6 +59,7 @@ export default defineComponent({
|
|||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useTemplateStore, ["deleteTemplate"]),
|
||||
triggerDelete() {
|
||||
this.status = "loading";
|
||||
this.deleteTemplate(this.data)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -19,7 +19,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">abbrechen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -53,6 +55,7 @@ export default defineComponent({
|
|||
...mapActions(useRoleStore, ["createRole"]),
|
||||
triggerCreateRole(e: any) {
|
||||
let formData = e.target.elements;
|
||||
this.status = "loading";
|
||||
this.createRole(formData.role.value)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">abbrechen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -56,6 +58,7 @@ export default defineComponent({
|
|||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useRoleStore, ["deleteRole"]),
|
||||
triggerDeleteRole() {
|
||||
this.status = "loading";
|
||||
this.deleteRole(this.data)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">abbrechen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -56,6 +58,7 @@ export default defineComponent({
|
|||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useUserStore, ["deleteUser"]),
|
||||
triggerDeleteUser() {
|
||||
this.status = "loading";
|
||||
this.deleteUser(this.data)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
|
|
|
@ -32,7 +32,9 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null">abbrechen</button>
|
||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -70,13 +72,13 @@ export default defineComponent({
|
|||
...mapActions(useInviteStore, ["createInvite"]),
|
||||
invite(e: any) {
|
||||
let formData = e.target.elements;
|
||||
this.status = "loading";
|
||||
let createInvite: CreateInviteViewModel = {
|
||||
username: formData.username.value,
|
||||
mail: formData.mail.value,
|
||||
firstname: formData.firstname.value,
|
||||
lastname: formData.lastname.value,
|
||||
};
|
||||
this.status = "loading";
|
||||
this.createInvite(createInvite)
|
||||
.then((result) => {
|
||||
this.status = { status: "success" };
|
||||
|
@ -86,7 +88,7 @@ export default defineComponent({
|
|||
})
|
||||
.catch((err) => {
|
||||
this.status = { status: "failed", reason: err.response.data };
|
||||
})
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export interface Config {
|
||||
server_address: string;
|
||||
app_name_overwrite: string;
|
||||
imprint_link: string;
|
||||
privacy_link: string;
|
||||
custom_login_message: string;
|
||||
|
@ -7,6 +8,7 @@ export interface Config {
|
|||
|
||||
export const config: Config = {
|
||||
server_address: import.meta.env.VITE_SERVER_ADDRESS,
|
||||
app_name_overwrite: import.meta.env.VITE_APP_NAME_OVERWRITE,
|
||||
imprint_link: import.meta.env.VITE_IMPRINT_LINK,
|
||||
privacy_link: import.meta.env.VITE_PRIVACY_LINK,
|
||||
custom_login_message: import.meta.env.VITE_CUSTOM_LOGIN_MESSAGE,
|
||||
|
|
|
@ -14,7 +14,6 @@ export function flattenQueryResult(result: Array<QueryResult>): Array<{ [key: st
|
|||
const newKey = prefix ? `${prefix}_${key}` : key;
|
||||
|
||||
if (Array.isArray(value) && value.every((item) => typeof item === "object" && item !== null)) {
|
||||
console.log(value, newKey);
|
||||
const arrayResults: Array<{ [key: string]: FieldType }> = [];
|
||||
value.forEach((item) => {
|
||||
const flattenedItems = flatten(item, newKey);
|
||||
|
@ -29,7 +28,6 @@ export function flattenQueryResult(result: Array<QueryResult>): Array<{ [key: st
|
|||
});
|
||||
results = tempResults;
|
||||
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
||||
console.log(value, newKey);
|
||||
const objResults = flatten(value as QueryResult, newKey);
|
||||
const tempResults: Array<{ [key: string]: FieldType }> = [];
|
||||
results.forEach((res) => {
|
||||
|
|
|
@ -9,6 +9,7 @@ import type { PermissionType, PermissionSection, PermissionModule } from "@/type
|
|||
import { resetMemberStores, setMemberId } from "./memberGuard";
|
||||
import { resetProtocolStores, setProtocolId } from "./protocolGuard";
|
||||
import { resetNewsletterStores, setNewsletterId } from "./newsletterGuard";
|
||||
import { config } from "../config";
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
|
@ -683,6 +684,10 @@ const router = createRouter({
|
|||
],
|
||||
});
|
||||
|
||||
router.afterEach((to, from) => {
|
||||
document.title = config.app_name_overwrite || "FF Admin";
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
declare module "vue-router" {
|
||||
|
|
|
@ -9,8 +9,6 @@ export async function setNewsletterId(to: any, from: any, next: any) {
|
|||
|
||||
useNewsletterDatesStore().$reset();
|
||||
useNewsletterRecipientsStore().$reset();
|
||||
useNewsletterPrintoutStore().unsubscribePdfPrintingProgress();
|
||||
useNewsletterPrintoutStore().unsubscribeMailSendingProgress();
|
||||
useNewsletterPrintoutStore().$reset();
|
||||
|
||||
next();
|
||||
|
@ -23,8 +21,6 @@ export async function resetNewsletterStores(to: any, from: any, next: any) {
|
|||
|
||||
useNewsletterDatesStore().$reset();
|
||||
useNewsletterRecipientsStore().$reset();
|
||||
useNewsletterPrintoutStore().unsubscribePdfPrintingProgress();
|
||||
useNewsletterPrintoutStore().unsubscribeMailSendingProgress();
|
||||
useNewsletterPrintoutStore().$reset();
|
||||
|
||||
next();
|
||||
|
|
|
@ -28,6 +28,15 @@ http.interceptors.request.use(
|
|||
}
|
||||
}
|
||||
|
||||
const isPWA =
|
||||
window.matchMedia("(display-mode: standalone)").matches ||
|
||||
window.matchMedia("(display-mode: fullscreen)").matches;
|
||||
if (isPWA) {
|
||||
if (config.headers) {
|
||||
config.headers["X-PWA-Client"] = isPWA ? "true" : "false";
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
|
@ -53,11 +62,15 @@ http.interceptors.response.use(
|
|||
.then(() => {
|
||||
return http(originalRequest);
|
||||
})
|
||||
.catch();
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
const notificationStore = useNotificationStore();
|
||||
notificationStore.push("Fehler", error.response.data, "error");
|
||||
if (error.toString().includes("Network Error")) {
|
||||
notificationStore.push("Netzwerkfehler", "Server nicht erreichbar!", "error");
|
||||
} else {
|
||||
notificationStore.push("Fehler", error.response.data, "error");
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
@ -99,4 +112,25 @@ function newEventSource(path: string) {
|
|||
});
|
||||
}
|
||||
|
||||
export { http, newEventSource, host };
|
||||
async function* streamingFetch(path: string, abort?: AbortController) {
|
||||
await refreshToken()
|
||||
.then(() => {})
|
||||
.catch(() => {});
|
||||
|
||||
const token = localStorage.getItem("accessToken");
|
||||
const response = await fetch(url + "/api" + path, {
|
||||
signal: abort?.signal,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
const reader = response.body?.getReader();
|
||||
while (true && reader) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
yield new TextDecoder().decode(value);
|
||||
}
|
||||
}
|
||||
|
||||
export { http, newEventSource, streamingFetch, host };
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { http, newEventSource } from "@/serverCom";
|
||||
import { http, newEventSource, streamingFetch } from "@/serverCom";
|
||||
import { useNewsletterStore } from "./newsletter";
|
||||
import type { AxiosResponse } from "axios";
|
||||
import type { EventSourcePolyfill } from "event-source-polyfill";
|
||||
import { useNotificationStore, type NotificationType } from "../../../notification";
|
||||
|
||||
export const useNewsletterPrintoutStore = defineStore("newsletterPrintout", {
|
||||
state: () => {
|
||||
|
@ -12,10 +13,10 @@ export const useNewsletterPrintoutStore = defineStore("newsletterPrintout", {
|
|||
printing: undefined as undefined | "loading" | "success" | "failed",
|
||||
sending: undefined as undefined | "loading" | "success" | "failed",
|
||||
sendingPreview: undefined as undefined | "loading" | "success" | "failed",
|
||||
pdfProgessSource: undefined as undefined | EventSourcePolyfill,
|
||||
mailProgessSource: undefined as undefined | EventSourcePolyfill,
|
||||
pdfSourceMessages: [] as Array<Object>,
|
||||
mailSourceMessages: [] as Array<Object>,
|
||||
pdfSourceMessages: [] as Array<{ kind: string; factor: string; [key: string]: string }>,
|
||||
mailSourceMessages: [] as Array<{ kind: string; factor: string; [key: string]: string }>,
|
||||
pdfPrintingAbort: undefined as undefined | AbortController,
|
||||
mailSendingAbort: undefined as undefined | AbortController,
|
||||
};
|
||||
},
|
||||
actions: {
|
||||
|
@ -63,6 +64,7 @@ export const useNewsletterPrintoutStore = defineStore("newsletterPrintout", {
|
|||
});
|
||||
},
|
||||
createNewsletterPrintout() {
|
||||
this.subscribePdfPrintingProgress();
|
||||
this.printing = "loading";
|
||||
const newsletterId = useNewsletterStore().activeNewsletter;
|
||||
if (newsletterId == null) return;
|
||||
|
@ -78,10 +80,12 @@ export const useNewsletterPrintoutStore = defineStore("newsletterPrintout", {
|
|||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
this.printing = undefined;
|
||||
this.pdfPrintingAbort?.abort();
|
||||
}, 1500);
|
||||
});
|
||||
},
|
||||
createNewsletterSend() {
|
||||
this.subscribeMailSendingProgress();
|
||||
this.sending = "loading";
|
||||
const newsletterId = useNewsletterStore().activeNewsletter;
|
||||
if (newsletterId == null) return;
|
||||
|
@ -96,32 +100,55 @@ export const useNewsletterPrintoutStore = defineStore("newsletterPrintout", {
|
|||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
this.sending = undefined;
|
||||
this.mailSendingAbort?.abort();
|
||||
}, 1500);
|
||||
});
|
||||
},
|
||||
subscribePdfPrintingProgress() {
|
||||
// const newsletterId = useNewsletterStore().activeNewsletter;
|
||||
// if (this.pdfProgessSource != undefined) return;
|
||||
// this.pdfProgessSource = newEventSource(`/admin/newsletter/${newsletterId}/printoutprogress`);
|
||||
// this.pdfProgessSource.onmessage = (event) => {
|
||||
// console.log("pdf", event);
|
||||
// };
|
||||
async subscribePdfPrintingProgress() {
|
||||
this.pdfSourceMessages = [];
|
||||
const newsletterId = useNewsletterStore().activeNewsletter;
|
||||
const notificationStore = useNotificationStore();
|
||||
this.pdfPrintingAbort = new AbortController();
|
||||
for await (let chunk of streamingFetch(
|
||||
`/admin/newsletter/${newsletterId}/printoutprogress`,
|
||||
this.pdfPrintingAbort
|
||||
)) {
|
||||
chunk.split("//").forEach((r) => {
|
||||
if (r.trim() != "") {
|
||||
let data = JSON.parse(r);
|
||||
this.pdfSourceMessages.push(data);
|
||||
let type: NotificationType = "info";
|
||||
let timeout = undefined;
|
||||
if (data.factor == "failed") {
|
||||
type = "error";
|
||||
timeout = 0;
|
||||
}
|
||||
notificationStore.push(`Druck: ${data.iteration}/${data.total}`, `${data.msg}`, type, timeout);
|
||||
}
|
||||
});
|
||||
this.fetchNewsletterPrintout();
|
||||
}
|
||||
},
|
||||
subscribeMailSendingProgress() {
|
||||
// const newsletterId = useNewsletterStore().activeNewsletter;
|
||||
// if (this.mailProgessSource != undefined) return;
|
||||
// this.mailProgessSource = newEventSource(`/admin/newsletter/${newsletterId}/sendprogress`);
|
||||
// this.mailProgessSource.onmessage = (event) => {
|
||||
// console.log("mail", event);
|
||||
// };
|
||||
},
|
||||
unsubscribePdfPrintingProgress() {
|
||||
this.pdfProgessSource?.close();
|
||||
this.pdfProgessSource = undefined;
|
||||
},
|
||||
unsubscribeMailSendingProgress() {
|
||||
this.mailProgessSource?.close();
|
||||
this.mailProgessSource = undefined;
|
||||
async subscribeMailSendingProgress() {
|
||||
this.mailSourceMessages = [];
|
||||
const newsletterId = useNewsletterStore().activeNewsletter;
|
||||
const notificationStore = useNotificationStore();
|
||||
this.mailSendingAbort = new AbortController();
|
||||
for await (let chunk of streamingFetch(`/admin/newsletter/${newsletterId}/sendprogress`, this.mailSendingAbort)) {
|
||||
chunk.split("//").forEach((r) => {
|
||||
if (r.trim() != "") {
|
||||
let data = JSON.parse(r);
|
||||
this.mailSourceMessages.push(data);
|
||||
let type: NotificationType = "info";
|
||||
let timeout = undefined;
|
||||
if (data.factor == "failed") {
|
||||
type = "error";
|
||||
timeout = 0;
|
||||
}
|
||||
notificationStore.push(`Mailversand: ${data.iteration}/${data.total}`, `${data.msg}`, type, timeout);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -38,7 +38,6 @@ export const useProtocolDecisionStore = defineStore("protocolDecision", {
|
|||
this.loading = "fetched";
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
this.loading = "failed";
|
||||
});
|
||||
},
|
||||
|
|
|
@ -98,30 +98,32 @@ export const useNavigationStore = defineStore("navigation", {
|
|||
settings: {
|
||||
mainTitle: "Einstellungen",
|
||||
main: [
|
||||
...(abilityStore.can("read", "settings", "qualification")
|
||||
? [{ key: "qualification", title: "Qualifikationen" }]
|
||||
: []),
|
||||
{ key: "divider1", title: "Mitgliederdaten" },
|
||||
...(abilityStore.can("read", "settings", "award") ? [{ key: "award", title: "Auszeichnungen" }] : []),
|
||||
...(abilityStore.can("read", "settings", "executive_position")
|
||||
? [{ key: "executive_position", title: "Vereinsämter" }]
|
||||
: []),
|
||||
...(abilityStore.can("read", "settings", "communication_type")
|
||||
? [{ key: "communication_type", title: "Kommunikationsarten" }]
|
||||
: []),
|
||||
...(abilityStore.can("read", "settings", "membership_status")
|
||||
? [{ key: "membership_status", title: "Mitgliedsstatus" }]
|
||||
: []),
|
||||
...(abilityStore.can("read", "settings", "calendar_type")
|
||||
? [{ key: "calendar_type", title: "Terminarten" }]
|
||||
...(abilityStore.can("read", "settings", "qualification")
|
||||
? [{ key: "qualification", title: "Qualifikationen" }]
|
||||
: []),
|
||||
...(abilityStore.can("read", "settings", "executive_position")
|
||||
? [{ key: "executive_position", title: "Vereinsämter" }]
|
||||
: []),
|
||||
{ key: "divider2", title: "Einstellungen" },
|
||||
...(abilityStore.can("read", "settings", "newsletter_config")
|
||||
? [{ key: "newsletter_config", title: "Newsletter Konfiguration" }]
|
||||
: []),
|
||||
...(abilityStore.can("read", "settings", "query") ? [{ key: "query_store", title: "Query Store" }] : []),
|
||||
...(abilityStore.can("read", "settings", "template") ? [{ key: "template", title: "Templates" }] : []),
|
||||
...(abilityStore.can("read", "settings", "template_usage")
|
||||
? [{ key: "template_usage", title: "Template-Verwendung" }]
|
||||
: []),
|
||||
...(abilityStore.can("read", "settings", "newsletter_config")
|
||||
? [{ key: "newsletter_config", title: "Newsletter Konfiguration" }]
|
||||
...(abilityStore.can("read", "settings", "calendar_type")
|
||||
? [{ key: "calendar_type", title: "Terminarten" }]
|
||||
: []),
|
||||
...(abilityStore.can("read", "settings", "query") ? [{ key: "query_store", title: "Query Store" }] : []),
|
||||
],
|
||||
},
|
||||
user: {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { defineStore } from "pinia";
|
||||
|
||||
export interface Notification {
|
||||
id: number;
|
||||
id: string;
|
||||
title: string;
|
||||
text: string;
|
||||
type: NotificationType;
|
||||
|
@ -19,7 +19,7 @@ export const useNotificationStore = defineStore("notification", {
|
|||
},
|
||||
actions: {
|
||||
push(title: string, text: string, type: NotificationType, timeout: number = 5000) {
|
||||
let id = Date.now();
|
||||
let id = `${Date.now()}_${Math.random()}`;
|
||||
this.notifications.push({
|
||||
id,
|
||||
title,
|
||||
|
@ -27,14 +27,16 @@ export const useNotificationStore = defineStore("notification", {
|
|||
type,
|
||||
indicator: false,
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.notifications[this.notifications.findIndex((n) => n.id === id)].indicator = true;
|
||||
}, 100);
|
||||
this.timeouts[id] = setTimeout(() => {
|
||||
this.revoke(id);
|
||||
}, timeout);
|
||||
if (timeout != 0) {
|
||||
setTimeout(() => {
|
||||
this.notifications[this.notifications.findIndex((n) => n.id === id)].indicator = true;
|
||||
}, 100);
|
||||
this.timeouts[id] = setTimeout(() => {
|
||||
this.revoke(id);
|
||||
}, timeout);
|
||||
}
|
||||
},
|
||||
revoke(id: number) {
|
||||
revoke(id: string) {
|
||||
this.notifications.splice(
|
||||
this.notifications.findIndex((n) => n.id === id),
|
||||
1
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div class="max-w-md w-full space-y-8 pb-20">
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<img src="/Logo.png" alt="LOGO" class="h-36" />
|
||||
<h2 class="text-center text-4xl font-extrabold text-gray-900">FF Admin</h2>
|
||||
<h2 class="text-center text-4xl font-extrabold text-gray-900">{{config.app_name_overwrite || "FF Admin"}}</h2>
|
||||
</div>
|
||||
|
||||
<form class="flex flex-col gap-2" @submit.prevent="login">
|
||||
|
@ -48,6 +48,7 @@ import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
|
|||
import FailureXMark from "@/components/FailureXMark.vue";
|
||||
import { resetAllPiniaStores } from "@/helpers/piniaReset";
|
||||
import FormBottomBar from "@/components/FormBottomBar.vue";
|
||||
import { config } from "@/config"
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<SidebarLayout>
|
||||
<template #sidebar>
|
||||
<SidebarTemplate mainTitle="Mein Account" topTitle="FF Admin" :showTopList="isOwner">
|
||||
<SidebarTemplate mainTitle="Mein Account" :topTitle="config.app_name_overwrite || 'FF Admin'" :showTopList="isOwner">
|
||||
<template v-if="isOwner" #topList>
|
||||
<RoutingLink
|
||||
title="Administration"
|
||||
|
@ -38,6 +38,7 @@ import SidebarTemplate from "@/templates/Sidebar.vue";
|
|||
import RoutingLink from "@/components/admin/RoutingLink.vue";
|
||||
import { RouterView } from "vue-router";
|
||||
import { useAbilityStore } from "@/stores/ability";
|
||||
import { config } from "@/config"
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
|
|
@ -16,13 +16,15 @@
|
|||
/>
|
||||
</template>
|
||||
<template #list>
|
||||
<RoutingLink
|
||||
v-for="item in activeNavigationObject.main"
|
||||
:key="item.key"
|
||||
:title="item.title"
|
||||
:link="{ name: `admin-${activeNavigation}-${item.key}` }"
|
||||
:active="activeLink == item.key"
|
||||
/>
|
||||
<div v-for="item in activeNavigationObject.main" :key="item.key">
|
||||
<RoutingLink
|
||||
v-if="!item.key.includes('divider')"
|
||||
:title="item.title"
|
||||
:link="{ name: `admin-${activeNavigation}-${item.key}` }"
|
||||
:active="activeLink == item.key"
|
||||
/>
|
||||
<p v-else class="pt-4 border-b border-gray-300">{{ item.title }}</p>
|
||||
</div>
|
||||
</template>
|
||||
</SidebarTemplate>
|
||||
</template>
|
||||
|
|
|
@ -61,6 +61,12 @@
|
|||
<SuccessCheckmark v-else-if="sendingPreview == 'success'" />
|
||||
<FailureXMark v-else-if="sendingPreview == 'failed'" />
|
||||
</button>
|
||||
<button v-if="pdfSourceMessages.length != 0" primary-outline class="!w-fit" @click="openPdfLogs">
|
||||
Druck Logs
|
||||
</button>
|
||||
<button v-if="mailSourceMessages.length != 0" primary-outline class="!w-fit" @click="openMailLogs">
|
||||
Versand Logs
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -84,13 +90,19 @@ export default defineComponent({
|
|||
newsletterId: String,
|
||||
},
|
||||
computed: {
|
||||
...mapState(useNewsletterPrintoutStore, ["printout", "loading", "printing", "sending", "sendingPreview"]),
|
||||
...mapState(useNewsletterPrintoutStore, [
|
||||
"printout",
|
||||
"loading",
|
||||
"printing",
|
||||
"sending",
|
||||
"sendingPreview",
|
||||
"mailSourceMessages",
|
||||
"pdfSourceMessages",
|
||||
]),
|
||||
...mapState(useAbilityStore, ["can"]),
|
||||
},
|
||||
mounted() {
|
||||
this.fetchNewsletterPrintout();
|
||||
this.subscribeMailSendingProgress();
|
||||
this.subscribePdfPrintingProgress();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useModalStore, ["openModal"]),
|
||||
|
@ -100,8 +112,6 @@ export default defineComponent({
|
|||
"fetchNewsletterPrintoutById",
|
||||
"createNewsletterMailPreview",
|
||||
"createNewsletterSend",
|
||||
"subscribeMailSendingProgress",
|
||||
"subscribePdfPrintingProgress",
|
||||
]),
|
||||
openPdfShow(filename?: string) {
|
||||
this.openModal(
|
||||
|
@ -122,6 +132,20 @@ export default defineComponent({
|
|||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
openPdfLogs() {
|
||||
this.openModal(
|
||||
markRaw(
|
||||
defineAsyncComponent(() => import("@/components/admin/club/newsletter/NewsletterPrintingProgressModal.vue"))
|
||||
)
|
||||
);
|
||||
},
|
||||
openMailLogs() {
|
||||
this.openModal(
|
||||
markRaw(
|
||||
defineAsyncComponent(() => import("@/components/admin/club/newsletter/NewsletterMailProgressModal.vue"))
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -139,7 +139,6 @@ export default defineComponent({
|
|||
if (!fromSave) this.loadDesign();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
this.loading = "failed";
|
||||
});
|
||||
},
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<template #sidebar>
|
||||
<SidebarTemplate mainTitle="Dokumentation">
|
||||
<template #list>
|
||||
<RoutingLink title="FF Admin" :link="{ name: 'docs-page', params: { page: 'ff-admin' } }" :active="page == 'ff-admin'" />
|
||||
<RoutingLink title="Admin" :link="{ name: 'docs-page', params: { page: 'ff-admin' } }" :active="page == 'ff-admin'" />
|
||||
<RoutingLink title="Mitgliederverwaltung" :link="{ name: 'docs-page', params: { page: 'member' } }" :active="page == 'member'" />
|
||||
<RoutingLink title="Kalendar" :link="{ name: 'docs-page', params: { page: 'calendar' } }" :active="page == 'calendar'" />
|
||||
<RoutingLink title="Newsletter-Versand" :link="{ name: 'docs-page', params: { page: 'newsletter' } }" :active="page == 'newsletter'" />
|
||||
|
|
|
@ -5,6 +5,7 @@ import vue from "@vitejs/plugin-vue";
|
|||
import vueDevTools from "vite-plugin-vue-devtools";
|
||||
import Markdown from "unplugin-vue-markdown/vite";
|
||||
import hljs from "highlight.js";
|
||||
import { VitePWA } from "vite-plugin-pwa";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
|
@ -34,6 +35,28 @@ export default defineConfig({
|
|||
});
|
||||
},
|
||||
}),
|
||||
VitePWA({
|
||||
registerType: "autoUpdate",
|
||||
injectRegister: "auto",
|
||||
includeAssets: ["favicon.png", "favicon.ico"],
|
||||
manifest: {
|
||||
name: "__APPNAMEOVERWRITE__",
|
||||
short_name: "__APPNAMEOVERWRITE__",
|
||||
theme_color: "#990b00",
|
||||
icons: [
|
||||
{
|
||||
src: "favicon.ico",
|
||||
sizes: "48x48",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "favicon.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
|
|