This commit is contained in:
commit
c9e6cf37e9
199
frontend/app.vue
199
frontend/app.vue
@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<div v-if="!userLoaded" class="bg-neutral-100 dark:bg-neutral-900 text-primary h-screen flex justify-center items-center">
|
||||
<div v-if="!userStore.userLoaded" class="bg-neutral-100 dark:bg-neutral-900 text-primary h-screen flex justify-center items-center">
|
||||
<div>
|
||||
<Icon size="4em" name="ion:load-c" class="animate-spin" />
|
||||
<h2 class="mt-2 font-bold">Loading...</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="">
|
||||
<div v-if="isAuthenticated" class="bg-neutral-100 dark:bg-neutral-900 text-primary h-screen flex flex-col">
|
||||
<div v-if="userStore.isAuthenticated" class="bg-neutral-100 dark:bg-neutral-900 text-primary h-screen flex flex-col">
|
||||
<LayoutTopbar />
|
||||
<div class="overflow-y-auto pt-3">
|
||||
<NuxtPage />
|
||||
@ -20,35 +20,12 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { doc, getFirestore, serverTimestamp, writeBatch, collection, getDoc } from "firebase/firestore";
|
||||
import { getAuth, onAuthStateChanged, signInWithEmailAndPassword } from "firebase/auth";
|
||||
import { getMessaging, getToken, onMessage } from "firebase/messaging";
|
||||
import { PushNotifications } from '@capacitor/push-notifications';
|
||||
import { Device } from '@capacitor/device';
|
||||
import { useToast } from 'vue-toastification'
|
||||
|
||||
const db = getFirestore()
|
||||
const route = useRoute()
|
||||
const toast = useToast()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const isAuthenticated = ref(false)
|
||||
const user = ref('frikandel')
|
||||
const registrationToken = ref('')
|
||||
const auth = ref(null)
|
||||
const userLoaded = ref(false)
|
||||
const userData = ref(null)
|
||||
const userPersons = ref([])
|
||||
const userAllPersons = ref([])
|
||||
const calEvents = ref([])
|
||||
const news = ref(null)
|
||||
const contestTimes = ref(null)
|
||||
const competitors = ref([])
|
||||
const users = ref([])
|
||||
|
||||
const messaging = ref(null)
|
||||
|
||||
onMounted(() => {
|
||||
auth.value = getAuth()
|
||||
onMounted(async () => {
|
||||
userStore.init()
|
||||
|
||||
Device.getInfo().then(info => {
|
||||
if (info.platform === 'ios') document.getElementsByClassName('top-right')[0].classList.add('toastios')
|
||||
@ -57,176 +34,12 @@ onMounted(() => {
|
||||
if (process.client) {
|
||||
if ('serviceWorker' in navigator && window.isSecureContext) {
|
||||
Device.getInfo().then(info => {
|
||||
if (info.platform === 'web') registerSW()
|
||||
if (info.platform === 'web') registerServiceWorker()
|
||||
else document.getElementsByClassName('top-right')[0].classList.add('toastios')
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
onAuthStateChanged(auth.value, async (usr) => {
|
||||
if (usr) {
|
||||
user.value = usr
|
||||
|
||||
let docRef = doc(db, "users", user.value.uid);
|
||||
let docSnap = await getDoc(docRef);
|
||||
|
||||
if (docSnap.exists()) {
|
||||
const data = docSnap.data()
|
||||
userData.value = data
|
||||
getPersons(userData.value.relatiecodes)
|
||||
} else {
|
||||
setTimeout(() => window.location.reload(true), 1000)
|
||||
}
|
||||
|
||||
if (!userData.value.sendNews && route.path === '/news/newmessage') navigateTo('/')
|
||||
if (!userData.value.admin && route.path.startsWith('/settings/admin')) navigateTo('/')
|
||||
|
||||
isAuthenticated.value = true
|
||||
|
||||
logDeviceInfo()
|
||||
|
||||
} else {
|
||||
isAuthenticated.value = false
|
||||
user.value = null
|
||||
userData.value = null
|
||||
userPersons.value = []
|
||||
}
|
||||
|
||||
userLoaded.value = true
|
||||
})
|
||||
})
|
||||
|
||||
const getPersons = async (persons) => {
|
||||
userPersons.value = [];
|
||||
|
||||
for (let i = 0; i < persons.length; i++) {
|
||||
const docRef = doc(db, "ledenlijst", persons[i]);
|
||||
const docSnap = await getDoc(docRef);
|
||||
|
||||
if (docSnap.exists()) {
|
||||
userPersons.value.push(docSnap.data())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const setupNotifications = () => {
|
||||
console.log('Initializing HomePage');
|
||||
|
||||
// Request permission to use push notifications
|
||||
// iOS will prompt user and return if they granted permission or not
|
||||
// Android will just grant without prompting
|
||||
PushNotifications.requestPermissions().then(result => {
|
||||
if (result.receive === 'granted') {
|
||||
// Register with Apple / Google to receive push via APNS/FCM
|
||||
PushNotifications.register()
|
||||
} else {
|
||||
toast.error('Error tijdens het registrenen van push notificaties')
|
||||
}
|
||||
});
|
||||
|
||||
// On success, we should be able to receive notifications
|
||||
PushNotifications.addListener('registration',
|
||||
async (token) => {
|
||||
// alert('Push registration success, token: ' + token.value);
|
||||
registrationToken.value = token
|
||||
|
||||
const { error } = await useFetch('/api/subscribetotopic', {
|
||||
method: 'post',
|
||||
body: { topic: 'all', registrationToken: token.value }
|
||||
})
|
||||
|
||||
if (error.value) {
|
||||
console.log(error.value)
|
||||
return toast.error('Error tijdens het krijgen van relateicodes')
|
||||
}
|
||||
|
||||
console.log('Subscribed to topic!')
|
||||
}
|
||||
);
|
||||
|
||||
// Some issue with our setup and push will not work
|
||||
PushNotifications.addListener('registrationError',
|
||||
(error) => {
|
||||
toast.error('Error tijdens het registreren van push notificaties')
|
||||
|
||||
console.log(error)
|
||||
}
|
||||
);
|
||||
|
||||
// Show us the notification payload if the app is open on our device
|
||||
PushNotifications.addListener('pushNotificationReceived',
|
||||
(notification) => {
|
||||
|
||||
toast.info(`${notification.title}`, {
|
||||
onClick: () => navigateTo('/news')
|
||||
})
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
// Method called when tapping on a notification
|
||||
PushNotifications.addListener('pushNotificationActionPerformed',
|
||||
(notification) => {
|
||||
navigateTo('/news')
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const registerFCM = () => {
|
||||
getToken(messaging.value, { vapidKey: 'BI7l3nyGV6wJcFh7wrwmQ42W7RSXl46bmhXZJmDd4P-0K_JFP0ClTqjO-rr5H5DXBbmVR4kXwxFpUlo_d6cUy4Q' }).then(async (currentToken) => {
|
||||
if (currentToken) {
|
||||
console.log(currentToken)
|
||||
|
||||
const { error} = await useFetch('/api/subscribetotopic', {
|
||||
method: 'post',
|
||||
body: { topic: 'all', registrationToken: currentToken }
|
||||
})
|
||||
|
||||
if (error.value) {
|
||||
console.log(error.value)
|
||||
return toast.error('Error tijdens het registreren van push notifications')
|
||||
}
|
||||
|
||||
registrationToken.value = currentToken
|
||||
console.log('Subscribed to topic!')
|
||||
} else {
|
||||
// Show permission request UI
|
||||
console.log('No registration token available. Request permission to generate one.');
|
||||
// ...
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log('An error occurred while retrieving token. ', err);
|
||||
// ...
|
||||
});
|
||||
}
|
||||
|
||||
const registerSW = () => {
|
||||
messaging.value = getMessaging()
|
||||
navigator.serviceWorker.register('/sw.js').catch(e => alert(e));
|
||||
|
||||
onMessage(messaging.value, (payload) => {
|
||||
console.log('Message received. ', payload);
|
||||
|
||||
toast.info(`${payload.notification.title}`, {
|
||||
onClick: () => navigateTo('/news')
|
||||
})
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
const logDeviceInfo = async () => {
|
||||
const info = await Device.getInfo();
|
||||
|
||||
console.log(info);
|
||||
|
||||
if (info.platform !== 'web') setupNotifications()
|
||||
else registerFCM()
|
||||
};
|
||||
|
||||
const ledenlijst = ref([])
|
||||
|
||||
provide('firebase', { db, ledenlijst, isAuthenticated, user, userData, userPersons, auth, users, userAllPersons, getPersons, calEvents, news, registrationToken, contestTimes, competitors, registrationToken })
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
@ -43,17 +43,17 @@
|
||||
<script setup>
|
||||
import { useToast } from 'vue-toastification'
|
||||
import { createUserWithEmailAndPassword, signInWithEmailAndPassword, sendPasswordResetEmail } from "firebase/auth";
|
||||
import { doc, setDoc } from "firebase/firestore";
|
||||
|
||||
|
||||
const { auth, db, userAllPersons } = inject('firebase')
|
||||
import { doc, setDoc, getFirestore } from "firebase/firestore";
|
||||
|
||||
const toast = useToast()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const showPassword = ref(false)
|
||||
const creatingAccount = ref(false)
|
||||
const disableButtons = ref(false)
|
||||
|
||||
const db = getFirestore()
|
||||
|
||||
const form = ref({
|
||||
email: '',
|
||||
password: '',
|
||||
@ -63,8 +63,7 @@ const form = ref({
|
||||
|
||||
const submitLoginForm = () => {
|
||||
disableButtons.value = true
|
||||
|
||||
signInWithEmailAndPassword(auth.value, form.value.email, form.value.password)
|
||||
signInWithEmailAndPassword(userStore.auth, form.value.email, form.value.password)
|
||||
.then(() => disableButtons.value = false)
|
||||
.catch(async (error) => {
|
||||
|
||||
@ -101,9 +100,9 @@ const submitCreateForm = () => {
|
||||
|
||||
disableButtons.value = true
|
||||
|
||||
createUserWithEmailAndPassword(auth.value, form.value.email, form.value.newPassword)
|
||||
createUserWithEmailAndPassword(userStore.auth, form.value.email, form.value.newPassword)
|
||||
.then(async (userCredential) => {
|
||||
const idToken = await auth.value.currentUser.getIdToken(true)
|
||||
const idToken = await userStore.auth.currentUser.getIdToken(true)
|
||||
const { error, data } = await useFetch('/api/getrelatiecodes', {
|
||||
method: 'post',
|
||||
body: { email: form.value.email, token: idToken }
|
||||
@ -132,7 +131,7 @@ const submitCreateForm = () => {
|
||||
}
|
||||
})
|
||||
|
||||
userAllPersons.value = data.value.persons
|
||||
userStore.userAllPersons = data.value.persons
|
||||
|
||||
if (data.value.relatiecodes.length > 1) {
|
||||
return navigateTo('/settings/config/managerelatiecodes')
|
||||
@ -150,7 +149,7 @@ const submitCreateForm = () => {
|
||||
}
|
||||
|
||||
const forgotPassword = () => {
|
||||
sendPasswordResetEmail(auth.value, form.value.email)
|
||||
sendPasswordResetEmail(userStore.auth, form.value.email)
|
||||
.then(() => {
|
||||
toast.info('Wachtwoord vergeten email verstuurd!')
|
||||
})
|
||||
|
@ -17,7 +17,7 @@
|
||||
<Icon size="1.8em" name="ion:settings-sharp" />
|
||||
<span>Settings</span>
|
||||
</NuxtLink>
|
||||
<NuxtLink v-if="userPersons[0] && userPersons.filter(a => a.wedstrijdteam).length > 0" to="/wedstrijd" class="flex flex-col items-center hover:cursor-pointer drop-shadow" :class="route.path.startsWith('/wedstrijd') ? 'text-primary' : ''">
|
||||
<NuxtLink v-if="userStore.userPersons[0] && userStore.userPersons.filter(a => a.wedstrijdteam).length > 0" to="/wedstrijd" class="flex flex-col items-center hover:cursor-pointer drop-shadow" :class="route.path.startsWith('/wedstrijd') ? 'text-primary' : ''">
|
||||
<Icon size="1.8em" name="ion:podium-outline" />
|
||||
<span>Wedstrijd</span>
|
||||
</NuxtLink>
|
||||
@ -29,7 +29,7 @@
|
||||
import { Device } from '@capacitor/device';
|
||||
|
||||
const route = useRoute()
|
||||
const { userData, userPersons } = inject('firebase')
|
||||
const userStore = useUserStore()
|
||||
|
||||
const platform = ref(null)
|
||||
|
||||
|
123
frontend/composables/notifications.js
Normal file
123
frontend/composables/notifications.js
Normal file
@ -0,0 +1,123 @@
|
||||
import { getMessaging, getToken, onMessage } from 'firebase/messaging'
|
||||
import { PushNotifications } from '@capacitor/push-notifications';
|
||||
import { Device } from '@capacitor/device';
|
||||
|
||||
|
||||
export const setupIosNotifications = () => {
|
||||
const userStore = useUserStore()
|
||||
const toast = useToast()
|
||||
|
||||
// Request permission to use push notifications
|
||||
// iOS will prompt user and return if they granted permission or not
|
||||
// Android will just grant without prompting
|
||||
PushNotifications.requestPermissions().then(result => {
|
||||
if (result.receive === 'granted') {
|
||||
// Register with Apple / Google to receive push via APNS/FCM
|
||||
PushNotifications.register()
|
||||
} else {
|
||||
toast.error('Error tijdens het registrenen van push notificaties')
|
||||
}
|
||||
});
|
||||
|
||||
// On success, we should be able to receive notifications
|
||||
PushNotifications.addListener('registration',
|
||||
async (token) => {
|
||||
// alert('Push registration success, token: ' + token.value);
|
||||
userStore.registrationToken = token
|
||||
|
||||
const { error } = await useFetch('/api/subscribetotopic', {
|
||||
method: 'post',
|
||||
body: { topic: 'all', registrationToken: token.value }
|
||||
})
|
||||
|
||||
if (error.value) {
|
||||
console.log(error.value)
|
||||
return toast.error('Error tijdens het krijgen van relateicodes')
|
||||
}
|
||||
|
||||
console.log('Subscribed to topic!')
|
||||
}
|
||||
);
|
||||
|
||||
// Some issue with our setup and push will not work
|
||||
PushNotifications.addListener('registrationError',
|
||||
(error) => {
|
||||
toast.error('Error tijdens het registreren van push notificaties')
|
||||
|
||||
console.log(error)
|
||||
}
|
||||
);
|
||||
|
||||
// Show us the notification payload if the app is open on our device
|
||||
PushNotifications.addListener('pushNotificationReceived',
|
||||
(notification) => {
|
||||
|
||||
toast.info(`${notification.title}`, {
|
||||
onClick: () => navigateTo('/news')
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
// Method called when tapping on a notification
|
||||
PushNotifications.addListener('pushNotificationActionPerformed',
|
||||
(notification) => {
|
||||
navigateTo('/news')
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export const setupWebNotifications = () => {
|
||||
const messaging = getMessaging()
|
||||
const userStore = useUserStore()
|
||||
const toast = useToast()
|
||||
|
||||
getToken(messaging, { vapidKey: 'BI7l3nyGV6wJcFh7wrwmQ42W7RSXl46bmhXZJmDd4P-0K_JFP0ClTqjO-rr5H5DXBbmVR4kXwxFpUlo_d6cUy4Q' }).then(async (currentToken) => {
|
||||
if (currentToken) {
|
||||
console.log(currentToken)
|
||||
|
||||
const { error} = await useFetch('/api/subscribetotopic', {
|
||||
method: 'post',
|
||||
body: { topic: 'all', registrationToken: currentToken }
|
||||
})
|
||||
|
||||
if (error.value) {
|
||||
console.log(error.value)
|
||||
return toast.error('Error tijdens het registreren van push notifications')
|
||||
}
|
||||
|
||||
userStore.registrationToken = currentToken
|
||||
console.log('Subscribed to topic!')
|
||||
} else {
|
||||
// Show permission request UI
|
||||
console.log('No registration token available. Request permission to generate one.');
|
||||
// ...
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log('An error occurred while retrieving token. ', err);
|
||||
// ...
|
||||
});
|
||||
}
|
||||
|
||||
export const setupNotifications = async () => {
|
||||
const info = await Device.getInfo();
|
||||
|
||||
console.log(info);
|
||||
|
||||
if (info.platform !== 'web') setupIosNotifications()
|
||||
else setupWebNotifications()
|
||||
}
|
||||
|
||||
export const registerServiceWorker = () => {
|
||||
const messaging = getMessaging()
|
||||
const toast = useToast()
|
||||
|
||||
navigator.serviceWorker.register('/sw.js').catch(e => alert(e));
|
||||
|
||||
onMessage(messaging, (payload) => {
|
||||
console.log('Message received. ', payload);
|
||||
|
||||
toast.info(`${payload.notification.title}`, {
|
||||
onClick: () => navigateTo('/news')
|
||||
})
|
||||
});
|
||||
}
|
6
frontend/composables/useToast.js
Normal file
6
frontend/composables/useToast.js
Normal file
@ -0,0 +1,6 @@
|
||||
import { useToast } from 'vue-toastification'
|
||||
|
||||
export default () => {
|
||||
const toast = useToast()
|
||||
return toast
|
||||
}
|
@ -1,12 +1,16 @@
|
||||
// https://v3.nuxtjs.org/api/configuration/nuxt.config
|
||||
export default defineNuxtConfig({
|
||||
ssr: false,
|
||||
imports: {
|
||||
dirs: ['stores']
|
||||
},
|
||||
modules: [
|
||||
'@nuxtjs/tailwindcss',
|
||||
'nuxt-icon',
|
||||
'@vueuse/nuxt',
|
||||
'@nuxtjs/robots',
|
||||
'@nuxtjs/plausible',
|
||||
[ '@pinia/nuxt', { autoImports: [ 'defineStore' ]} ],
|
||||
],
|
||||
plausible: {
|
||||
domain: 'wrbapp.xeovalyte.com',
|
||||
|
4097
frontend/package-lock.json
generated
4097
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,8 @@
|
||||
{
|
||||
"private": true,
|
||||
"overrides": {
|
||||
"vue": "latest"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
@ -8,27 +11,29 @@
|
||||
"postinstall": "nuxt prepare"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capacitor/cli": "^4.6.3",
|
||||
"@capacitor/cli": "^4.7.1",
|
||||
"@nuxtjs/plausible": "^0.2.0",
|
||||
"@nuxtjs/tailwindcss": "^6.3.1",
|
||||
"@nuxtjs/tailwindcss": "^6.6.0",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@vueuse/core": "^9.12.0",
|
||||
"@vueuse/nuxt": "^9.12.0",
|
||||
"nuxt": "^3.2.0",
|
||||
"nuxt-icon": "^0.2.11"
|
||||
"@vueuse/core": "^9.13.0",
|
||||
"@vueuse/nuxt": "^9.13.0",
|
||||
"nuxt": "^3.3.1",
|
||||
"nuxt-icon": "^0.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@capacitor/core": "^4.6.3",
|
||||
"@capacitor/core": "^4.7.1",
|
||||
"@capacitor/device": "^4.1.0",
|
||||
"@capacitor/ios": "^4.6.3",
|
||||
"@capacitor/ios": "^4.7.1",
|
||||
"@capacitor/push-notifications": "^4.1.2",
|
||||
"@formkit/nuxt": "^1.0.0-beta.11-c95e605",
|
||||
"@nuxtjs/robots": "^3.0.0",
|
||||
"@vueuse/components": "^9.12.0",
|
||||
"@vueuse/firebase": "^9.12.0",
|
||||
"@vueuse/shared": "^9.12.0",
|
||||
"firebase": "^9.17.1",
|
||||
"@pinia/nuxt": "^0.4.7",
|
||||
"@vueuse/components": "^9.13.0",
|
||||
"@vueuse/firebase": "^9.13.0",
|
||||
"@vueuse/shared": "^9.13.0",
|
||||
"firebase": "^9.18.0",
|
||||
"firebase-admin": "^11.5.0",
|
||||
"pinia": "^2.0.33",
|
||||
"vue-toastification": "^2.0.0-rc.5"
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md">
|
||||
<div v-if="calEvents[0]" class="flex flex-col gap-3">
|
||||
<div v-for="(event, index) in calEvents" :key="index">
|
||||
<div v-if="calendarStore.events[0]" class="flex flex-col gap-3">
|
||||
<div v-for="(event, index) in calendarStore.events" :key="index">
|
||||
<div class="item container flex flex-col">
|
||||
<h2 class="">{{ longEventDate(event.date) }}</h2>
|
||||
<p class="whitespace-pre overflow-x-auto font-bold text-xl">{{ event.description}}</p>
|
||||
@ -20,12 +20,24 @@ definePageMeta({
|
||||
title: 'Agenda'
|
||||
})
|
||||
|
||||
const { userPersons, calEvents } = inject('firebase')
|
||||
const userStore = useUserStore()
|
||||
const calendarStore = useCalendarStore()
|
||||
|
||||
onMounted(() => {
|
||||
if (!calEvents[0]) getCalendarEvents([...new Set(userPersons.value.map(a => a.groups.join()).join().split(','))])
|
||||
getEvents()
|
||||
})
|
||||
|
||||
const getEvents = () => {
|
||||
|
||||
if (userStore.userPersons[0]) {
|
||||
const groups = [...new Set(userStore.userPersons.map(a => a.groups.join()).join().split(','))]
|
||||
|
||||
calendarStore.getEvents(groups)
|
||||
} else {
|
||||
setTimeout(() => { getEvents() }, 50)
|
||||
}
|
||||
}
|
||||
|
||||
const longEventDate = (eventDate) => {
|
||||
const date = new Date(eventDate)
|
||||
|
||||
@ -39,58 +51,5 @@ const longEventDate = (eventDate) => {
|
||||
}
|
||||
)}
|
||||
|
||||
const getCalendarEvents = async (group) => {
|
||||
|
||||
if (!group[0]) return setTimeout(() => { getCalendarEvents([...new Set(userPersons.value.map(a => a.groups.join()).join().split(','))])}, 50)
|
||||
|
||||
const events = []
|
||||
let fridayEvents = []
|
||||
let saturdayEvents = []
|
||||
let matchEvents = []
|
||||
|
||||
if (group.includes('Zaterdag')){
|
||||
let data = await fetch('https://www.googleapis.com/calendar/v3/calendars/c_astg0d0auheip5o4269v1qvv3g@group.calendar.google.com/events?key=AIzaSyBLmxWNEnbkiW_0c2UrsHMbxXv73dX-KYw')
|
||||
if (!data.ok){
|
||||
throw Error('No data available')
|
||||
}
|
||||
|
||||
const jsonEvents = await data.json()
|
||||
saturdayEvents = jsonEvents.items
|
||||
} if (group.includes('Vrijdag')){
|
||||
let data = await fetch('https://www.googleapis.com/calendar/v3/calendars/c_u3895n01jt8qnusm7f6pmqjb0k%40group.calendar.google.com/events?key=AIzaSyBLmxWNEnbkiW_0c2UrsHMbxXv73dX-KYw')
|
||||
if (!data.ok){
|
||||
throw Error('No data available')
|
||||
}
|
||||
|
||||
const jsonEvents = await data.json()
|
||||
fridayEvents = jsonEvents.items
|
||||
} if (group.includes('Wedstrijd')){
|
||||
|
||||
let data = await fetch('https://www.googleapis.com/calendar/v3/calendars/c_c2296iboq07n24galuobeesovs@group.calendar.google.com/events?key=AIzaSyBLmxWNEnbkiW_0c2UrsHMbxXv73dX-KYw')
|
||||
if (!data.ok){
|
||||
throw Error('No data available')
|
||||
}
|
||||
|
||||
const jsonEvents = await data.json()
|
||||
matchEvents = jsonEvents.items
|
||||
}
|
||||
|
||||
const allEvents = [...fridayEvents, ...saturdayEvents, ...matchEvents]
|
||||
|
||||
const now = new Date()
|
||||
now.setHours(0, 0, 0, 0)
|
||||
|
||||
const sortedEvents = allEvents.sort((a, b) => new Date(a.start.dateTime) - new Date(b.start.dateTime))
|
||||
const filteredEvents = sortedEvents.filter((event) => new Date(event.start.dateTime) > now )
|
||||
|
||||
filteredEvents.forEach(element => {
|
||||
events.push({
|
||||
description: element.description,
|
||||
date: new Date(element.start.dateTime)
|
||||
})
|
||||
});
|
||||
|
||||
calEvents.value = events
|
||||
}
|
||||
|
||||
</script>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex flex-col justify-center items-center px-2 overflow-hidden">
|
||||
<h1 class="font-bold text-3xl text-center mt-6 mb-3">Reddingsbrigade Waddinxveen</h1>
|
||||
<h2 class="text-xl text-center mb-12">{{ userPersons.map(a => a.fullName).join(', ')}}</h2>
|
||||
<h2 class="text-xl text-center mb-12">{{ userStore.userPersons.map(a => a.fullName).join(', ')}}</h2>
|
||||
<div class="container w-full max-w-md">
|
||||
<NuxtLink to="/news" class="rounded-t item-hover py-2 flex items-center">
|
||||
<span>Nieuws</span>
|
||||
@ -26,6 +26,5 @@
|
||||
definePageMeta({
|
||||
title: 'Home',
|
||||
})
|
||||
|
||||
const { userPersons } = inject('firebase')
|
||||
const userStore = useUserStore()
|
||||
</script>
|
||||
|
@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md">
|
||||
<div v-if="loadedNews && news" class="flex flex-col gap-3">
|
||||
<NuxtLink to="/news/newmessage" v-if="userData.sendNews" class="item-hover border-dashed border-2 container text-center font-bold text-xl border-neutral-500 mb-3">
|
||||
<div v-if="newsStore.loaded && newsStore.news" class="flex flex-col gap-3">
|
||||
<NuxtLink to="/news/newmessage" v-if="userStore.userData.sendNews" class="item-hover border-dashed border-2 container text-center font-bold text-xl border-neutral-500 mb-3">
|
||||
Nieuw Bericht
|
||||
</NuxtLink>
|
||||
<div v-if="news[0]" v-for="(item, index) in news" :key="index">
|
||||
<div v-if="newsStore.news[0]" v-for="(item, index) in newsStore.news" :key="index">
|
||||
<div class="item container flex flex-col relative">
|
||||
<h3 class="text-sm">{{ longEventDate(item.date.toDate()) }}</h3>
|
||||
<h2 class="text-2xl font-bold">{{ item.title }}</h2>
|
||||
<p>{{ item.description }}</p>
|
||||
<Icon v-if="userData.sendNews" @click="deleteItem(item, index)" size="1.5em" name="ion:trash-sharp" class="absolute top-3 right-3 hover:cursor-pointer text-red-500" />
|
||||
<Icon v-if="userStore.userData.sendNews" @click="newsStore.deleteNews(item, index)" size="1.5em" name="ion:trash-sharp" class="absolute top-3 right-3 hover:cursor-pointer text-red-500" />
|
||||
</div>
|
||||
</div>
|
||||
<h2 v-else class="font-bold text-center text-xl mt-3">
|
||||
@ -24,22 +24,15 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getDocs, collection, deleteDoc, doc } from 'firebase/firestore'
|
||||
import { useToast } from 'vue-toastification'
|
||||
|
||||
definePageMeta({
|
||||
title: 'Nieuws'
|
||||
})
|
||||
|
||||
const { news, userData, db } = inject('firebase')
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
const loadedNews = ref(false)
|
||||
const userStore = useUserStore()
|
||||
const newsStore = useNewsStore()
|
||||
|
||||
onMounted(() => {
|
||||
if (!news.value) getNews()
|
||||
else loadedNews.value = true
|
||||
newsStore.getNews()
|
||||
})
|
||||
|
||||
const longEventDate = (eventDate) => {
|
||||
@ -55,38 +48,4 @@ const longEventDate = (eventDate) => {
|
||||
}
|
||||
)}
|
||||
|
||||
const getNews = async () => {
|
||||
news.value = []
|
||||
try {
|
||||
const querySnapshot = await getDocs(collection(db, "news"));
|
||||
querySnapshot.forEach((doc) => {
|
||||
let data = doc.data()
|
||||
data.id = doc.id
|
||||
news.value.push(data)
|
||||
});
|
||||
|
||||
news.value.sort((a, b) => b.date.seconds - a.date.seconds)
|
||||
|
||||
loadedNews.value = true
|
||||
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
const deleteItem = async (item, index) => {
|
||||
if (!item.id) return toast.error('Refresh eerst voordat je dit bericht kan verwijderen')
|
||||
|
||||
try {
|
||||
news.value.splice(index, 1)
|
||||
|
||||
await deleteDoc(doc(db, "news", item.id));
|
||||
toast.success('Bericht verwijderd')
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
|
||||
toast.error('Error tijdens bericht verwijderen')
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
@ -22,66 +22,35 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { addDoc, collection, serverTimestamp, Timestamp } from 'firebase/firestore'
|
||||
import { useToast } from 'vue-toastification'
|
||||
|
||||
definePageMeta({
|
||||
title: 'Nieuw Bericht',
|
||||
key: 'back'
|
||||
})
|
||||
|
||||
const { news, db, auth } = inject('firebase')
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
const newsStore = useNewsStore()
|
||||
|
||||
const disableButtons = ref(false)
|
||||
|
||||
const sendNews = async () => {
|
||||
try {
|
||||
disableButtons.value = true
|
||||
|
||||
await newsStore.send(form.value)
|
||||
|
||||
disableButtons.value = false
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
disableButtons.value = false
|
||||
|
||||
toast.error('Error tijdens versturen bericht')
|
||||
}
|
||||
}
|
||||
|
||||
const form = ref({
|
||||
title: '',
|
||||
description: '',
|
||||
topic: ''
|
||||
})
|
||||
|
||||
const sendNews = async () => {
|
||||
disableButtons.value = true
|
||||
|
||||
try {
|
||||
const idToken = await auth.value.currentUser.getIdToken(true)
|
||||
console.log(idToken)
|
||||
|
||||
const { error } = await useFetch('/api/sendmessage', {
|
||||
method: 'post',
|
||||
body: { title: form.value.title, body: form.value.description, token: idToken, topic: form.value.topic }
|
||||
})
|
||||
|
||||
if (error.value) {
|
||||
console.log(error.value)
|
||||
return toast.error('Error tijdens het versturen van het bericht')
|
||||
}
|
||||
|
||||
await addDoc(collection(db, "news"), {
|
||||
title: form.value.title,
|
||||
description: form.value.description,
|
||||
date: serverTimestamp()
|
||||
});
|
||||
|
||||
if (news.value) {
|
||||
news.value.unshift({
|
||||
title: form.value.title,
|
||||
description: form.value.description,
|
||||
date: Timestamp.now()
|
||||
})
|
||||
}
|
||||
toast.success('Bericht is verstuurd')
|
||||
|
||||
navigateTo('/news')
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
|
||||
toast.error('Error tijdens het berict sturen')
|
||||
}
|
||||
|
||||
|
||||
disableButtons.value = false
|
||||
}
|
||||
</script>
|
||||
|
@ -42,7 +42,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { doc, getDocs, collection, writeBatch, updateDoc, setDoc } from "firebase/firestore";
|
||||
import { doc, getDocs, collection, writeBatch, updateDoc, setDoc, getFirestore } from "firebase/firestore";
|
||||
import { useToast } from 'vue-toastification'
|
||||
|
||||
definePageMeta({
|
||||
@ -50,11 +50,11 @@ definePageMeta({
|
||||
key: 'back'
|
||||
})
|
||||
|
||||
const { db, ledenlijst, users } = inject('firebase')
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
const usersStore = useUsersStore()
|
||||
const modelData = ref(null)
|
||||
const db = getFirestore()
|
||||
|
||||
const file = ref(null)
|
||||
const disableButtons = ref(false)
|
||||
@ -63,18 +63,7 @@ const newLedenlijst = ref([])
|
||||
const showModel = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
if (!ledenlijst.value.length) {
|
||||
try {
|
||||
const querySnapshot = await getDocs(collection(db, "ledenlijst"));
|
||||
querySnapshot.forEach((doc) => {
|
||||
ledenlijst.value.push(doc.data())
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
ledenlijst.value.sort((a, b) => a.fullName.localeCompare(b.fullName))
|
||||
}
|
||||
usersStore.getLedenlijst()
|
||||
})
|
||||
|
||||
const handleModel = (lid) => {
|
||||
@ -88,7 +77,7 @@ const handleModel = (lid) => {
|
||||
|
||||
const submitModelForm = async () => {
|
||||
disableButtons.value = true
|
||||
ledenlijst.value.filter(a => a.relatiecode === modelData.value.relatiecode)[0].wedstrijdteam = modelData.value.wedstrijdteam
|
||||
usersStore.ledenlijst.filter(a => a.relatiecode === modelData.value.relatiecode)[0].wedstrijdteam = modelData.value.wedstrijdteam
|
||||
|
||||
if (modelData.value.wedstrijdteam === modelData.value.oldWedstrijdteam) {
|
||||
disableButtons.value = false
|
||||
@ -123,7 +112,7 @@ const submitModelForm = async () => {
|
||||
}
|
||||
|
||||
const filteredLedenlijst = computed(() => {
|
||||
return ledenlijst.value.filter(lid => lid.fullName.toLowerCase().includes(searchTerm.value.toLowerCase()))
|
||||
return usersStore.ledenlijst.filter(lid => lid.fullName.toLowerCase().includes(searchTerm.value.toLowerCase()))
|
||||
})
|
||||
|
||||
const handleFileChanged = (event) => {
|
||||
@ -224,7 +213,7 @@ const uploadLedenlijst = async () => {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
ledenlijst.value = newLedenlijst.value
|
||||
usersStore.ledenlijst = newLedenlijst.value
|
||||
|
||||
updateUsers()
|
||||
}
|
||||
|
@ -44,7 +44,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getDocs, collection, doc, updateDoc } from 'firebase/firestore'
|
||||
import { getDocs, collection, doc, updateDoc, getFirestore } from 'firebase/firestore'
|
||||
import { useToast } from 'vue-toastification'
|
||||
|
||||
definePageMeta({
|
||||
@ -52,8 +52,9 @@ definePageMeta({
|
||||
key: 'back'
|
||||
})
|
||||
|
||||
const { users, db } = inject('firebase')
|
||||
const toast = useToast()
|
||||
const db = getFirestore()
|
||||
const usersStore = useUsersStore()
|
||||
|
||||
const searchTerm = ref('')
|
||||
const disableButtons = ref(false)
|
||||
@ -62,16 +63,7 @@ const showModel = ref(false)
|
||||
const modelData = ref({})
|
||||
|
||||
onMounted(async () => {
|
||||
if (!users.value.length) {
|
||||
try {
|
||||
const querySnapshot = await getDocs(collection(db, "users"));
|
||||
querySnapshot.forEach((doc) => {
|
||||
users.value.push(doc.data())
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
usersStore.getUsers()
|
||||
})
|
||||
|
||||
const handleModel = (user) => {
|
||||
@ -84,7 +76,7 @@ const handleModel = (user) => {
|
||||
}
|
||||
|
||||
const filteredUsers = computed(() => {
|
||||
return users.value.filter(user => user.email.toLowerCase().includes(searchTerm.value.toLowerCase()))
|
||||
return usersStore.users.filter(user => user.email.toLowerCase().includes(searchTerm.value.toLowerCase()))
|
||||
})
|
||||
|
||||
const submitModelForm = async () => {
|
||||
|
@ -32,10 +32,9 @@ definePageMeta({
|
||||
key: 'back'
|
||||
})
|
||||
|
||||
const { user, auth } = inject('firebase')
|
||||
|
||||
const toast = useToast()
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const form = ref({
|
||||
oldPassword: '',
|
||||
@ -57,12 +56,12 @@ const savePassword = () => {
|
||||
disableButtons.value = true
|
||||
|
||||
const credential = EmailAuthProvider.credential(
|
||||
user.value.email,
|
||||
userStore.user.email,
|
||||
form.value.oldPassword
|
||||
)
|
||||
|
||||
reauthenticateWithCredential(auth.value.currentUser, credential).then(() => {
|
||||
updatePassword(auth.value.currentUser, form.value.newPassword).then(() => {
|
||||
reauthenticateWithCredential(userStore.auth.currentUser, credential).then(() => {
|
||||
updatePassword(userStore.auth.currentUser, form.value.newPassword).then(() => {
|
||||
toast.success('Wachtwoord is veranderd')
|
||||
|
||||
navigateTo('/settings')
|
||||
@ -81,7 +80,6 @@ const savePassword = () => {
|
||||
|
||||
console.log(error)
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const showPassword = ref(false)
|
||||
|
@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md">
|
||||
<div v-if="userAllPersons.length !== 0 && userPersons.length !== 0" class="flex flex-col gap-3">
|
||||
<div v-for="person in userAllPersons" :key="person.relatiecode">
|
||||
<div @click="updateCheckbox(person)" class="item container flex flex-wrap" :class="person.relatiecode === userPersons[0].relatiecode ? 'bg-neutral-200 dark:bg-neutral-850 text-neutral-400 dark:text-neutral-500 hover:cursor-not-allowed' : 'hover:cursor-pointer'">
|
||||
<input v-model="person.checked" :disabled="person.relatiecode === userPersons[0].relatiecode" class="checkbox my-auto mr-3 disabled:bg-neutral-300 disabled:hover:text-neutral-300 dark:disabled:bg-neutral-600 dark:disabled:hover:text-neutral-600 disabled:hover:cursor-not-allowed" type="checkbox">
|
||||
<div v-if="userStore.userAllPersons.length !== 0 && userStore.userPersons.length !== 0" class="flex flex-col gap-3">
|
||||
<div v-for="person in userStore.userAllPersons" :key="person.relatiecode">
|
||||
<div @click="updateCheckbox(person)" class="item container flex flex-wrap" :class="person.relatiecode === userStore.userPersons[0].relatiecode ? 'bg-neutral-200 dark:bg-neutral-850 text-neutral-400 dark:text-neutral-500 hover:cursor-not-allowed' : 'hover:cursor-pointer'">
|
||||
<input v-model="person.checked" :disabled="person.relatiecode === userStore.userPersons[0].relatiecode" class="checkbox my-auto mr-3 disabled:bg-neutral-300 disabled:hover:text-neutral-300 dark:disabled:bg-neutral-600 dark:disabled:hover:text-neutral-600 disabled:hover:cursor-not-allowed" type="checkbox">
|
||||
<span><b>{{ person.fullName }}</b></span>
|
||||
</div>
|
||||
</div>
|
||||
@ -20,7 +20,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { updateDoc, doc } from 'firebase/firestore'
|
||||
import { updateDoc, doc, getFirestore } from 'firebase/firestore'
|
||||
import { useToast } from 'vue-toastification'
|
||||
|
||||
const router = useRouter()
|
||||
@ -30,23 +30,14 @@ definePageMeta({
|
||||
key: 'back'
|
||||
})
|
||||
|
||||
const { user, userAllPersons, userPersons, db, getPersons, auth } = inject('firebase')
|
||||
const toast = useToast()
|
||||
const userStore = useUserStore()
|
||||
const db = getFirestore()
|
||||
|
||||
const buttonsDisabled = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
if (userAllPersons.value.length === 0) {
|
||||
getAllPersons()
|
||||
} else {
|
||||
userAllPersons.value.forEach(person => {
|
||||
if (userPersons.value.map(a => a.relatiecode).includes(person.relatiecode)) {
|
||||
person.checked = true
|
||||
} else {
|
||||
person.checked = false
|
||||
}
|
||||
})
|
||||
}
|
||||
userStore.getAllPersons()
|
||||
})
|
||||
|
||||
const save = async () => {
|
||||
@ -54,52 +45,25 @@ const save = async () => {
|
||||
|
||||
const newRelatiecodes = []
|
||||
|
||||
userAllPersons.value.forEach(person => {
|
||||
userStore.userAllPersons.forEach(person => {
|
||||
if (person.checked) {
|
||||
newRelatiecodes.push(person.relatiecode)
|
||||
}
|
||||
})
|
||||
|
||||
await updateDoc(doc(db, "users", user.value.uid), {
|
||||
await updateDoc(doc(db, "users", userStore.user.uid), {
|
||||
relatiecodes: newRelatiecodes
|
||||
})
|
||||
|
||||
getPersons(newRelatiecodes)
|
||||
userStore.getPersons(newRelatiecodes)
|
||||
|
||||
buttonsDisabled.value = false
|
||||
navigateTo('/settings')
|
||||
}
|
||||
|
||||
const updateCheckbox = (person) => {
|
||||
if (person.relatiecode === userPersons.value[0].relatiecode) return;
|
||||
if (person.relatiecode === userStore.userPersons[0].relatiecode) return;
|
||||
|
||||
person.checked = !person.checked
|
||||
}
|
||||
|
||||
const getAllPersons = async () => {
|
||||
if (userPersons.value.length === 0) return setTimeout(() => getAllPersons(), 50)
|
||||
|
||||
const idToken = await auth.value.currentUser.getIdToken(true)
|
||||
|
||||
|
||||
const { data: response, error } = await useFetch('/api/getrelatiecodes', {
|
||||
method: 'post',
|
||||
body: { email: user.value.email, token: idToken }
|
||||
})
|
||||
|
||||
if (error.value) {
|
||||
console.log(error.value)
|
||||
return toast.error('Error tijdens het krijgen van relateicodes')
|
||||
}
|
||||
|
||||
response.value.persons.forEach(person => {
|
||||
if (userPersons.value.map(a => a.relatiecode).includes(person.relatiecode)) {
|
||||
person.checked = true
|
||||
} else {
|
||||
person.checked = false
|
||||
}
|
||||
})
|
||||
|
||||
userAllPersons.value = response.value.persons
|
||||
}
|
||||
</script>
|
||||
|
@ -4,19 +4,19 @@
|
||||
<h1 class="text-xl ml-2 font-bold">Info</h1>
|
||||
<div class="container">
|
||||
<div class="item">
|
||||
Email: <b>{{ user.email }}</b>
|
||||
Email: <b>{{ userStore.user.email }}</b>
|
||||
</div>
|
||||
<div class="divider" />
|
||||
<div class="item">
|
||||
Personen: <b>{{ userPersons.map(a => a.fullName).join(', ')}}</b>
|
||||
Personen: <b>{{ userStore.userPersons.map(a => a.fullName).join(', ')}}</b>
|
||||
</div>
|
||||
<div class="divider" />
|
||||
<div class="item">
|
||||
Groepen: <b>{{ groups.join(', ') }}</b>
|
||||
</div>
|
||||
<div v-if="userPersons.map(a => a.diploma).filter(n => n !== '').join('')" class="divider" />
|
||||
<div v-if="userPersons.map(a => a.diploma).filter(n => n !== '').join('')" class="item">
|
||||
Diploma: <b>{{ userPersons.map(a => a.diploma).filter(n => n !== '').join(', ')}}</b>
|
||||
<div v-if="userStore.userPersons.map(a => a.diploma).filter(n => n !== '').join('')" class="divider" />
|
||||
<div v-if="userStore.userPersons.map(a => a.diploma).filter(n => n !== '').join('')" class="item">
|
||||
Diploma: <b>{{ userStore.userPersons.map(a => a.diploma).filter(n => n !== '').join(', ')}}</b>
|
||||
</div>
|
||||
<div class="divider" />
|
||||
<NuxtLink to="/settings/moreinfo" class="item-hover py-2 rounded-t flex items-center">
|
||||
@ -43,7 +43,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="userData.admin">
|
||||
<div v-if="userStore.userData.admin">
|
||||
<h1 class="text-xl ml-2 font-bold">Admin</h1>
|
||||
<div class="container">
|
||||
<NuxtLink to="/settings/admin/users" class="rounded-t item-hover py-2 flex items-center">
|
||||
@ -70,14 +70,14 @@ definePageMeta({
|
||||
title: 'Settings'
|
||||
})
|
||||
|
||||
const { auth, userData, userPersons, user } = inject('firebase')
|
||||
const userStore = useUserStore()
|
||||
|
||||
const groups = computed(() => {
|
||||
return [...new Set(userPersons.value.map(a => a.groups.join()).join().split(','))]
|
||||
return [...new Set(userStore.userPersons.map(a => a.groups.join()).join().split(','))]
|
||||
})
|
||||
|
||||
const logout = () => {
|
||||
signOut(auth.value)
|
||||
signOut(userStore.auth)
|
||||
.catch((error) => {
|
||||
console.log(error)
|
||||
})
|
||||
|
@ -15,11 +15,11 @@
|
||||
<h1 class="text-xl ml-2 font-bold"></h1>
|
||||
<div class="container">
|
||||
<div class="item break-words ">
|
||||
Registration Token: <b>{{ registrationToken }}</b>
|
||||
Registration Token: <b>{{ userStore.registrationToken }}</b>
|
||||
</div>
|
||||
<div class="divider" />
|
||||
<div class="item break-words ">
|
||||
User ID: <b>{{ userData.id }}</b>
|
||||
User ID: <b>{{ userStore.userData.id }}</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -32,5 +32,5 @@ definePageMeta({
|
||||
key: 'back'
|
||||
})
|
||||
|
||||
const { registrationToken, userData } = inject('firebase')
|
||||
const userStore = useUserStore()
|
||||
</script>
|
||||
|
@ -1,23 +0,0 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md text-default text-sm">
|
||||
<h2 class="text-xl font-bold">
|
||||
Privacy
|
||||
</h2>
|
||||
<p>
|
||||
Gegevens binnen deze app worden gebruikt voor de interne organisatie. Via Google analytics wordt bijgehouden welke schermen het meest worden gebruikt. Daarnaast maken wij gebruik van Firebase voor het anoniem verzamelen van gegevens omtrent crashes, bugs en het gebruik van de app.
|
||||
</p>
|
||||
<h2 class="text-xl font-bold">
|
||||
AVG
|
||||
</h2>
|
||||
<p>
|
||||
Sinds 25 mei 2018 is de Algemene verordening gegevensbescherming (AVG) van toepassing waardoor elke vereniging helder moet maken wat zij doen om de privacy van persoonsgegevens te waarborgen. U kunt <a href="https://www.reddingsbrigadewaddinxveen.nl/Doc/201809%20-%20Privacyverklaring%20WRB.pdf" class="underline">hier</a> onze privacy verklaring vinden.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
title: 'Privacybeleid',
|
||||
key: 'back'
|
||||
})
|
||||
</script>
|
@ -5,7 +5,7 @@
|
||||
|
||||
<label class="font-bold text-default">Deelnemer</label>
|
||||
<select :disabled="modelData.edit" required v-model="modelData.relatiecode" class="input dark:bg-neutral-700 bg-neutral-300 mb-5">
|
||||
<option v-if="!modelData.edit" v-for="user in competitors.filter(x => !contest.events[modelData.event].competitors.map(y => y.relatiecode).includes(x.relatiecode))" :value="user.relatiecode">{{ user.name}} ({{ user.relatiecode }})</option>
|
||||
<option v-if="!modelData.edit" v-for="user in contestStore.competitors.filter(x => !contest.events[modelData.event].competitors.map(y => y.relatiecode).includes(x.relatiecode))" :value="user.relatiecode">{{ user.name}} ({{ user.relatiecode }})</option>
|
||||
<option v-else v-for="user in competitors" :value="user.relatiecode">{{ user.name}} ({{ user.relatiecode }})</option>
|
||||
</select>
|
||||
|
||||
@ -45,7 +45,7 @@
|
||||
|
||||
<label class="font-bold">Onderdelen</label>
|
||||
<div class="flex flex-col gap-y-3">
|
||||
<div v-if="competitors" v-for="event in contest.events" class="container p-2">
|
||||
<div v-if="contestStore.competitors[0]" v-for="event in contest.events" class="container p-2">
|
||||
<div @click="event.open = !event.open" class="flex hover:cursor-pointer">
|
||||
<h2 class="font-bold">{{ event.name }}</h2>
|
||||
<Icon size="1.2em" name="ion:arrow-down-b" class="ml-auto my-auto mr-2 transition-all" :class="{'rotate-180' : event.open }" />
|
||||
@ -61,11 +61,11 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr @click="handleModel(competitor, event.id, true, index)" v-for="(competitor, index) in event.competitors" class="even:dark:bg-neutral-700 even:bg-neutral-300 hover:cursor-pointer">
|
||||
<td class="py-1 pl-1">{{ competitors.find(x => x.relatiecode === competitor.relatiecode ).name }}</td>
|
||||
<td class="py-1 pl-1">{{ contestStore.competitors.find(x => x.relatiecode === competitor.relatiecode ).name }}</td>
|
||||
<td>{{ competitor.time.minutes.toString().padStart(2, '0') }}:{{ competitor.time.seconds.toString().padStart(2, '0') }}:{{ competitor.time.milliseconds.toString().padStart(2, '0') }}</td>
|
||||
<td>{{ competitor.dsq }}</td>
|
||||
</tr>
|
||||
<tr v-if="competitors.filter(x => !event.competitors.map(y => y.relatiecode).includes(x.relatiecode)).length > 0" class="even:dark:bg-neutral-700 even:bg-neutral-300">
|
||||
<tr v-if="contestStore.competitors.filter(x => !event.competitors.map(y => y.relatiecode).includes(x.relatiecode)).length > 0" class="even:dark:bg-neutral-700 even:bg-neutral-300">
|
||||
<td @click="handleModel(null, event.id)" class="hover:cursor-pointer py-1 pl-1">+ Deelnemer toevoegen</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
@ -81,7 +81,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getDocs, collection, writeBatch, doc } from "firebase/firestore"
|
||||
import { getDocs, collection, writeBatch, doc, getFirestore } from "firebase/firestore"
|
||||
import { useToast } from 'vue-toastification'
|
||||
|
||||
definePageMeta({
|
||||
@ -91,7 +91,9 @@ definePageMeta({
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
const { db, competitors } = inject('firebase')
|
||||
const contestStore = useContestStore()
|
||||
const userStore = useUserStore()
|
||||
const db = getFirestore()
|
||||
|
||||
const showModel = ref(false)
|
||||
const disableButtons = ref(false)
|
||||
@ -151,14 +153,6 @@ const contest = ref({
|
||||
}
|
||||
})
|
||||
|
||||
const getCompetitors = async () => {
|
||||
if (competitors.value[0]) return
|
||||
const querySnapshot = await getDocs(collection(db, "competitors"))
|
||||
querySnapshot.forEach((doc) => {
|
||||
competitors.value.push(doc.data())
|
||||
})
|
||||
}
|
||||
|
||||
const handleModel = (competitor, event, edit, index) => {
|
||||
if(!competitor) competitor = {
|
||||
relatiecode: '',
|
||||
@ -206,7 +200,11 @@ const submitContestForm = async () => {
|
||||
const docRef = doc(collection(db, 'timings'))
|
||||
batch.set(docRef, {
|
||||
relatiecode: competitor.relatiecode,
|
||||
<<<<<<< HEAD
|
||||
contest: { location: contest.value.location, date: contest.value.date, type: contest.value.type },
|
||||
=======
|
||||
contest: { location: contest.value.location, date: new Date(contest.value.date), type: contest.value.type },
|
||||
>>>>>>> dev
|
||||
event: event.id,
|
||||
time: { minutes: competitor.time.minutes.toString().padStart(2, '0'), seconds: competitor.time.seconds.toString().padStart(2, '0'), milliseconds: competitor.time.milliseconds.toString().padStart(2, '0'), combined: combinedTime },
|
||||
dsq: competitor.dsq,
|
||||
@ -223,7 +221,7 @@ const submitContestForm = async () => {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getCompetitors()
|
||||
contestStore.getCompetitors()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -4,44 +4,44 @@
|
||||
<h1 class="font-bold text-center text-lg mb-5">Tijd {{ modelData.eventName }}</h1>
|
||||
|
||||
<label class="font-bold">Locatie</label>
|
||||
<input :disabled="!userData.wedstrijdAdmin" v-model="modelData.contest.location" type="text" class="input dark:bg-neutral-700 bg-neutral-300 mb-5" />
|
||||
<input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.contest.location" type="text" class="input dark:bg-neutral-700 bg-neutral-300 mb-5" />
|
||||
|
||||
<label class="font-bold">Type zwembad</label>
|
||||
<select :disabled="!userData.wedstrijdAdmin" v-model="modelData.contest.type" required="true" class="input dark:bg-neutral-700 bg-neutral-300 mb-5">
|
||||
<select :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.contest.type" required="true" class="input dark:bg-neutral-700 bg-neutral-300 mb-5">
|
||||
<option value="50m">50 Meter</option>
|
||||
<option value="25m">25 Meter</option>
|
||||
</select>
|
||||
|
||||
<label class="font-bold">Datum</label>
|
||||
<input :disabled="!userData.wedstrijdAdmin" :value="dateToYYYYMMDD(modelData.contest.date)" @input="modelData.contest.date = $event.target.valueAsDate" required="true" class="input dark:bg-neutral-700 bg-neutral-300 w-min hover:cursor-pointer pr-0 mb-5 " type="date">
|
||||
<input :disabled="!userStore.userData.wedstrijdAdmin" :value="dateToYYYYMMDD(modelData.contest.date)" @input="modelData.contest.date = $event.target.valueAsDate" required="true" class="input dark:bg-neutral-700 bg-neutral-300 w-min hover:cursor-pointer pr-0 mb-5 " type="date">
|
||||
|
||||
<label class="font-bold">Tijd</label>
|
||||
<div class="mb-1">
|
||||
<input :disabled="!userData.wedstrijdAdmin" v-model="modelData.time.minutes" type="number" step="1" min="0" max="99" placeholder="mm" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
|
||||
<input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.time.minutes" type="number" step="1" min="0" max="99" placeholder="mm" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
|
||||
<span class="text-default text-xl font-bold mx-1">:</span>
|
||||
<input :disabled="!userData.wedstrijdAdmin" v-model="modelData.time.seconds" type="number" step="1" min="0" max="99" placeholder="ss" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
|
||||
<input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.time.seconds" type="number" step="1" min="0" max="99" placeholder="ss" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
|
||||
<span class="text-default text-xl font-bold mx-1">:</span>
|
||||
<input :disabled="!userData.wedstrijdAdmin" v-model="modelData.time.milliseconds" type="number" step="1" min="0" max="99" placeholder="ms" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
|
||||
<input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.time.milliseconds" type="number" step="1" min="0" max="99" placeholder="ms" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center mb-5">
|
||||
<input :disabled="!userData.wedstrijdAdmin" type="checkbox" v-model="modelData.dsq" class="mr-1 checkbox">
|
||||
<input :disabled="!userStore.userData.wedstrijdAdmin" type="checkbox" v-model="modelData.dsq" class="mr-1 checkbox">
|
||||
<span class="text-default">Diskwalificatie</span>
|
||||
</div>
|
||||
|
||||
<label class="font-bold">Info </label>
|
||||
<input :disabled="!userData.wedstrijdAdmin" v-model="modelData.info" type="text" placeholder="Bijv. Een diskwalificatie" class="input dark:bg-neutral-700 bg-neutral-300 mb-10" />
|
||||
<input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.info" type="text" placeholder="Bijv. Een diskwalificatie" class="input dark:bg-neutral-700 bg-neutral-300 mb-10" />
|
||||
|
||||
<input v-if="userData.wedstrijdAdmin" :disabled="disableButtons" type="submit" class="btn" value="Bewerken" />
|
||||
<input v-if="userStore.userData.wedstrijdAdmin" :disabled="disableButtons" type="submit" class="btn" value="Bewerken" />
|
||||
</form>
|
||||
</div>
|
||||
<div v-if="timings[0]" class="flex flex-col justify-center items-center gap-y-3 px-2 overflow-hidden">
|
||||
<div v-if="contestStore.timings[0]" class="flex flex-col justify-center items-center gap-y-3 px-2 overflow-hidden">
|
||||
<div class="flex gap-x-5">
|
||||
<div class="relative">
|
||||
<button @click.stop="showDeelnemersDropdown = !showDeelnemersDropdown" class="btn">Deelnemers <Icon size="1.2em" name="ion:arrow-down-b" /></button>
|
||||
<div v-if="showDeelnemersDropdown" v-on-click-outside.bubble="handleDeelnemersDropdown" class="w-48 mt-2 container absolute rounded-lg shadow p-3">
|
||||
<ul class="space-y-2 text-default">
|
||||
<li v-for="competitor in competitors" @click="competitor.checked = !competitor.checked" class="flex gap-x-1 items-center hover:cursor-pointer">
|
||||
<li v-for="competitor in contestStore.competitors" @click="competitor.checked = !competitor.checked" class="flex gap-x-1 items-center hover:cursor-pointer">
|
||||
<input v-model="competitor.checked" type="checkbox" class="checkbox">
|
||||
<label class="hover:cursor-pointer">{{ competitor.name }}</label>
|
||||
</li>
|
||||
@ -63,14 +63,14 @@
|
||||
<div v-for="event in events" class="container w-full max-w-3xl py-2 px-4">
|
||||
<div @click="event.open = !event.open" class="flex hover:cursor-pointer">
|
||||
<h2 class="font-bold mr-auto">{{ event.name }}</h2>
|
||||
<span v-if="filteredTimings.filter(a => a.event === event.id).length > 0" class="">
|
||||
<span v-if="contestStore.filteredTimings.filter(a => a.event === event.id).length > 0" class="">
|
||||
<span class="hidden md:inline-block mr-1">
|
||||
{{ filteredTimings.filter(a => a.event === event.id)[0].contest.date.toLocaleDateString('nl-NL') }} |
|
||||
{{ filteredTimings.filter(a => a.event === event.id)[0].contest.location }} |
|
||||
{{ filteredTimings.filter(a => a.event === event.id)[0].contest.type }} |
|
||||
{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].contest.date.toLocaleDateString('nl-NL') }} |
|
||||
{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].contest.location }} |
|
||||
{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].contest.type }} |
|
||||
</span>
|
||||
<span>
|
||||
{{ filteredTimings.filter(a => a.event === event.id)[0].time.minutes }}:{{ filteredTimings.filter(a => a.event === event.id)[0].time.seconds }}:{{ filteredTimings.filter(a => a.event === event.id)[0].time.milliseconds }}</span>
|
||||
{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].time.minutes }}:{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].time.seconds }}:{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].time.milliseconds }}</span>
|
||||
</span>
|
||||
<span v-else class="">Geen tijd</span>
|
||||
<Icon size="1.2em" name="ion:arrow-down-b" class="my-auto ml-2 transition-all" :class="{'rotate-180' : event.open }" />
|
||||
@ -83,10 +83,10 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr @click="handleModel(time, event, index)" v-for="(time, index) in filteredTimings.filter(a => a.event === event.id)" class="even:dark:bg-neutral-700 even:bg-neutral-300 hover:cursor-pointer">
|
||||
<tr @click="handleModel(time, event, index)" v-for="(time, index) in contestStore.filteredTimings.filter(a => a.event === event.id)" class="even:dark:bg-neutral-700 even:bg-neutral-300 hover:cursor-pointer">
|
||||
<td v-if="properties.time.enabled" class="pl-1" :class="time.dsq ? 'line-through' : ''">{{ time.time.minutes }}:{{ time.time.seconds }}:{{ time.time.milliseconds}}</td>
|
||||
<td v-if="properties.date.enabled">{{ time.contest.date.toLocaleDateString('nl-NL') }}</td>
|
||||
<td v-if="properties.name.enabled" class="overflow-hidden whitespace-nowrap truncate">{{ competitors.filter(a => a.relatiecode === time.relatiecode)[0].name.split(', ')[1] + ' ' + competitors.filter(a => a.relatiecode === time.relatiecode)[0].name.split(', ')[0] }}</td>
|
||||
<td v-if="properties.name.enabled" class="overflow-hidden whitespace-nowrap truncate">{{ contestStore.competitors.filter(a => a.relatiecode === time.relatiecode)[0].name.split(', ')[1] + ' ' + contestStore.competitors.filter(a => a.relatiecode === time.relatiecode)[0].name.split(', ')[0] }}</td>
|
||||
<td v-if="properties.type.enabled">{{ time.contest.type }}</td>
|
||||
<td v-if="properties.location.enabled">{{ time.contest.location }}</td>
|
||||
</tr>
|
||||
@ -107,10 +107,10 @@ definePageMeta({
|
||||
key: 'back'
|
||||
})
|
||||
|
||||
const { db, userData, competitors } = inject('firebase')
|
||||
const toast = useToast()
|
||||
const userStore = useUserStore()
|
||||
const contestStore = useContestStore()
|
||||
|
||||
const timings = ref([])
|
||||
const showModel = ref(false)
|
||||
const disableButtons = ref(false)
|
||||
const showDeelnemersDropdown = ref(false)
|
||||
@ -124,16 +124,6 @@ const handlePropertiesDropdown = () => {
|
||||
showPropertiesDropdown.value = false
|
||||
}
|
||||
|
||||
const filteredTimings = computed(() => {
|
||||
timings.value = timings.value.sort((a, b) => a.time.combined.localeCompare(b.time.combined))
|
||||
timings.value.forEach((time, index) => {
|
||||
if (time.dsq === true) {
|
||||
timings.value.push(timings.value.splice(index, 1)[0])
|
||||
}
|
||||
})
|
||||
return timings.value.filter(a => competitors.value.filter(b => b.checked === true).map(x => x.relatiecode).includes(a.relatiecode))
|
||||
})
|
||||
|
||||
const modelData = ref({
|
||||
time: {
|
||||
minutes: null,
|
||||
@ -216,35 +206,11 @@ const events = ref({
|
||||
},
|
||||
})
|
||||
|
||||
const getCompetitors = async () => {
|
||||
if (competitors.value[0]) return
|
||||
const querySnapshot = await getDocs(collection(db, "competitors"))
|
||||
querySnapshot.forEach((doc) => {
|
||||
const data = doc.data()
|
||||
data.checked = true
|
||||
competitors.value.push(data)
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const citiesRef = collection(db, "timings");
|
||||
await contestStore.getCompetitors()
|
||||
await contestStore.getTimings()
|
||||
|
||||
const q = query(citiesRef);
|
||||
|
||||
const querySnapshot = await getDocs(q);
|
||||
|
||||
querySnapshot.forEach((doc) => {
|
||||
// doc.data() is never undefined for query doc snapshots
|
||||
const data = doc.data()
|
||||
data.id = doc.id
|
||||
|
||||
data.contest.date = data.contest.date.toDate()
|
||||
|
||||
timings.value.push(data)
|
||||
});
|
||||
|
||||
timings.value = timings.value.sort((a, b) => a.time.combined.localeCompare(b.time.combined))
|
||||
getCompetitors()
|
||||
contestStore.selectCompetitors('all')
|
||||
})
|
||||
|
||||
const dateToYYYYMMDD = (d) => {
|
||||
|
@ -10,9 +10,9 @@
|
||||
<span>Brigade Tijden</span>
|
||||
<Icon class="ml-auto" size="2em" name="ion:arrow-forward"/>
|
||||
</NuxtLink>
|
||||
<div v-if="userData.wedstrijdAdmin" class="divider" />
|
||||
<NuxtLink v-if="userData.wedstrijdAdmin" to="/wedstrijd/addcontest" class="rounded-b item-hover py-2 flex items-center">
|
||||
<span>Wedstrijd Toevoegen</span>
|
||||
<div v-if="userStore.userData.wedstrijdAdmin" class="divider" />
|
||||
<NuxtLink v-if="userStore.userData.wedstrijdAdmin" to="/wedstrijd/addcontest" class="rounded-b item-hover py-2 flex items-center">
|
||||
<span>Tijden Toevoegen</span>
|
||||
<Icon class="ml-auto" size="2em" name="ion:arrow-forward"/>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
@ -24,5 +24,5 @@ definePageMeta({
|
||||
title: 'Wedstrijd'
|
||||
})
|
||||
|
||||
const { userData } = inject('firebase')
|
||||
const userStore = useUserStore()
|
||||
</script>
|
||||
|
@ -4,42 +4,42 @@
|
||||
<h1 class="font-bold text-center text-lg mb-5">Tijd {{ modelData.eventName }}</h1>
|
||||
|
||||
<label class="font-bold">Locatie</label>
|
||||
<input :disabled="!userData.wedstrijdAdmin" v-model="modelData.contest.location" type="text" class="input dark:bg-neutral-700 bg-neutral-300 mb-5" />
|
||||
<input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.contest.location" type="text" class="input dark:bg-neutral-700 bg-neutral-300 mb-5" />
|
||||
|
||||
<label class="font-bold">Datum</label>
|
||||
<input :disabled="!userData.wedstrijdAdmin" :value="dateToYYYYMMDD(modelData.contest.date)" @input="modelData.contest.date = $event.target.valueAsDate" required="true" class="input dark:bg-neutral-700 bg-neutral-300 w-min hover:cursor-pointer pr-0 mb-5 " type="date">
|
||||
<input :disabled="!userStore.userData.wedstrijdAdmin" :value="dateToYYYYMMDD(modelData.contest.date)" @input="modelData.contest.date = $event.target.valueAsDate" required="true" class="input dark:bg-neutral-700 bg-neutral-300 w-min hover:cursor-pointer pr-0 mb-5 " type="date">
|
||||
|
||||
<label class="font-bold">Type zwembad</label>
|
||||
<select :disabled="!userData.wedstrijdAdmin" v-model="modelData.contest.type" required="true" class="input dark:bg-neutral-700 bg-neutral-300 mb-5">
|
||||
<select :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.contest.type" required="true" class="input dark:bg-neutral-700 bg-neutral-300 mb-5">
|
||||
<option value="50m">50 Meter</option>
|
||||
<option value="25m">25 Meter</option>
|
||||
</select>
|
||||
|
||||
<label class="font-bold">Tijd</label>
|
||||
<div class="mb-1">
|
||||
<input :disabled="!userData.wedstrijdAdmin" v-model="modelData.time.minutes" type="number" step="1" min="0" max="99" placeholder="mm" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
|
||||
<input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.time.minutes" type="number" step="1" min="0" max="99" placeholder="mm" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
|
||||
<span class="text-default text-xl font-bold mx-1">:</span>
|
||||
<input :disabled="!userData.wedstrijdAdmin" v-model="modelData.time.seconds" type="number" step="1" min="0" max="99" placeholder="ss" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
|
||||
<input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.time.seconds" type="number" step="1" min="0" max="99" placeholder="ss" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
|
||||
<span class="text-default text-xl font-bold mx-1">:</span>
|
||||
<input :disabled="!userData.wedstrijdAdmin" v-model="modelData.time.milliseconds" type="number" step="1" min="0" max="99" placeholder="ms" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
|
||||
<input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.time.milliseconds" type="number" step="1" min="0" max="99" placeholder="ms" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center mb-5">
|
||||
<input :disabled="!userData.wedstrijdAdmin" type="checkbox" v-model="modelData.dsq" class="mr-1 checkbox">
|
||||
<input :disabled="!userStore.userData.wedstrijdAdmin" type="checkbox" v-model="modelData.dsq" class="mr-1 checkbox">
|
||||
<span class="text-default">Diskwalificatie</span>
|
||||
</div>
|
||||
|
||||
<label class="font-bold">Info </label>
|
||||
<input :disabled="!userData.wedstrijdAdmin" v-model="modelData.info" type="text" placeholder="Bijv. Een diskwalificatie" class="input dark:bg-neutral-700 bg-neutral-300 mb-10" />
|
||||
<input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.info" type="text" placeholder="Bijv. Een diskwalificatie" class="input dark:bg-neutral-700 bg-neutral-300 mb-10" />
|
||||
|
||||
<input v-if="userData.wedstrijdAdmin" :disabled="disableButtons" type="submit" class="btn" value="Bewerken" />
|
||||
<input v-if="userStore.userData.wedstrijdAdmin" :disabled="disableButtons" type="submit" class="btn" value="Bewerken" />
|
||||
</form>
|
||||
</div>
|
||||
<div v-if="timings[0]" class="flex flex-col justify-center items-center gap-y-3 px-2 overflow-hidden">
|
||||
<div v-if="contestStore.filteredTimings[0]" class="flex flex-col justify-center items-center gap-y-3 px-2 overflow-hidden">
|
||||
<div v-for="event in events" class="container w-full max-w-md py-2 px-4">
|
||||
<div @click="event.open = !event.open" class="flex hover:cursor-pointer">
|
||||
<h2 class="font-bold">{{ event.name }}</h2>
|
||||
<span v-if="timings.filter(a => a.event === event.id).length > 0" class="ml-auto">{{ filteredTimings.filter(a => a.event === event.id)[0].time.minutes }}:{{ filteredTimings.filter(a => a.event === event.id)[0].time.seconds }}:{{ filteredTimings.filter(a => a.event === event.id)[0].time.milliseconds }}</span>
|
||||
<span v-if="contestStore.filteredTimings.filter(a => a.event === event.id)[0]" class="ml-auto">{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].time.minutes }}:{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].time.seconds }}:{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].time.milliseconds }}</span>
|
||||
<span v-else class="ml-auto">Geen tijd</span>
|
||||
<Icon size="1.2em" name="ion:arrow-down-b" class="my-auto ml-2 transition-all" :class="{'rotate-180' : event.open }" />
|
||||
</div>
|
||||
@ -53,7 +53,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr @click="handleModel(time, event, index)" v-for="(time, index) in filteredTimings.filter(a => a.event === event.id)" class="even:dark:bg-neutral-700 even:bg-neutral-300 hover:cursor-pointer">
|
||||
<tr @click="handleModel(time, event, index)" v-for="(time, index) in contestStore.filteredTimings.filter(a => a.event === event.id)" class="even:dark:bg-neutral-700 even:bg-neutral-300 hover:cursor-pointer">
|
||||
<td class="pl-1" :class="time.dsq ? 'line-through' : ''">{{ time.time.minutes }}:{{ time.time.seconds }}:{{ time.time.milliseconds}}</td>
|
||||
<td>{{ time.contest.date.toLocaleDateString('nl-NL') }}</td>
|
||||
<td>{{ time.contest.type }}</td>
|
||||
@ -74,23 +74,13 @@ definePageMeta({
|
||||
key: 'back'
|
||||
})
|
||||
|
||||
const { db, userData } = inject('firebase')
|
||||
const toast = useToast()
|
||||
const contestStore = useContestStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const timings = ref([])
|
||||
const showModel = ref(false)
|
||||
const disableButtons = ref(false)
|
||||
|
||||
const filteredTimings = computed(() => {
|
||||
timings.value = timings.value.sort((a, b) => a.time.combined.localeCompare(b.time.combined))
|
||||
timings.value.forEach((time, index) => {
|
||||
if (time.dsq === true) {
|
||||
timings.value.push(timings.value.splice(index, 1)[0])
|
||||
}
|
||||
})
|
||||
return timings.value
|
||||
})
|
||||
|
||||
const modelData = ref({
|
||||
time: {
|
||||
minutes: null,
|
||||
@ -146,23 +136,8 @@ const events = ref({
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
const citiesRef = collection(db, "timings");
|
||||
|
||||
const q = query(citiesRef, where("relatiecode", "in", userData.value.relatiecodes ));
|
||||
|
||||
const querySnapshot = await getDocs(q);
|
||||
|
||||
querySnapshot.forEach((doc) => {
|
||||
// doc.data() is never undefined for query doc snapshots
|
||||
const data = doc.data()
|
||||
data.id = doc.id
|
||||
|
||||
data.contest.date = data.contest.date.toDate()
|
||||
|
||||
timings.value.push(data)
|
||||
});
|
||||
|
||||
timings.value = timings.value.sort((a, b) => a.time.combined.localeCompare(b.time.combined))
|
||||
await contestStore.getTimings()
|
||||
contestStore.selectCompetitors('user', userStore.userData.relatiecodes)
|
||||
})
|
||||
|
||||
|
||||
|
52
frontend/stores/calendarStore.js
Normal file
52
frontend/stores/calendarStore.js
Normal file
@ -0,0 +1,52 @@
|
||||
export const useCalendarStore = defineStore('calendar', () => {
|
||||
const events = ref([])
|
||||
|
||||
const getEvents = async (group) => {
|
||||
let fridayEvents = []
|
||||
let saturdayEvents = []
|
||||
let matchEvents = []
|
||||
|
||||
if (group.includes('Zaterdag')){
|
||||
let data = await fetch('https://www.googleapis.com/calendar/v3/calendars/c_astg0d0auheip5o4269v1qvv3g@group.calendar.google.com/events?key=AIzaSyBLmxWNEnbkiW_0c2UrsHMbxXv73dX-KYw')
|
||||
if (!data.ok){
|
||||
throw Error('No data available')
|
||||
}
|
||||
|
||||
const jsonEvents = await data.json()
|
||||
saturdayEvents = jsonEvents.items
|
||||
} if (group.includes('Vrijdag')){
|
||||
let data = await fetch('https://www.googleapis.com/calendar/v3/calendars/c_u3895n01jt8qnusm7f6pmqjb0k%40group.calendar.google.com/events?key=AIzaSyBLmxWNEnbkiW_0c2UrsHMbxXv73dX-KYw')
|
||||
if (!data.ok){
|
||||
throw Error('No data available')
|
||||
}
|
||||
|
||||
const jsonEvents = await data.json()
|
||||
fridayEvents = jsonEvents.items
|
||||
} if (group.includes('Wedstrijd')){
|
||||
let data = await fetch('https://www.googleapis.com/calendar/v3/calendars/c_c2296iboq07n24galuobeesovs@group.calendar.google.com/events?key=AIzaSyBLmxWNEnbkiW_0c2UrsHMbxXv73dX-KYw')
|
||||
if (!data.ok){
|
||||
throw Error('No data available')
|
||||
}
|
||||
const jsonEvents = await data.json()
|
||||
matchEvents = jsonEvents.items
|
||||
}
|
||||
|
||||
const allEvents = [...fridayEvents, ...saturdayEvents, ...matchEvents]
|
||||
|
||||
const now = new Date()
|
||||
now.setHours(0, 0, 0, 0)
|
||||
|
||||
const sortedEvents = allEvents.sort((a, b) => new Date(a.start.dateTime) - new Date(b.start.dateTime))
|
||||
const filteredEvents = sortedEvents.filter((event) => new Date(event.start.dateTime) > now )
|
||||
|
||||
filteredEvents.forEach(element => {
|
||||
events.value.push({
|
||||
description: element.description,
|
||||
date: new Date(element.start.dateTime)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return { events, getEvents }
|
||||
})
|
||||
|
74
frontend/stores/contestStore.js
Normal file
74
frontend/stores/contestStore.js
Normal file
@ -0,0 +1,74 @@
|
||||
import { getDocs, collection, getFirestore, query } from 'firebase/firestore'
|
||||
|
||||
export const useContestStore = defineStore('contest', () => {
|
||||
// General
|
||||
const db = getFirestore()
|
||||
|
||||
// Contest competitors
|
||||
const competitors = ref([])
|
||||
|
||||
const getCompetitors = async () => {
|
||||
if (competitors.value[0]) return
|
||||
|
||||
const querySnapshot = await getDocs(collection(db, "competitors"))
|
||||
|
||||
querySnapshot.forEach((doc) => {
|
||||
const data = doc.data()
|
||||
data.checked = true
|
||||
competitors.value.push(data)
|
||||
})
|
||||
}
|
||||
|
||||
const selectCompetitors = (x, relatiecodes) => {
|
||||
if (x === 'user') {
|
||||
competitors.value.forEach(competitor => {
|
||||
if (relatiecodes.includes(competitor.relatiecode)) {
|
||||
competitor.checked = true
|
||||
} else {
|
||||
competitor.checked = false
|
||||
}
|
||||
})
|
||||
} else if (x === 'all') {
|
||||
competitors.value.forEach(competitor => competitor.checked = true)
|
||||
} else if (x === 'none') {
|
||||
competitors.value.forEach(competitor => competitor.checked = false)
|
||||
}
|
||||
}
|
||||
|
||||
// Contest Timings
|
||||
const timings = ref([])
|
||||
|
||||
const filteredTimings = computed(() => {
|
||||
timings.value = timings.value.sort((a, b) => a.time.combined.localeCompare(b.time.combined))
|
||||
|
||||
timings.value.forEach((time, index) => {
|
||||
if (time.dsq === true) {
|
||||
timings.value.push(timings.value.splice(index, 1)[0])
|
||||
}
|
||||
})
|
||||
|
||||
return timings.value.filter(a => competitors.value.filter(b => b.checked === true).map(x => x.relatiecode).includes(a.relatiecode))
|
||||
})
|
||||
|
||||
const getTimings = async () => {
|
||||
if (timings.value[0]) return;
|
||||
if (!competitors.value[0]) await getCompetitors()
|
||||
|
||||
const timingsRef = collection(db, "timings");
|
||||
|
||||
const q = query(timingsRef);
|
||||
|
||||
const querySnapshot = await getDocs(q);
|
||||
|
||||
querySnapshot.forEach((doc) => {
|
||||
const data = doc.data()
|
||||
data.id = doc.id
|
||||
|
||||
data.contest.date = data.contest.date.toDate()
|
||||
|
||||
timings.value.push(data)
|
||||
});
|
||||
}
|
||||
|
||||
return { competitors, getCompetitors, getTimings, timings, filteredTimings, selectCompetitors }
|
||||
})
|
80
frontend/stores/newsStore.js
Normal file
80
frontend/stores/newsStore.js
Normal file
@ -0,0 +1,80 @@
|
||||
import { getDocs, collection, getFirestore, deleteDoc, doc, serverTimestamp, Timestamp, addDoc } from 'firebase/firestore'
|
||||
import { getAuth } from 'firebase/auth'
|
||||
|
||||
export const useNewsStore = defineStore('news', () => {
|
||||
const db = getFirestore()
|
||||
const auth = getAuth()
|
||||
|
||||
const news = ref([])
|
||||
const loaded = ref(false)
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
const getNews = async () => {
|
||||
news.value = []
|
||||
try {
|
||||
const querySnapshot = await getDocs(collection(db, "news"));
|
||||
querySnapshot.forEach((doc) => {
|
||||
let data = doc.data()
|
||||
data.id = doc.id
|
||||
news.value.push(data)
|
||||
});
|
||||
|
||||
news.value.sort((a, b) => b.date.seconds - a.date.seconds)
|
||||
|
||||
loaded.value = true
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
const deleteNews = async (item, index) => {
|
||||
if (!item.id) return toast.error('Refresh eerst voordat je dit bericht kan verwijderen')
|
||||
|
||||
try {
|
||||
await deleteDoc(doc(db, "news", item.id));
|
||||
|
||||
news.value.splice(index, 1)
|
||||
toast.success('Bericht verwijderd')
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
|
||||
toast.error('Error tijdens bericht verwijderen')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const send = async (form) => {
|
||||
const idToken = await auth.currentUser.getIdToken(true)
|
||||
console.log(idToken)
|
||||
|
||||
const { error } = await useFetch('/api/sendmessage', {
|
||||
method: 'post',
|
||||
body: { title: form.title, body: form.description, token: idToken, topic: form.topic }
|
||||
})
|
||||
|
||||
if (error.value) {
|
||||
console.log(error.value)
|
||||
return toast.error('Error tijdens het versturen van het bericht')
|
||||
}
|
||||
|
||||
await addDoc(collection(db, "news"), {
|
||||
title: form.title,
|
||||
description: form.description,
|
||||
date: serverTimestamp()
|
||||
});
|
||||
|
||||
if (news.value) {
|
||||
news.value.unshift({
|
||||
title: form.title,
|
||||
description: form.description,
|
||||
date: Timestamp.now()
|
||||
})
|
||||
}
|
||||
toast.success('Bericht is verstuurd')
|
||||
|
||||
navigateTo('/news')
|
||||
}
|
||||
|
||||
return { getNews, news, loaded, deleteNews, send }
|
||||
});
|
93
frontend/stores/userStore.js
Normal file
93
frontend/stores/userStore.js
Normal file
@ -0,0 +1,93 @@
|
||||
import { onAuthStateChanged, getAuth } from 'firebase/auth'
|
||||
import { getDoc, doc, getFirestore } from 'firebase/firestore'
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
const db = getFirestore()
|
||||
const route = useRoute()
|
||||
|
||||
const auth = ref(null)
|
||||
auth.value = getAuth()
|
||||
|
||||
const user = ref(null)
|
||||
const isAuthenticated = ref(false)
|
||||
const userData = ref(false)
|
||||
const userPersons = ref([])
|
||||
const userLoaded = ref(false)
|
||||
const registrationToken = ref('')
|
||||
const userAllPersons = ref([])
|
||||
|
||||
const init = () => {
|
||||
onAuthStateChanged(auth.value, async (usr) => {
|
||||
if (usr) {
|
||||
user.value = usr
|
||||
|
||||
let docRef = doc(db, "users", user.value.uid);
|
||||
let docSnap = await getDoc(docRef);
|
||||
|
||||
if (docSnap.exists()) {
|
||||
const data = docSnap.data()
|
||||
userData.value = data
|
||||
getPersons(userData.value.relatiecodes)
|
||||
} else {
|
||||
setTimeout(() => window.location.reload(true), 1000)
|
||||
}
|
||||
|
||||
if (!userData.value.sendNews && route.path === '/news/newmessage') navigateTo('/')
|
||||
if (!userData.value.admin && route.path.startsWith('/settings/admin')) navigateTo('/')
|
||||
|
||||
isAuthenticated.value = true
|
||||
|
||||
setupNotifications()
|
||||
|
||||
} else {
|
||||
isAuthenticated.value = false
|
||||
user.value = null
|
||||
userData.value = null
|
||||
userPersons.value = []
|
||||
}
|
||||
|
||||
userLoaded.value = true
|
||||
})
|
||||
}
|
||||
|
||||
const getPersons = async (persons) => {
|
||||
userPersons.value = [];
|
||||
|
||||
for (let i = 0; i < persons.length; i++) {
|
||||
const docRef = doc(db, "ledenlijst", persons[i]);
|
||||
const docSnap = await getDoc(docRef);
|
||||
|
||||
if (docSnap.exists()) {
|
||||
userPersons.value.push(docSnap.data())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getAllPersons = async () => {
|
||||
if (userPersons.value.length === 0) return setTimeout(() => getAllPersons(), 50)
|
||||
|
||||
const idToken = await auth.value.currentUser.getIdToken(true)
|
||||
|
||||
const { data: response, error } = await useFetch('/api/getrelatiecodes', {
|
||||
method: 'post',
|
||||
body: { email: user.value.email, token: idToken }
|
||||
})
|
||||
|
||||
if (error.value) {
|
||||
console.log(error.value)
|
||||
return toast.error('Error tijdens het krijgen van relateicodes')
|
||||
}
|
||||
|
||||
response.value.persons.forEach(person => {
|
||||
if (userPersons.value.map(a => a.relatiecode).includes(person.relatiecode)) {
|
||||
person.checked = true
|
||||
} else {
|
||||
person.checked = false
|
||||
}
|
||||
})
|
||||
|
||||
userAllPersons.value = response.value.persons
|
||||
}
|
||||
|
||||
return { init, auth, isAuthenticated, userData, userPersons, userAllPersons, getAllPersons, registrationToken, getPersons, userLoaded, user }
|
||||
})
|
38
frontend/stores/usersStore.js
Normal file
38
frontend/stores/usersStore.js
Normal file
@ -0,0 +1,38 @@
|
||||
import { getDocs, collection, getFirestore } from 'firebase/firestore'
|
||||
|
||||
export const useUsersStore = defineStore('users', () => {
|
||||
const ledenlijst = ref([])
|
||||
const users = ref([])
|
||||
|
||||
const db = getFirestore()
|
||||
|
||||
const getLedenlijst = async () => {
|
||||
if (ledenlijst[0]) return;
|
||||
|
||||
try {
|
||||
const querySnapshot = await getDocs(collection(db, "ledenlijst"));
|
||||
querySnapshot.forEach((doc) => {
|
||||
ledenlijst.value.push(doc.data())
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
ledenlijst.value.sort((a, b) => a.fullName.localeCompare(b.fullName))
|
||||
}
|
||||
|
||||
const getUsers = async () => {
|
||||
if (users[0]) return
|
||||
|
||||
try {
|
||||
const querySnapshot = await getDocs(collection(db, "users"));
|
||||
querySnapshot.forEach((doc) => {
|
||||
users.value.push(doc.data())
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
return { ledenlijst, users, getLedenlijst, getUsers }
|
||||
});
|
Loading…
Reference in New Issue
Block a user