From aec741f094273b52f54cc2b7e5d2e68c8d831d05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Z=C3=BCrcher?= Date: Tue, 4 Nov 2025 10:59:56 +0100 Subject: [PATCH] add new event and attendance table with automatic now timestamp --- backend/go.mod | 2 +- backend/go.sum | 4 +- backend/main.go | 8 +- backend/members.dba | Bin 176128 -> 176128 bytes package.json | 2 +- quasar.config.ts | 2 +- src/assets/lang/de-CH.yaml | 24 ++ src/assets/lang/de-DE.yaml | 26 +- src/assets/lang/en-US.yaml | 24 ++ src/boot/auth.ts | 2 +- src/boot/restore-route.js | 32 +++ src/components/AddToEvent.vue | 91 ++++++ src/components/EditOneDialog.vue | 73 +++-- src/components/EventEditAllDialog.vue | 93 ++++++ src/components/MemberEditAllDialog.vue | 16 +- src/components/RoleEditAllDialog.vue | 2 +- src/components/UserEditAllDialog.vue | 2 +- src/layouts/MainLayout.vue | 12 +- src/pages/EventsTable.vue | 21 ++ src/pages/LoginPage.vue | 3 +- src/pages/MembersTable.vue | 12 +- src/pages/SettingsPage.vue | 8 +- src/router/routes.ts | 5 + .../checkboxes/CheckBoxGroupPermissions.vue | 1 + src/vueLib/checkboxes/permissions.ts | 4 +- src/vueLib/login/LoginMenu.vue | 10 +- src/vueLib/login/useLogin.ts | 10 +- src/vueLib/models/attendee.ts | 7 + src/vueLib/models/event.ts | 9 + src/vueLib/models/user.ts | 2 + src/vueLib/tables/attendees/AttendeesTable.ts | 77 +++++ .../tables/attendees/AttendeesTable.vue | 233 +++++++++++++++ src/vueLib/tables/events/EventsTable.ts | 79 ++++++ src/vueLib/tables/events/EventsTable.vue | 268 ++++++++++++++++++ src/vueLib/tables/members/MembersTable.ts | 219 +++++++------- src/vueLib/tables/members/MembersTable.vue | 139 +++++++-- src/vueLib/tables/roles/RoleTable.ts | 6 +- src/vueLib/tables/roles/RoleTable.vue | 23 +- src/vueLib/tables/users/UserTable.vue | 21 +- 39 files changed, 1343 insertions(+), 229 deletions(-) create mode 100644 src/boot/restore-route.js create mode 100644 src/components/AddToEvent.vue create mode 100644 src/components/EventEditAllDialog.vue create mode 100644 src/pages/EventsTable.vue create mode 100644 src/vueLib/models/attendee.ts create mode 100644 src/vueLib/models/event.ts create mode 100644 src/vueLib/tables/attendees/AttendeesTable.ts create mode 100644 src/vueLib/tables/attendees/AttendeesTable.vue create mode 100644 src/vueLib/tables/events/EventsTable.ts create mode 100644 src/vueLib/tables/events/EventsTable.vue diff --git a/backend/go.mod b/backend/go.mod index 2f46216..686a2c9 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -4,7 +4,7 @@ go 1.24.5 require ( gitea.tecamino.com/paadi/access-handler v1.0.12 - gitea.tecamino.com/paadi/memberDB v1.0.4 + gitea.tecamino.com/paadi/memberDB v1.0.11 gitea.tecamino.com/paadi/tecamino-dbm v0.1.1 gitea.tecamino.com/paadi/tecamino-logger v0.2.1 github.com/gin-contrib/cors v1.7.6 diff --git a/backend/go.sum b/backend/go.sum index 1d33899..2b89399 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -2,8 +2,8 @@ gitea.tecamino.com/paadi/access-handler v1.0.12 h1:lSmW0YrBJJvCqCg0ukTJHlFUNwd7q gitea.tecamino.com/paadi/access-handler v1.0.12/go.mod h1:w71lpnuu5MgAWG3oiI9vsY2dWi4njF/iPrM/xV/dbBQ= gitea.tecamino.com/paadi/dbHandler v1.0.4 h1:ctnaec0GDdtw3gRQdUISVDYLJ9x+vt50VW41OemfhD4= gitea.tecamino.com/paadi/dbHandler v1.0.4/go.mod h1:y/xn/POJg1DO++67uKvnO23lJQgh+XFQq7HZCS9Getw= -gitea.tecamino.com/paadi/memberDB v1.0.4 h1:2H7obSoMq4dW+VN8PsDOv1UK75hCtlF+NymU5NBGuw4= -gitea.tecamino.com/paadi/memberDB v1.0.4/go.mod h1:iLm7nunVRzqJK8CV4PJVuWIhgPlQjNIaeOkmtfK5fMg= +gitea.tecamino.com/paadi/memberDB v1.0.11 h1:Mwo86NVe7sLReRf+R4Z6hRxeEIgjjq6EPyiL1L1TlrA= +gitea.tecamino.com/paadi/memberDB v1.0.11/go.mod h1:iLm7nunVRzqJK8CV4PJVuWIhgPlQjNIaeOkmtfK5fMg= 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/tecamino-logger v0.2.1 h1:sQTBKYPdzn9mmWX2JXZBtGBvNQH7cuXIwsl4TD0aMgE= diff --git a/backend/main.go b/backend/main.go index 76b28eb..a1464bc 100644 --- a/backend/main.go +++ b/backend/main.go @@ -121,16 +121,22 @@ func main() { role := auth.Group("", accessHandler.AuthorizeRole("/api")) role.GET("/members", dbHandler.GetMember) + auth.GET("/events", dbHandler.GetEvent) auth.GET("/users", accessHandler.GetUser) auth.GET("/roles", accessHandler.GetRole) auth.POST("database/open", dbHandler.OpenDatabase) auth.POST("/members/add", dbHandler.AddNewMember) - auth.POST("/members/edit", dbHandler.EditMember) + auth.POST("/members/edit", dbHandler.UpdateMember) auth.POST("/members/delete", dbHandler.DeleteMember) auth.POST("/members/import/csv", dbHandler.ImportCSV) + auth.POST("/events/add", dbHandler.StartNewEvent) + auth.POST("/events/add/attendees", dbHandler.AddNewAttendees) + auth.POST("/events/delete/attendees", dbHandler.DeleteAttendee) + auth.POST("/events/delete", dbHandler.DeleteEvent) + auth.POST("/roles/add", accessHandler.AddRole) auth.POST("/roles/update", accessHandler.UpdateRole) auth.POST("/roles/delete", accessHandler.DeleteRole) diff --git a/backend/members.dba b/backend/members.dba index 5cd7f65c7ca331adb7948b4360fafd7dce605eeb..3bce73007e204fded1e4f0f99ea12ed31450e1d0 100644 GIT binary patch delta 15264 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 delta 20068 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 diff --git a/package.json b/package.json index 44332c7..a7c3c1f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lightcontrol", - "version": "1.0.4", + "version": "1.0.5", "description": "A Tecamino App", "productName": "Member Database", "author": "A. Zuercher", diff --git a/quasar.config.ts b/quasar.config.ts index 90459c8..d370c53 100644 --- a/quasar.config.ts +++ b/quasar.config.ts @@ -11,7 +11,7 @@ export default defineConfig((/* ctx */) => { // app boot file (/src/boot) // --> boot files are part of "main.js" // https://v2.quasar.dev/quasar-cli-vite/boot-files - boot: ['auth', 'axios', 'lang', 'quasar-global'], + boot: ['auth', 'axios', 'lang', 'quasar-global', 'restore-route'], // https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#css css: ['app.scss'], diff --git a/src/assets/lang/de-CH.yaml b/src/assets/lang/de-CH.yaml index 4612c97..4b35e87 100644 --- a/src/assets/lang/de-CH.yaml +++ b/src/assets/lang/de-CH.yaml @@ -61,7 +61,9 @@ emailIsRequired: Email isch erforderlich roleIsRequired: Rolle isch erforderlich permissions: Recht selectRoleOptions: Wähle Roue Optione +selectEventOptions: Wähle Verastautigs Optione addNewRole: Füeg neui Roue hinzue +addNewEvent: Füeg neui Verastautig hinzue veryWeak: sehr Schwach weak: Schwach fair: So so @@ -81,3 +83,25 @@ members: Mitglider attendanceTable: Anweseheits Tabelle excursionTable: Usflugs Tabelle updated: aktualisiert +events: Veranstalige +eventNameIsRequired: Verastatigsname isch erforderlich +eventName: Verastatigsname +attendees: Teilnähmer +now: Jetzt +addToEvent: Füge zu Veranstautig +add: Hinzuefüege +event: Verastautig +dateAndTime: Datum und Zyt +count: Anzau +selectAttendeesOptions: Wähle Teilnehmer Optionen +addNewAttendees: Füeg neue Teilnehmer hinzue +notAllRequiredFieldsFilled: Nid aui erforderliche Felder usgfüet +memberUpdated: Mitglied aktualisiert +membersUpdated: Mitglieder aktualisiert +deleteAttendee: Teilnehmer entfernt +deleteAttendees: Teilnehmer entfernt +deleteRoles: Rolen entfernt +attendeeAdded: Teilnämer hinzuegfüegt +attendeesAdded: Teilnämer hinzuegfüegt +eventAdded: Verastautig hinzuegfüegt +userUpdated: Benutzer aktualisiert diff --git a/src/assets/lang/de-DE.yaml b/src/assets/lang/de-DE.yaml index f45deb9..21709c0 100644 --- a/src/assets/lang/de-DE.yaml +++ b/src/assets/lang/de-DE.yaml @@ -60,8 +60,10 @@ userIsRequired: Benutzer ist erforderlich emailIsRequired: Email ist erforderlich roleIsRequired: Rolle ist erforderlich permissions: Rechte -selectRoleOptions: Wähle Rollen Option +selectRoleOptions: Wähle Rollen Optionen +selectEventOptions: Wähle Veranstaltungs Optionen addNewRole: Füge neue Rolle hinzu +addNewEvent: Füeg neue Veranstaltung hinzu veryWeak: sehr Schwach weak: Schwach fair: Ausreichend @@ -81,3 +83,25 @@ members: Mitglieder attendanceTable: Anwesenheits Tabelle excursionTable: Ausflugs Tabelle updated: aktualisiert +events: Veranstaltungen +eventNameIsRequired: Veranstaltungsname ist erforderlich +eventName: Veranstaltungsname +attendees: Teilnehmer +now: Jetzt +addToEvent: Füge zu Veranstaltung +add: Hinzufügen +event: Veranstaltung +dateAndTime: Datum und Zeit +count: Anzahl +selectAttendeesOptions: Wähle Teilnehmer Optionen +addNewAttendees: Füge neuen Teilnehmer hinzu +notAllRequiredFieldsFilled: Nicht alle erforderlichen Felder ausgefüllt +memberUpdated: Mitglied aktualisiert +membersUpdated: Mitglieder aktualisiert +deleteAttendee: Teilnehmer entfernt +deleteAttendees: Teilnehmer entfernt +deleteRoles: Rolen entfernt +attendeeAdded: Teilnehmer hinzugefügt +attendeesAdded: Teilnehmer hinzugefügt +eventAdded: Veranstaltung hinzugefügt +userUpdated: Benutzer aktualisiert diff --git a/src/assets/lang/en-US.yaml b/src/assets/lang/en-US.yaml index 706ffa8..bb70dcb 100644 --- a/src/assets/lang/en-US.yaml +++ b/src/assets/lang/en-US.yaml @@ -61,7 +61,9 @@ emailIsRequired: Email is required roleIsRequired: Role is required permissions: Permissions selectRoleOptions: Select Role Options +selectEventOptions: Select Event Options addNewRole: Add new Role +addNewEvent: Add new Event veryWeak: very Weak weak: Weak fair: Fair @@ -81,3 +83,25 @@ members: Members attendanceTable: Attendance Table excursionTable: Excursion Table updated: updated +events: Events +eventNameIsRequired: Eventname is required +eventName: Eventname +attendees: Attendees +now: Now +addToEvent: Add to event +add: Add +event: Event +dateAndTime: Date and Time +count: Count +selectAttendeesOptions: Select Attendees Options +addNewAttendees: Add new Attendee +notAllRequiredFieldsFilled: Not all required fields are filled in +memberUpdated: Member updated +membersUpdated: Members updated +deleteAttendee: Attendee deleted +deleteAttendees: Attendees deleted +deleteRoles: Roles deleted +attendeeAdded: Attendee added +attendeesAdded: Attendees added +eventAdded: Event added +userUpdated: User updated diff --git a/src/boot/auth.ts b/src/boot/auth.ts index 3bc8220..83db2aa 100644 --- a/src/boot/auth.ts +++ b/src/boot/auth.ts @@ -16,7 +16,7 @@ export default boot(async ({ app }) => { .then((resp) => { useStore .setUser({ id: resp.data.id, username: resp.data.username, role: resp.data.role }) - .catch((err) => console.log(err)); + .catch((err) => console.error(err)); login.refresh().catch((err) => console.error(err)); }) .catch(() => { diff --git a/src/boot/restore-route.js b/src/boot/restore-route.js new file mode 100644 index 0000000..b1cb255 --- /dev/null +++ b/src/boot/restore-route.js @@ -0,0 +1,32 @@ +import { boot } from 'quasar/wrappers'; +import { useUserStore } from 'src/vueLib/login/userStore'; + +export default boot(async ({ router }) => { + const userStore = useUserStore(); + + // Restore logic after router is ready but before navigation + router.isReady().then(() => { + const lastRoute = sessionStorage.getItem('lastRoute'); + const currentPath = router.currentRoute.value.fullPath; + + // Restore only if: + // - we’re on root ("/" or "/#/"), and + // - a last route exists, and + // - the user is authenticated + if ( + lastRoute && + ['/', '/#/', '/#/index.html'].includes(currentPath) && + userStore.isAuthenticated + ) { + router.replace(lastRoute).catch(() => {}); + } + }); + + // Save the route after every successful navigation + router.afterEach((to) => { + // Don't save login page as "last route" + if (to.path !== '/login' && to.path !== '/') { + sessionStorage.setItem('lastRoute', to.fullPath); + } + }); +}); diff --git a/src/components/AddToEvent.vue b/src/components/AddToEvent.vue new file mode 100644 index 0000000..b96c417 --- /dev/null +++ b/src/components/AddToEvent.vue @@ -0,0 +1,91 @@ + + + diff --git a/src/components/EditOneDialog.vue b/src/components/EditOneDialog.vue index 8da98e4..30425d3 100644 --- a/src/components/EditOneDialog.vue +++ b/src/components/EditOneDialog.vue @@ -1,17 +1,35 @@ @@ -22,6 +40,7 @@ import { ref } from 'vue'; import { appApi } from 'src/boot/axios'; import type { Member } from 'src/vueLib/models/member'; import { useNotify } from 'src/vueLib/general/useNotify'; +import { i18n } from 'src/boot/lang'; const dialog = ref(); const localMember = ref(); @@ -34,9 +53,6 @@ const props = defineProps({ type: String, required: true, }, - queryId: { - type: Boolean, - }, }); const emit = defineEmits(['update']); @@ -52,26 +68,24 @@ function open(label: string, field: string, member: Member) { } function save() { - let query = props.endpoint; - if (props.queryId) { - query += '?id=' + localMember.value.id; - } let payload = {}; if (value.value === localMember.value[localField.value]) { dialog.value.close(); return; } - payload = { - id: localMember.value.id, - [localField.value]: value.value, - }; + payload = [ + { + id: localMember.value.id, + [localField.value]: value.value, + }, + ]; appApi - .post(query, payload) - .then((resp) => { + .post(props.endpoint, payload) + .then(() => { emit('update'); - NotifyResponse(resp.data); + NotifyResponse(i18n.global.t('memberUpdated')); dialog.value.close(); }) .catch((err) => { @@ -79,5 +93,18 @@ function save() { }); } +function setTimeNow() { + const now = new Date(); + + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); // Months are 0-based + const day = String(now.getDate()).padStart(2, '0'); + const hours = String(now.getHours()).padStart(2, '0'); + const minutes = String(now.getMinutes()).padStart(2, '0'); + const seconds = String(now.getSeconds()).padStart(2, '0'); + + value.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; +} + defineExpose({ open }); diff --git a/src/components/EventEditAllDialog.vue b/src/components/EventEditAllDialog.vue new file mode 100644 index 0000000..bfd2fd9 --- /dev/null +++ b/src/components/EventEditAllDialog.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/src/components/MemberEditAllDialog.vue b/src/components/MemberEditAllDialog.vue index 8df03d2..bd06dab 100644 --- a/src/components/MemberEditAllDialog.vue +++ b/src/components/MemberEditAllDialog.vue @@ -90,7 +90,7 @@
- Save + {{ $t('save') }}
@@ -101,6 +101,7 @@ import { ref } from 'vue'; import { appApi } from 'src/boot/axios'; import type { Member } from 'src/vueLib/models/member'; import { useNotify } from 'src/vueLib/general/useNotify'; +import { i18n } from 'src/boot/lang'; const { NotifyResponse } = useNotify(); const dialog = ref(); @@ -158,18 +159,21 @@ function open(member: Member | null) { async function save() { const valid = await form.value.validate(); + if (!valid) { + NotifyResponse(i18n.global.t('notAllRequiredFieldsFilled'), 'error'); + return; + } - if (!valid) return; - - let query = 'members/edit?id=' + localMember.value.id; + let query = 'members/edit'; if (newMember.value) { query = 'members/add'; } - appApi - .post(query, JSON.stringify(localMember.value)) + await appApi + .post(query, JSON.stringify([localMember.value])) .then(() => { emit('update-member'); + NotifyResponse(i18n.global.t('memberUpdated')); dialog.value.close(); }) .catch((err) => NotifyResponse(err, 'error')); diff --git a/src/components/RoleEditAllDialog.vue b/src/components/RoleEditAllDialog.vue index 861de59..8d49ef8 100644 --- a/src/components/RoleEditAllDialog.vue +++ b/src/components/RoleEditAllDialog.vue @@ -27,7 +27,7 @@
- Save + {{ $t('save') }}
diff --git a/src/components/UserEditAllDialog.vue b/src/components/UserEditAllDialog.vue index c207159..e9f16ff 100644 --- a/src/components/UserEditAllDialog.vue +++ b/src/components/UserEditAllDialog.vue @@ -100,7 +100,7 @@
- Save + {{ $t('save') }}
diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue index 32d4d05..9b1407c 100644 --- a/src/layouts/MainLayout.vue +++ b/src/layouts/MainLayout.vue @@ -31,7 +31,17 @@ v-ripple @click="closeDrawer" > - Members + {{ $t('members') }} + + + {{ $t('events') }} diff --git a/src/pages/EventsTable.vue b/src/pages/EventsTable.vue new file mode 100644 index 0000000..9b1a84b --- /dev/null +++ b/src/pages/EventsTable.vue @@ -0,0 +1,21 @@ + + + diff --git a/src/pages/LoginPage.vue b/src/pages/LoginPage.vue index 6f5cd6d..246b8ac 100644 --- a/src/pages/LoginPage.vue +++ b/src/pages/LoginPage.vue @@ -21,6 +21,7 @@ onMounted(() => { const forwardToPage = async () => { await nextTick(); - await router.push('/members'); + const lastRoute = sessionStorage.getItem('lastRoute') || '/members'; + await router.push(lastRoute); }; diff --git a/src/pages/MembersTable.vue b/src/pages/MembersTable.vue index 5bc23cb..dfa800d 100644 --- a/src/pages/MembersTable.vue +++ b/src/pages/MembersTable.vue @@ -9,9 +9,6 @@ Get Selected -
- Click Me -
@@ -20,15 +17,12 @@ import MembersTable from 'src/vueLib/tables/members/MembersTable.vue'; import DialogFrame from 'src/vueLib/dialog/DialogFrame.vue'; import { ref } from 'vue'; import type { MemberDialog } from 'src/vueLib/tables/members/MembersTable.vue'; +import type { Members } from 'src/vueLib/models/member'; const dialog = ref(); const memberDialog = ref(); -const open = () => dialog.value?.open(); - -function getSelection() { - const selected = memberDialog.value?.getSelected(); - if (selected === undefined) return; - console.log(65, selected[0]?.id); +function getSelection(): Members { + return memberDialog.value?.getSelected() || []; } diff --git a/src/pages/SettingsPage.vue b/src/pages/SettingsPage.vue index c2b63e0..3005b3a 100644 --- a/src/pages/SettingsPage.vue +++ b/src/pages/SettingsPage.vue @@ -139,11 +139,9 @@ import { 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 { useLogin } from 'src/vueLib/login/useLogin'; import { useUserStore } from 'src/vueLib/login/userStore'; const { NotifyResponse } = useNotify(); -const { getUser } = useLogin(); const colorGroup = ref(false); const user = useUserStore(); @@ -185,8 +183,12 @@ function save() { localStorage.setItem('secondaryColor', settings.secondaryColor); localStorage.setItem('secondaryColorText', settings.secondaryColorText); + const tempuser = user.user; + if (tempuser) { + tempuser.settings = settings; + } appApi - .post('settings/update', { user: getUser()?.username, settings }) + .post('users/update', tempuser) .then((resp) => NotifyResponse(resp.data.message)) .catch((err) => NotifyResponse(err, 'error')); } diff --git a/src/router/routes.ts b/src/router/routes.ts index bb94809..628444d 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -18,6 +18,11 @@ const routes: RouteRecordRaw[] = [ component: () => import('pages/MembersTable.vue'), meta: { requiresAuth: true, requiresAdmin: true }, }, + { + path: 'events', + component: () => import('pages/EventsTable.vue'), + meta: { requiresAuth: true, requiresAdmin: true }, + }, { path: 'settings', component: () => import('pages/SettingsPage.vue'), diff --git a/src/vueLib/checkboxes/CheckBoxGroupPermissions.vue b/src/vueLib/checkboxes/CheckBoxGroupPermissions.vue index 51c3c8c..7ee77f9 100644 --- a/src/vueLib/checkboxes/CheckBoxGroupPermissions.vue +++ b/src/vueLib/checkboxes/CheckBoxGroupPermissions.vue @@ -2,6 +2,7 @@ + {{ permission }}
{{ $t(permission.name) }}
diff --git a/src/vueLib/checkboxes/permissions.ts b/src/vueLib/checkboxes/permissions.ts index 65b38ca..2a3cfff 100644 --- a/src/vueLib/checkboxes/permissions.ts +++ b/src/vueLib/checkboxes/permissions.ts @@ -26,8 +26,8 @@ export const defaultPermissions = [ permission: 0, }, { - name: 'attendanceTable', - label: i18n.global.t('attendanceTable'), + name: 'events', + label: i18n.global.t('events'), permission: 0, }, { diff --git a/src/vueLib/login/LoginMenu.vue b/src/vueLib/login/LoginMenu.vue index eb366de..19e4ad2 100644 --- a/src/vueLib/login/LoginMenu.vue +++ b/src/vueLib/login/LoginMenu.vue @@ -3,9 +3,7 @@ - {{ - currentUser?.username - }} + {{ currentUser?.username }} {{ loginText }} @@ -47,23 +45,23 @@ + + diff --git a/src/vueLib/tables/events/EventsTable.ts b/src/vueLib/tables/events/EventsTable.ts new file mode 100644 index 0000000..8fd0602 --- /dev/null +++ b/src/vueLib/tables/events/EventsTable.ts @@ -0,0 +1,79 @@ +import { appApi } from 'src/boot/axios'; +import { ref, computed } from 'vue'; +import type { Events } from 'src/vueLib/models/event'; +import { useNotify } from 'src/vueLib/general/useNotify'; +import { i18n } from 'boot/lang'; + +export function useEventTable() { + const Events = ref([]); + + const pagination = ref({ + sortBy: 'firstName', + 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: 'attendees', + align: 'center' as const, + label: i18n.global.t('attendees'), + field: 'attendees', + sortable: true, + }, + { + name: 'date', + align: 'left' as const, + label: i18n.global.t('dateAndTime'), + field: 'date', + sortable: true, + }, + { name: 'option', align: 'center' as const, label: '', field: 'option', icon: 'option' }, + ]); + + const { NotifyResponse } = useNotify(); + + const loading = ref(false); + + //updates Event list from database + function updateEvents() { + loading.value = true; + + appApi + .get('events') + .then((resp) => { + if (resp.data === null) { + Events.value = []; + return; + } + Events.value = resp.data as Events; + if (Events.value === null) { + Events.value = []; + return; + } + }) + + .catch((err) => { + NotifyResponse(err, 'error'); + }) + .finally(() => { + loading.value = false; + }); + } + + return { + Events, + pagination, + columns, + loading, + updateEvents, + }; +} diff --git a/src/vueLib/tables/events/EventsTable.vue b/src/vueLib/tables/events/EventsTable.vue new file mode 100644 index 0000000..b255915 --- /dev/null +++ b/src/vueLib/tables/events/EventsTable.vue @@ -0,0 +1,268 @@ + + + + + diff --git a/src/vueLib/tables/members/MembersTable.ts b/src/vueLib/tables/members/MembersTable.ts index 09aacb0..7705325 100644 --- a/src/vueLib/tables/members/MembersTable.ts +++ b/src/vueLib/tables/members/MembersTable.ts @@ -16,101 +16,122 @@ export function useMemberTable() { rowsPerPage: 20, }); - const columns = computed(() => [ - { name: 'cake', align: 'center' as const, label: '', field: 'cake', icon: 'cake' }, - { - name: 'firstName', - align: 'left' as const, - label: i18n.global.t('prename'), - field: 'firstName', - sortable: true, - }, - { - name: 'lastName', - align: 'left' as const, - label: i18n.global.t('lastName'), - field: 'lastName', - sortable: true, - }, - { - name: 'birthday', - align: 'left' as const, - label: i18n.global.t('birthday'), - field: 'birthday', - sortable: true, - }, - { - name: 'age', - align: 'left' as const, - label: i18n.global.t('age'), - field: 'age', - sortable: true, - }, - { - name: 'address', - align: 'left' as const, - label: i18n.global.t('address'), - field: 'address', - sortable: true, - }, - { - name: 'town', - align: 'left' as const, - label: i18n.global.t('town'), - field: 'town', - sortable: true, - }, - { - name: 'zip', - align: 'left' as const, - label: i18n.global.t('zipCode'), - field: 'zip', - sortable: true, - }, - { - name: 'phone', - align: 'left' as const, - label: i18n.global.t('phone'), - field: 'phone', - sortable: true, - }, - { - name: 'email', - align: 'left' as const, - label: i18n.global.t('email'), - field: 'email', - sortable: true, - }, - { - name: 'group', - align: 'left' as const, - label: i18n.global.t('group'), - field: 'group', - sortable: true, - }, - { - name: 'responsiblePerson', - align: 'left' as const, - label: i18n.global.t('responsible'), - field: 'responsiblePerson', - sortable: true, - }, - { - name: 'firstVisit', - align: 'left' as const, - label: i18n.global.t('firstVisit'), - field: 'firstVisit', - sortable: true, - }, - { - name: 'lastVisit', - align: 'left' as const, - label: i18n.global.t('lastVisit'), - field: 'lastVisit', - sortable: true, - }, - { name: 'option', align: 'center' as const, label: '', field: 'option', icon: 'option' }, - ]); + //add enabling of each columns + const enabledColumns = ref>({ + cake: true, + firstName: true, + lastName: true, + birthday: true, + age: true, + address: false, + town: true, + zip: true, + phone: true, + email: true, + group: true, + responsiblePerson: true, + firstVisit: true, + lastVisit: true, + option: true, + }); + + const columns = computed(() => + [ + { name: 'cake', align: 'center' as const, label: '', field: 'cake', icon: 'cake' }, + { + name: 'firstName', + align: 'left' as const, + label: i18n.global.t('prename'), + field: 'firstName', + sortable: true, + }, + { + name: 'lastName', + align: 'left' as const, + label: i18n.global.t('lastName'), + field: 'lastName', + sortable: true, + }, + { + name: 'birthday', + align: 'left' as const, + label: i18n.global.t('birthday'), + field: 'birthday', + sortable: true, + }, + { + name: 'age', + align: 'left' as const, + label: i18n.global.t('age'), + field: 'age', + sortable: true, + }, + { + name: 'address', + align: 'left' as const, + label: i18n.global.t('address'), + field: 'address', + sortable: true, + }, + { + name: 'town', + align: 'left' as const, + label: i18n.global.t('town'), + field: 'town', + sortable: true, + }, + { + name: 'zip', + align: 'left' as const, + label: i18n.global.t('zipCode'), + field: 'zip', + sortable: true, + }, + { + name: 'phone', + align: 'left' as const, + label: i18n.global.t('phone'), + field: 'phone', + sortable: true, + }, + { + name: 'email', + align: 'left' as const, + label: i18n.global.t('email'), + field: 'email', + sortable: true, + }, + { + name: 'group', + align: 'left' as const, + label: i18n.global.t('group'), + field: 'group', + sortable: true, + }, + { + name: 'responsiblePerson', + align: 'left' as const, + label: i18n.global.t('responsible'), + field: 'responsiblePerson', + sortable: true, + }, + { + name: 'firstVisit', + align: 'left' as const, + label: i18n.global.t('firstVisit'), + field: 'firstVisit', + sortable: true, + }, + { + name: 'lastVisit', + align: 'left' as const, + label: i18n.global.t('lastVisit'), + field: 'lastVisit', + sortable: true, + }, + { name: 'option', align: 'center' as const, label: '', field: 'option', icon: 'option' }, + ].filter((c) => enabledColumns.value[c.name]), + ); const { NotifyResponse } = useNotify(); @@ -197,6 +218,13 @@ export function useMemberTable() { }); } + function disableColumns(...columns: string[]) { + columns.forEach((col) => { + if (col in enabledColumns.value) { + enabledColumns.value[col] = false; + } + }); + } return { members, pagination, @@ -205,5 +233,6 @@ export function useMemberTable() { getRowClass, updateMembers, isXDaysBeforeAnnualDate, + disableColumns, }; } diff --git a/src/vueLib/tables/members/MembersTable.vue b/src/vueLib/tables/members/MembersTable.vue index 7ef8f07..8958598 100644 --- a/src/vueLib/tables/members/MembersTable.vue +++ b/src/vueLib/tables/members/MembersTable.vue @@ -9,7 +9,7 @@ :no-data-label="$t('noDataAvailable')" :loading-label="$t('loading')" :rows-per-page-label="$t('recordsPerPage')" - :selected-rows-label="(val) => val + $t('recordSelected')" + :selected-rows-label="(val) => val + ' ' + $t('recordSelected')" :rows="members" :columns="columns" row-key="id" @@ -24,7 +24,7 @@ @@ -79,9 +92,9 @@ @@ -100,10 +113,7 @@