"Added picsInInternetAllowed field to Member model and updated related controllers, services, and views to handle new field; modified diary date activity routes and diary view; updated member service and member controller to handle new field; added new routes for diary date
This commit is contained in:
29
backend/controllers/diaryDateTagController.js
Normal file
29
backend/controllers/diaryDateTagController.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import diaryDateTagService from "../services/diaryDateTagService.js"
|
||||
|
||||
export const getDiaryDateMemberTags = async (req, res) => {
|
||||
console.log("getDiaryDateMemberTags");
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId, memberId } = req.params;
|
||||
const tags = await diaryDateTagService.getDiaryDateMemberTags(userToken, clubId, memberId);
|
||||
res.status(200).json(tags);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Error retrieving diary date tags" });
|
||||
}
|
||||
}
|
||||
|
||||
export const addDiaryDateTag = async (req, res) => {
|
||||
console.log("addDiaryDateTag");
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId } = req.params;
|
||||
const { dateId, memberId, tag } = req.body;
|
||||
const tags = await diaryDateTagService.addDiaryDateTag(userToken, clubId, dateId, memberId, tag);
|
||||
res.status(201).json(tags);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Error adding diary date tag" });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ const getClubMembers = async(req, res) => {
|
||||
if (showAll === null) {
|
||||
showAll = false;
|
||||
}
|
||||
console.log('-------------- clubid:', clubId);
|
||||
res.status(200).json(await MemberService.getClubMembers(userToken, clubId, showAll));
|
||||
} catch(error) {
|
||||
console.log('[getClubMembers] - Error: ', error);
|
||||
@@ -34,11 +33,12 @@ const getWaitingApprovals = async(req, res) => {
|
||||
|
||||
const setClubMembers = async (req, res) => {
|
||||
try {
|
||||
const { id: memberId, firstname: firstName, lastname: lastName, street, city, birthdate, phone, email, active, testMembership } = req.body;
|
||||
const { id: memberId, firstname: firstName, lastname: lastName, street, city, birthdate, phone, email, active,
|
||||
testMembership, picsInInternetAllowed } = req.body;
|
||||
const { id: clubId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
const addResult = await MemberService.setClubMember(userToken, clubId, memberId, firstName, lastName, street, city, birthdate,
|
||||
phone, email, active, testMembership);
|
||||
phone, email, active, testMembership, picsInInternetAllowed);
|
||||
res.status(addResult.status || 500).json(addResult.response);
|
||||
} catch (error) {
|
||||
console.error('[setClubMembers] - Error:', error);
|
||||
|
||||
@@ -116,6 +116,11 @@ const Member = sequelize.define('User', {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
default: false,
|
||||
},
|
||||
picsInInternetAllowed: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
default: false,
|
||||
}
|
||||
}, {
|
||||
underscored: true,
|
||||
|
||||
@@ -111,6 +111,9 @@ PredefinedActivity.hasMany(GroupActivity, { foreignKey: 'predefinedActivityId',
|
||||
DiaryTag.hasMany(DiaryDateTag, { foreignKey: 'tagId', as: 'diaryDateTags' });
|
||||
DiaryDateTag.belongsTo(DiaryTag, { foreignKey: 'tagId', as: 'tag' });
|
||||
|
||||
DiaryMemberTag.belongsTo(DiaryDate, { foreignKey: 'diaryDateId', as: 'diaryDates' });
|
||||
DiaryDate.hasMany(DiaryMemberTag, { foreignKey: 'diaryDateId', as: 'diaryMemberTags' });
|
||||
|
||||
export {
|
||||
User,
|
||||
Log,
|
||||
|
||||
@@ -11,11 +11,13 @@ import { authenticate } from '../middleware/authMiddleware.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/group', authenticate, addGroupActivity);
|
||||
router.post('/:clubId/', authenticate, createDiaryDateActivity);
|
||||
router.put('/:clubId/:id/order', authenticate, updateDiaryDateActivityOrder);
|
||||
router.put('/:clubId/:id', authenticate, updateDiaryDateActivity);
|
||||
router.delete('/:clubId/:id', authenticate, deleteDiaryDateActivity);
|
||||
router.get('/:clubId/:diaryDateId', authenticate, getDiaryDateActivities);
|
||||
router.use(authenticate);
|
||||
|
||||
router.post('/group', addGroupActivity);
|
||||
router.post('/:clubId/', createDiaryDateActivity);
|
||||
router.put('/:clubId/:id/order', updateDiaryDateActivityOrder);
|
||||
router.put('/:clubId/:id', updateDiaryDateActivity);
|
||||
router.delete('/:clubId/:id', deleteDiaryDateActivity);
|
||||
router.get('/:clubId/:diaryDateId', getDiaryDateActivities);
|
||||
|
||||
export default router;
|
||||
|
||||
12
backend/routes/diaryDateTagRoutes.js
Normal file
12
backend/routes/diaryDateTagRoutes.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import express from 'express';
|
||||
import { getDiaryDateMemberTags, addDiaryDateTag } from '../controllers/diaryDateTagController.js';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(authenticate);
|
||||
|
||||
router.get('/:clubId/:memberId', getDiaryDateMemberTags);
|
||||
router.post('/:clubId', addDiaryDateTag);
|
||||
|
||||
export default router;
|
||||
@@ -25,6 +25,7 @@ import matchRoutes from './routes/matchRoutes.js';
|
||||
import Season from './models/Season.js';
|
||||
import Location from './models/Location.js';
|
||||
import groupRoutes from './routes/groupRoutes.js';
|
||||
import diaryDateTagRoutes from './routes/diaryDateTagRoutes.js';
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3000;
|
||||
@@ -49,6 +50,7 @@ app.use('/api/predefined-activities', predefinedActivityRoutes);
|
||||
app.use('/api/diary-date-activities', diaryDateActivityRoutes);
|
||||
app.use('/api/matches', matchRoutes);
|
||||
app.use('/api/group', groupRoutes);
|
||||
app.use('/api/diarydatetags', diaryDateTagRoutes);
|
||||
|
||||
app.use(express.static(path.join(__dirname, '../frontend/dist')));
|
||||
|
||||
|
||||
60
backend/services/diaryDateTagService.js
Normal file
60
backend/services/diaryDateTagService.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import DiaryDate from "../models/DiaryDates.js";
|
||||
import DiaryDateTag from "../models/DiaryDateTag.js";
|
||||
import DiaryMemberTag from "../models/DiaryMemberTag.js";
|
||||
import { DiaryTag } from "../models/DiaryTag.js";
|
||||
import Member from "../models/Member.js";
|
||||
import { checkAccess } from '../utils/userUtils.js';
|
||||
import { Op, literal } from "sequelize";
|
||||
|
||||
class DiaryDateTagService {
|
||||
async getDiaryDateMemberTags(userToken, clubId, memberId) {
|
||||
await checkAccess(userToken, clubId);
|
||||
return await DiaryTag.findAll({
|
||||
include: [
|
||||
{
|
||||
model: DiaryMemberTag,
|
||||
as: 'diaryMemberTags',
|
||||
where: { memberId },
|
||||
required: true,
|
||||
include: [
|
||||
{
|
||||
model: DiaryDate,
|
||||
as: 'diaryDates',
|
||||
required: true,
|
||||
on: {
|
||||
'$diaryMemberTags.diary_date_id$': { [Op.eq]: literal('`diaryMemberTags->diaryDates`.`id`') }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
order: [[literal('`diaryMemberTags->diaryDates`.`date`'), 'DESC']],
|
||||
group: ['DiaryTag.id'],
|
||||
having: literal('COUNT(DISTINCT `diaryMemberTags->diaryDates`.`id`) >= 1'),
|
||||
});
|
||||
}
|
||||
|
||||
async addDiaryDateTag(userToken, clubId, diaryDateId, memberId, tag) {
|
||||
console.log(userToken, clubId, diaryDateId, memberId, tag);
|
||||
await checkAccess(userToken, clubId);
|
||||
const tagObject = await DiaryTag.findOne({ where: { id: tag.id } });
|
||||
if (!tagObject) {
|
||||
throw new Error('Tag not found');
|
||||
}
|
||||
const diaryDate = await DiaryDate.findByPk(diaryDateId);
|
||||
if (!diaryDate) {
|
||||
throw new Error('DiaryDate not found');
|
||||
}
|
||||
const member = await Member.findByPk(memberId);
|
||||
if (!member) {
|
||||
throw new Error('Member not found');
|
||||
}
|
||||
const existingTag = await DiaryMemberTag.findOne({ where: { diaryDateId, memberId, tagId: tag.id } });
|
||||
if (!existingTag) {
|
||||
await DiaryMemberTag.create({ diaryDateId, memberId, tagId: tag.id });
|
||||
}
|
||||
return await this.getDiaryDateMemberTags(userToken, clubId, memberId);
|
||||
}
|
||||
}
|
||||
|
||||
export default new DiaryDateTagService();
|
||||
@@ -53,7 +53,8 @@ class MemberService {
|
||||
});
|
||||
}
|
||||
|
||||
async setClubMember(userToken, clubId, memberId, firstName, lastName, street, city, birthdate, phone, email, active = true, testMembership = false) {
|
||||
async setClubMember(userToken, clubId, memberId, firstName, lastName, street, city, birthdate, phone, email, active = true, testMembership = false,
|
||||
picsInInternetAllowed = false) {
|
||||
try {
|
||||
console.log('[setClubMembers] - Check access');
|
||||
await checkAccess(userToken, clubId);
|
||||
@@ -73,7 +74,8 @@ class MemberService {
|
||||
member.phone = phone;
|
||||
member.email = email;
|
||||
member.active = active;
|
||||
member.testMembership = testMembership
|
||||
member.testMembership = testMembership;
|
||||
member.picsInInternetAllowed = picsInInternetAllowed;
|
||||
await member.save();
|
||||
} else {
|
||||
await Member.create({
|
||||
@@ -86,7 +88,8 @@ class MemberService {
|
||||
email: email,
|
||||
clubId: clubId,
|
||||
active: active,
|
||||
testMembership: testMembership
|
||||
testMembership: testMembership,
|
||||
picsInInternetAllowed: picsInInternetAllowed,
|
||||
});
|
||||
}
|
||||
console.log('[setClubMembers] - return response');
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
member.firstName
|
||||
}} {{
|
||||
member.lastName }}</span>
|
||||
<span @click="openNotesModal(member)" class="clickable">📝</span>
|
||||
<span v-if="false" @click="openNotesModal(member)" class="clickable">📝</span>
|
||||
<span @click="showPic(member)" class="img-icon" v-if="member.hasImage">🖼</span>
|
||||
<span class="pointer" @click="openTagInfos(member)">ℹ️</span>
|
||||
</li>
|
||||
@@ -192,7 +192,49 @@
|
||||
<div class="modal-content">
|
||||
<span class="close" @click="closeTagHistoryModal">×</span>
|
||||
<h3>Tag-Historie {{ tagHistoryMember.firstName }} {{ tagHistoryMember.lastName }}</h3>
|
||||
Diese Funktion ist noch nicht implementiert
|
||||
<div>
|
||||
<multiselect v-model="selectedMemberDayTags" :options="selectedActivityTags"
|
||||
placeholder="Tags auswählen" label="name" track-by="id" multiple :close-on-select="false"
|
||||
:taggable="false" @select="addNewTagForDay" :allow-empty="false" />
|
||||
</div>
|
||||
<div v-if="tagHistory">
|
||||
<div v-for="tag in tagHistory" :key="tag.id">
|
||||
<div class="tag-headerplement">{{ tag.name }}</div>
|
||||
<ul class="tag-list">
|
||||
<li v-for="entry in tag.diaryMemberTags" :key="entry.id">{{
|
||||
getFormattedDate(entry.diaryDates.date) }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showNotesModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close" @click="closeNotesModal">×</span>
|
||||
<h3>Notizen für {{ noteMember.firstName }} {{ noteMember.lastName }}</h3>
|
||||
<div>Telefon-Nr.: {{ noteMember.phone }}</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-left">
|
||||
<img v-if="noteMember.imageUrl" :src="noteMember.imageUrl" alt="Mitgliedsbild"
|
||||
style="width: 250px; height: 250px; object-fit: cover;" />
|
||||
</div>
|
||||
<div class="modal-right">
|
||||
<multiselect v-model="selectedMemberTags" :options="availableTags" placeholder="Tags auswählen"
|
||||
label="name" track-by="id" multiple :close-on-select="false"
|
||||
@tag="addNewTagForMemberFromSelection" @remove="removeMemberDayTag" allow-empty="false" />
|
||||
<div>
|
||||
<textarea v-model="newNoteContent" placeholder="Neue Notiz" rows="4" cols="30"></textarea>
|
||||
<button @click="addMemberNote">Hinzufügen</button>
|
||||
</div>
|
||||
<ul>
|
||||
<li v-for="note in notes" :key="note.id">
|
||||
<button @click="deleteNote(note.id)" class="cancel-action">Löschen</button>
|
||||
{{ note.content }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showImage" class="memberImage">
|
||||
@@ -231,6 +273,7 @@ export default {
|
||||
showNotesModal: false,
|
||||
selectedActivityTags: [],
|
||||
selectedMemberTags: [],
|
||||
selectedMemberDayTags: [],
|
||||
availableTags: [],
|
||||
previousActivityTags: [],
|
||||
previousMemberTags: [],
|
||||
@@ -259,6 +302,7 @@ export default {
|
||||
doMemberTagUpdates: true,
|
||||
showTagHistoryModal: false,
|
||||
tagHistoryMember: null,
|
||||
tagHistory: null,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
@@ -887,13 +931,34 @@ export default {
|
||||
this.editingGroupId = null;
|
||||
},
|
||||
async openTagInfos(member) {
|
||||
if (!member) {
|
||||
console.warn("Member is undefined or null");
|
||||
return;
|
||||
}
|
||||
this.showTagHistoryModal = true;
|
||||
this.tagHistoryMember = member;
|
||||
const tags = await apiClient.get(`/diarydatetags/${this.currentClub}/${member.id}`);
|
||||
this.tagHistory = tags.data;
|
||||
},
|
||||
closeTagHistoryModal() {
|
||||
this.showTagHistoryModal = false;
|
||||
this.tagHistoryMember = null;
|
||||
},
|
||||
async addNewTagForDay(tag) {
|
||||
await apiClient.post(`/diarydatetags/${this.currentClub}`, {
|
||||
dateId:this.date.id,
|
||||
memberId: this.tagHistoryMember.id,
|
||||
tag: tag,
|
||||
});
|
||||
},
|
||||
async removeDayTag(tag) {
|
||||
},
|
||||
async addNewDayTagFromInput(event) {
|
||||
const tag = event.target.value;
|
||||
if (tag) {
|
||||
await this.addNewTagForDay(tag);
|
||||
}
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
await this.init();
|
||||
@@ -1162,4 +1227,12 @@ img {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tag-header {
|
||||
margin: 0
|
||||
}
|
||||
|
||||
.tag-list {
|
||||
margin: 0 0 1em 1.5em;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<label><span>Telefon-Nr.:</span> <input type="text" v-model="newPhone"></label>
|
||||
<label><span>Email-Adresse:</span> <input type="email" v-model="newEmail"></label>
|
||||
<label><span>Aktiv:</span> <input type="checkbox" v-model="newActive"></label>
|
||||
<label><span>Pics in Internet erlaubt:</span> <input type="checkbox" v-model="newPicsInInternetAllowed"></label>
|
||||
<label><span>Testmitgliedschaft:</span> <input type="checkbox" v-model="testMembership" </label>
|
||||
<label><span>Bild:</span> <input type="file" @change="onFileSelected"></label>
|
||||
<div v-if="memberImagePreview">
|
||||
@@ -41,7 +42,7 @@
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Bild</th>
|
||||
<th>Bild (Inet?)</th>
|
||||
<th>Testm.</th>
|
||||
<th>Name, Vorname</th>
|
||||
<th>Adresse</th>
|
||||
@@ -58,6 +59,7 @@
|
||||
<img v-if="member.imageUrl" :src="member.imageUrl" alt="Mitgliedsbild"
|
||||
style="max-width: 50px; max-height: 50px;"
|
||||
@click.stop="openImageModal(member.imageUrl)">
|
||||
<span>{{ member.picsInInternetAllowed ? '✓' : '' }}</span>
|
||||
</td>
|
||||
<td>{{ member.testMembership ? '*' : '' }}</td>
|
||||
<td>{{ member.lastName }}, {{ member.firstName }}</td>
|
||||
@@ -132,6 +134,7 @@ export default {
|
||||
selectedImageUrl: null,
|
||||
testMembership: false,
|
||||
showInactiveMembers: false,
|
||||
newPicsInInternetAllowed: false
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
@@ -172,6 +175,8 @@ export default {
|
||||
this.newPhone = '';
|
||||
this.newEmail = '';
|
||||
this.newActive = true;
|
||||
this.newPicsInInternetAllowed = false;
|
||||
this.testMembership = true;
|
||||
this.memberImage = null;
|
||||
this.memberImagePreview = null;
|
||||
},
|
||||
@@ -197,7 +202,8 @@ export default {
|
||||
email: this.newEmail,
|
||||
active: this.newActive,
|
||||
id: this.memberToEdit ? this.memberToEdit.id : null,
|
||||
testMembership: this.testMembership
|
||||
testMembership: this.testMembership,
|
||||
picsInInternetAllowed: this.newPicsInInternetAllowed,
|
||||
};
|
||||
|
||||
let response;
|
||||
@@ -241,6 +247,7 @@ export default {
|
||||
const date = new Date(member.birthDate);
|
||||
this.newBirthdate = date.toISOString().split('T')[0];
|
||||
this.testMembership = member.testMembership;
|
||||
this.newPicsInInternetAllowed = member.picsInInternetAllowed;
|
||||
try {
|
||||
const response = await apiClient.get(`/clubmembers/image/${member.id}`, {
|
||||
responseType: 'blob'
|
||||
|
||||
Reference in New Issue
Block a user