diff --git a/backend/go.mod b/backend/go.mod
index 6b12e18..47d3d4f 100644
--- a/backend/go.mod
+++ b/backend/go.mod
@@ -3,7 +3,7 @@ module backend
go 1.24.5
require (
- gitea.tecamino.com/paadi/access-handler v1.0.23
+ gitea.tecamino.com/paadi/access-handler v1.0.24
gitea.tecamino.com/paadi/memberDB v1.1.2
gitea.tecamino.com/paadi/tecamino-dbm v0.1.1
gitea.tecamino.com/paadi/tecamino-logger v0.2.1
diff --git a/backend/go.sum b/backend/go.sum
index 388c273..214ef07 100644
--- a/backend/go.sum
+++ b/backend/go.sum
@@ -1,5 +1,5 @@
-gitea.tecamino.com/paadi/access-handler v1.0.23 h1:bsQgGEyiV8nutZbkNKCDkHWhIpOUEMV22wePxQsn3QU=
-gitea.tecamino.com/paadi/access-handler v1.0.23/go.mod h1:wKsB5/Rvaj580gdg3+GbUf5V/0N00XN6cID+C/8135M=
+gitea.tecamino.com/paadi/access-handler v1.0.24 h1:wgCMJg1tgtPSCCVWLyNx1bT3B1N2NwHth0UJAH2nZIY=
+gitea.tecamino.com/paadi/access-handler v1.0.24/go.mod h1:wKsB5/Rvaj580gdg3+GbUf5V/0N00XN6cID+C/8135M=
gitea.tecamino.com/paadi/dbHandler v1.0.8 h1:ZWSBM/KFtLwTv2cBqwK1mOxWAxAfL0BcWEC3kJ9JALU=
gitea.tecamino.com/paadi/dbHandler v1.0.8/go.mod h1:y/xn/POJg1DO++67uKvnO23lJQgh+XFQq7HZCS9Getw=
gitea.tecamino.com/paadi/memberDB v1.1.2 h1:j/Tsr7JnzAkdOvgjG77TzTVBWd4vBrmEFzPXNpW7GYk=
diff --git a/src/assets/lang/de-CH.yaml b/src/assets/lang/de-CH.yaml
index 263f0ba..befcb39 100644
--- a/src/assets/lang/de-CH.yaml
+++ b/src/assets/lang/de-CH.yaml
@@ -15,6 +15,7 @@ lastVisit: Letscht Bsuech
search: Suechi
noDataAvailable: Keni Date
importCSV: importier CSV
+exportCSV: exportier CSV
selectMemberOptions: Wähle Mitglieder Optione
addNewMember: Neues Mitglied
csvOptions: CSV Optionen
@@ -118,3 +119,5 @@ responsibles: Verantwortliche
comment: Bemerkung
dark_mode: Dunkel-Modus
light_mode: Hell-Modus
+import: Import
+export: Export
diff --git a/src/assets/lang/de-DE.yaml b/src/assets/lang/de-DE.yaml
index 437fc8d..f66e307 100644
--- a/src/assets/lang/de-DE.yaml
+++ b/src/assets/lang/de-DE.yaml
@@ -15,6 +15,7 @@ lastVisit: Letzter Besuch
search: Suche
noDataAvailable: Keine Daten
importCSV: importiere CSV
+exportCSV: exportiere CSV
selectMemberOptions: Wähle Mitglieder Optionen
addNewMember: Neues Mitglied
csvOptions: CSV Optionen
@@ -118,3 +119,5 @@ responsibles: Verantwortliche
comment: Bemerkung
dark_mode: Dunkel-Modus
light_mode: Hell-Modus
+import: Import
+export: Export
diff --git a/src/assets/lang/en-US.yaml b/src/assets/lang/en-US.yaml
index 0da10e6..4baf7d4 100644
--- a/src/assets/lang/en-US.yaml
+++ b/src/assets/lang/en-US.yaml
@@ -15,6 +15,7 @@ lastVisit: Last Visit
search: search
noDataAvailable: No data available
importCSV: Import CSV
+exportCSV: Export CSV
selectMemberOptions: Select Member Options
addNewMember: Add new Member
csvOptions: CSV Options
@@ -118,3 +119,5 @@ responsibles: Responsibles
comment: Comment
dark_mode: Dark-Mode
light_mode: Light-Mode
+import: Import
+export: Export
diff --git a/src/components/RoleEditAllDialog.vue b/src/components/RoleEditAllDialog.vue
index 8ffd5cc..def3f8b 100644
--- a/src/components/RoleEditAllDialog.vue
+++ b/src/components/RoleEditAllDialog.vue
@@ -3,7 +3,7 @@
ref="dialog"
:header-title="newRole ? $t('addNewRole') : 'Edit ' + localRole.role"
:height="700"
- :width="500"
+ :width="700"
>
toggleBit(index, 2, val)"
>{{ i18n.global.t('delete') }}
+ toggleBit(index, 3, val)"
+ >{{ i18n.global.t('import') }}
+ toggleBit(index, 4, val)"
+ >{{ i18n.global.t('export') }}
@@ -47,6 +61,7 @@ const localPermission = ref(
props.permissions.map((e) => ({
name: e.name,
permission: e.permission ?? 0,
+ permissionNumber: e.name === 'members' ? 5 : 3,
})),
);
diff --git a/src/vueLib/login/userStore.ts b/src/vueLib/login/userStore.ts
index d865f67..d96196a 100644
--- a/src/vueLib/login/userStore.ts
+++ b/src/vueLib/login/userStore.ts
@@ -24,7 +24,7 @@ export const useUserStore = defineStore('user', {
};
},
isPermittedTo: (state: UserState) => {
- return (name: string, type: 'read' | 'write' | 'delete'): boolean => {
+ return (name: string, type: 'read' | 'write' | 'delete' | 'import' | 'export'): boolean => {
const permission = state.user?.permissions?.find((r: Permission) => r.name === name);
switch (type) {
case 'read':
@@ -33,6 +33,10 @@ export const useUserStore = defineStore('user', {
return permission?.permission ? (permission.permission & (1 << 1)) === 2 : false;
case 'delete':
return permission?.permission ? (permission.permission & (1 << 2)) === 4 : false;
+ case 'import':
+ return permission?.permission ? (permission.permission & (1 << 3)) === 8 : false;
+ case 'export':
+ return permission?.permission ? (permission.permission & (1 << 4)) === 16 : false;
}
};
},
diff --git a/src/vueLib/tables/members/MembersTable.ts b/src/vueLib/tables/members/MembersTable.ts
index 3af8ec6..0c5074f 100644
--- a/src/vueLib/tables/members/MembersTable.ts
+++ b/src/vueLib/tables/members/MembersTable.ts
@@ -4,6 +4,7 @@ import type { Member, Members } from 'src/vueLib/models/member';
import { useNotify } from 'src/vueLib/general/useNotify';
import { i18n } from 'boot/lang';
import { useResponsibleTable } from '../responsible/ResponsibleTable';
+import { appName } from 'src/vueLib/models/settings';
export function useMemberTable() {
const members = ref([]);
@@ -253,6 +254,47 @@ export function useMemberTable() {
});
}
+ function exportCsv() {
+ const comma = ';';
+ // Extract only columns that have a field (not icons/options)
+ const exportableColumns = columns.value.filter(
+ (col) => typeof col.field === 'string' && col.field !== 'cake' && col.field !== 'option',
+ ) as { field: keyof Member; label: string }[];
+
+ // Build CSV header row
+ const header = exportableColumns.map((col) => col.field).join(comma);
+
+ // Build CSV rows
+ const data = members.value.map((member) =>
+ exportableColumns
+ .map((col) => {
+ const value = member[col.field];
+ // handle nested objects (e.g. responsiblePerson)
+ if (typeof value === 'object' && value !== null) {
+ if ('firstName' in value && 'lastName' in value)
+ return `"${value.firstName} ${value.lastName}"`;
+ return `"${JSON.stringify(value)}"`;
+ }
+ return `"${value ?? ''}"`;
+ })
+ .join(comma),
+ );
+
+ // Combine into CSV string
+ const csv = [header, ...data].join('\n');
+
+ // Create blob and trigger download
+ const BOM = '\uFEFF';
+ const blob = new Blob([BOM + csv], { type: 'text/csv;charset=utf-8;' });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.setAttribute('download', i18n.global.t(appName.value) + '.csv');
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ }
+
return {
members,
responsibles,
@@ -263,5 +305,6 @@ export function useMemberTable() {
updateMembers,
isXDaysBeforeAnnualDate,
disableColumns,
+ exportCsv,
};
}
diff --git a/src/vueLib/tables/members/MembersTable.vue b/src/vueLib/tables/members/MembersTable.vue
index 8219b49..48733a2 100644
--- a/src/vueLib/tables/members/MembersTable.vue
+++ b/src/vueLib/tables/members/MembersTable.vue
@@ -43,7 +43,7 @@
{{ $t('selectMemberOptions') }}
{{ $t('importCSV') }}
+
+ {{ $t('exportCSV') }}
+