Added online schedulling
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
<a href="/members">Mitglieder</a>
|
||||
<a href="/diary">Tagebuch</a>
|
||||
<a href="/pending-approvals">Freigaben</a>
|
||||
<a href="/schedule">Spielpläne</a>
|
||||
</template>
|
||||
<div>
|
||||
<button @click="logout()">Ausloggen</button>
|
||||
|
||||
@@ -19,4 +19,43 @@ h1 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
button {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: 2px solid #4CAF50;
|
||||
border-radius: 0;
|
||||
padding: 5px 10px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2);
|
||||
transition: background-color 0.3s ease;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
button.cancel-action {
|
||||
background-color: white;
|
||||
color: #4CAF50;
|
||||
border: 2px solid #4CAF50;
|
||||
border-radius: 0;
|
||||
padding: 5px 10px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2);
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
button.cancel-action:hover {
|
||||
background-color: #f2f2f2;
|
||||
color: #45a049;
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import ClubView from './views/ClubView.vue';
|
||||
import MembersView from './views/MembersView.vue';
|
||||
import DiaryView from './views/DiaryView.vue';
|
||||
import PendingApprovalsView from './views/PendingApprovalsView.vue';
|
||||
import ScheduleView from './views/ScheduleView.vue';
|
||||
|
||||
const routes = [
|
||||
{ path: '/register', component: Register },
|
||||
@@ -19,6 +20,7 @@ const routes = [
|
||||
{ path: '/members', component: MembersView },
|
||||
{ path: '/diary', component: DiaryView },
|
||||
{ path: '/pending-approvals', component: PendingApprovalsView},
|
||||
{ path: '/schedule', component: ScheduleView},
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
|
||||
@@ -5,7 +5,14 @@ const store = createStore({
|
||||
token: localStorage.getItem('token') || null,
|
||||
username: localStorage.getItem('username') || '',
|
||||
currentClub: localStorage.getItem('currentClub') || null,
|
||||
clubs: localStorage.getItem('clubs') || [],
|
||||
clubs: (() => {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem('clubs')) || [];
|
||||
} catch (e) {
|
||||
console.error('Error parsing clubs from localStorage:', e);
|
||||
return [];
|
||||
}
|
||||
})(),
|
||||
},
|
||||
mutations: {
|
||||
setToken(state, token) {
|
||||
@@ -24,7 +31,7 @@ const store = createStore({
|
||||
},
|
||||
setClubsMutation(state, clubs) {
|
||||
state.clubs = clubs;
|
||||
localStorage.setItem('clubs', clubs);
|
||||
localStorage.setItem('clubs', JSON.stringify(clubs)); // Als JSON-String speichern
|
||||
},
|
||||
clearToken(state) {
|
||||
state.token = null;
|
||||
@@ -48,7 +55,7 @@ const store = createStore({
|
||||
},
|
||||
setCurrentClub({ commit }, club) {
|
||||
commit('setClub', club);
|
||||
},
|
||||
},
|
||||
setClubs({ commit }, clubs) {
|
||||
commit('setClubsMutation', clubs);
|
||||
}
|
||||
@@ -59,6 +66,17 @@ const store = createStore({
|
||||
username: state => state.username,
|
||||
currentClub: state => state.currentClub,
|
||||
clubs: state => state.clubs,
|
||||
currentClubName: state => {
|
||||
console.log('Current Club ID:', state.currentClub);
|
||||
console.log('Clubs:', state.clubs);
|
||||
const club = state.clubs.find(club => club.id === parseInt(state.currentClub));
|
||||
if (club) {
|
||||
console.log('Found Club:', club.name);
|
||||
} else {
|
||||
console.log('Club not found');
|
||||
}
|
||||
return club ? club.name : '';
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@
|
||||
</div>
|
||||
<ul>
|
||||
<li v-for="note in notes" :key="note.id">
|
||||
<button @click="deleteNote(note.id)">Löschen</button>
|
||||
<button @click="deleteNote(note.id)" class="cancel-action">Löschen</button>
|
||||
{{ note.content }}
|
||||
</li>
|
||||
</ul>
|
||||
@@ -223,6 +223,10 @@ export default {
|
||||
this.loadPredefinedActivities();
|
||||
}
|
||||
},
|
||||
setCurrentDate() {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
this.newDate = today;
|
||||
},
|
||||
async handleDateChange() {
|
||||
this.showForm = this.date === 'new';
|
||||
if (this.date && this.date !== 'new') {
|
||||
@@ -646,22 +650,10 @@ form div {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button[type="button"] {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.columns {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
</div>
|
||||
<div>
|
||||
<button @click="addNewMember">{{ memberToEdit ? 'Ändern' : 'Anlegen' }}</button>
|
||||
<button @click="resetNewMember" v-if="memberToEdit === null">Felder leeren</button>
|
||||
<button @click="resetNewMember" v-if="memberToEdit === null" class="cancel-action">Felder
|
||||
leeren</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -79,7 +80,7 @@
|
||||
</div>
|
||||
<ul>
|
||||
<li v-for="note in notes" :key="note.id">
|
||||
<button @click="deleteNote(note.id)">Löschen</button>
|
||||
<button @click="deleteNote(note.id)" class="cancel-action">Löschen</button>
|
||||
{{ note.content }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
250
frontend/src/views/ScheduleView.vue
Normal file
250
frontend/src/views/ScheduleView.vue
Normal file
@@ -0,0 +1,250 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>Spielpläne</h2>
|
||||
<button @click="openImportModal">Spielplanimport</button>
|
||||
<div v-if="hoveredMatch" class="hover-info">
|
||||
<p><strong>{{ hoveredMatch.location.name }}</strong></p>
|
||||
<p>{{ hoveredMatch.location.address }}</p>
|
||||
<p>{{ hoveredMatch.location.zip }} {{ hoveredMatch.location.city }}</p>
|
||||
</div>
|
||||
<div class="output">
|
||||
<ul>
|
||||
<li v-for="league in leagues" :key="league" @click="loadMatchesForLeague(league.id)">{{ league.name }}</li>
|
||||
</ul>
|
||||
<div class="flex-item">
|
||||
<div v-if="matches.length > 0">
|
||||
<h3>Spiele für {{ selectedLeague }}</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Datum</th>
|
||||
<th>Uhrzeit</th>
|
||||
<th>Heimmannschaft</th>
|
||||
<th>Gastmannschaft</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="match in matches" :key="match.id" @mouseover="hoveredMatch = match"
|
||||
@mouseleave="hoveredMatch = null">
|
||||
<td>{{ formatDate(match.date) }}</td>
|
||||
<td>{{ match.time.toString().slice(0, 5) }} Uhr</td>
|
||||
<td v-html="highlightClubName(match.homeTeam.name)"></td>
|
||||
<td v-html="highlightClubName(match.guestTeam.name)"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p>Keine Spiele vorhanden</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showImportModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close" @click="closeImportModal">×</span>
|
||||
<h3>Spielplan importieren</h3>
|
||||
<form @submit.prevent="importCSV">
|
||||
<label for="csvFile">CSV-Datei hochladen:</label>
|
||||
<input type="file" id="csvFile" @change="onFileSelected" accept=".csv" required />
|
||||
<button type="submit">Importieren</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import apiClient from '../apiClient.js';
|
||||
|
||||
export default {
|
||||
name: 'ScheduleView',
|
||||
computed: {
|
||||
...mapGetters(['isAuthenticated', 'currentClub', 'clubs', 'currentClubName']),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showImportModal: false,
|
||||
selectedFile: null,
|
||||
leagues: [],
|
||||
matches: [],
|
||||
selectedLeague: '',
|
||||
hoveredMatch: null,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
openImportModal() {
|
||||
this.showImportModal = true;
|
||||
},
|
||||
closeImportModal() {
|
||||
this.showImportModal = false;
|
||||
this.selectedFile = null;
|
||||
},
|
||||
onFileSelected(event) {
|
||||
this.selectedFile = event.target.files[0];
|
||||
},
|
||||
async importCSV() {
|
||||
if (!this.selectedFile) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', this.selectedFile);
|
||||
formData.append('clubId', this.currentClub);
|
||||
|
||||
try {
|
||||
const response = await apiClient.post('/matches/import', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
});
|
||||
alert('Spielplan erfolgreich importiert!');
|
||||
this.closeImportModal();
|
||||
this.loadLeagues();
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Importieren der CSV:', error);
|
||||
alert('Fehler beim Importieren der CSV.');
|
||||
}
|
||||
},
|
||||
async loadLeagues() {
|
||||
try {
|
||||
const clubId = this.currentClub;
|
||||
const response = await apiClient.get(`/matches/leagues/current/${clubId}`);
|
||||
this.leagues = response.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to load leagues:', error);
|
||||
}
|
||||
},
|
||||
async loadMatchesForLeague(league) {
|
||||
this.selectedLeague = league;
|
||||
try {
|
||||
const response = await apiClient.get(`/matches/leagues/${this.currentClub}/matches/${league}`);
|
||||
this.matches = response.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to load matches:', error);
|
||||
this.matches = [];
|
||||
}
|
||||
},
|
||||
formatDate(date) {
|
||||
const options = { year: 'numeric', month: '2-digit', day: '2-digit' };
|
||||
return new Date(date).toLocaleDateString('de-DE', options);
|
||||
},
|
||||
highlightClubName(teamName) {
|
||||
const clubName = this.currentClubName;
|
||||
if (clubName && teamName.includes(clubName)) {
|
||||
return `<strong>${teamName}</strong>`;
|
||||
}
|
||||
return teamName;
|
||||
},
|
||||
getCurrentClubName() {
|
||||
const club = this.clubs.find(club => club.id === this.currentClub);
|
||||
console.log(club, this.currentClub);
|
||||
return club ? club.name : '';
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.loadLeagues();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.output {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.output ul {
|
||||
flex: 0;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.flex-item {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
table,
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.hover-info {
|
||||
margin-top: 10px;
|
||||
background-color: #eef;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
max-width: 300px;
|
||||
position: fixed;
|
||||
top:16em;
|
||||
left:14em;
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: #fefefe;
|
||||
padding: 20px;
|
||||
border: 1px solid #888;
|
||||
width: 80%;
|
||||
max-width: 500px;
|
||||
max-height: 80%;
|
||||
box-shadow: 4px 3px 2px #999;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 15px;
|
||||
color: #aaa;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.close:hover,
|
||||
.close:focus {
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
white-space: nowrap;
|
||||
color: #45a049;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user