feat: Implement member self-service features for profile change requests and event responses

- Added MemberProfileChangeRequest and MemberEventResponse models with associations.
- Created API endpoints for member dashboard, profile change requests, event responses, and training attendance updates.
- Developed MemberHomeView component to display member-specific information and actions.
- Updated routing to include new member dashboard and profile change request functionalities.
- Added SQL migration scripts for new database tables related to member self-service.
- Enhanced login functionality to redirect members based on their roles and club associations.
This commit is contained in:
Torsten Schulz (local)
2026-07-24 07:53:05 +02:00
parent 3ac4039097
commit 63a5db1196
12 changed files with 398 additions and 21 deletions

View File

@@ -0,0 +1,17 @@
import { DataTypes } from 'sequelize';
import sequelize from '../database.js';
const MemberEventResponse = sequelize.define('MemberEventResponse', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
clubId: { type: DataTypes.INTEGER, allowNull: false, field: 'club_id' },
eventId: { type: DataTypes.INTEGER, allowNull: false, field: 'event_id' },
memberId: { type: DataTypes.INTEGER, allowNull: false, field: 'member_id' },
status: { type: DataTypes.ENUM('attending', 'declined'), allowNull: false },
}, {
tableName: 'member_event_responses',
underscored: true,
timestamps: true,
indexes: [{ unique: true, fields: ['event_id', 'member_id'] }, { fields: ['club_id', 'event_id'] }],
});
export default MemberEventResponse;

View File

@@ -0,0 +1,21 @@
import { DataTypes } from 'sequelize';
import sequelize from '../database.js';
const MemberProfileChangeRequest = sequelize.define('MemberProfileChangeRequest', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
clubId: { type: DataTypes.INTEGER, allowNull: false, field: 'club_id' },
memberId: { type: DataTypes.INTEGER, allowNull: false, field: 'member_id' },
requestedByUserId: { type: DataTypes.INTEGER, allowNull: false, field: 'requested_by_user_id' },
requestedChanges: { type: DataTypes.JSON, allowNull: false, field: 'requested_changes' },
status: { type: DataTypes.ENUM('open', 'approved', 'rejected'), allowNull: false, defaultValue: 'open' },
reviewNote: { type: DataTypes.TEXT, allowNull: true, field: 'review_note' },
reviewedByUserId: { type: DataTypes.INTEGER, allowNull: true, field: 'reviewed_by_user_id' },
reviewedAt: { type: DataTypes.DATE, allowNull: true, field: 'reviewed_at' },
}, {
tableName: 'member_profile_change_requests',
underscored: true,
timestamps: true,
indexes: [{ fields: ['club_id', 'status'] }, { fields: ['member_id', 'status'] }],
});
export default MemberProfileChangeRequest;

View File

@@ -93,6 +93,8 @@ import ClubCommunicationDeliveryLog from './ClubCommunicationDeliveryLog.js';
import ClubCommunicationTemplate from './ClubCommunicationTemplate.js';
import ClubDistributionGroup from './ClubDistributionGroup.js';
import ClubDistributionGroupMember from './ClubDistributionGroupMember.js';
import MemberProfileChangeRequest from './MemberProfileChangeRequest.js';
import MemberEventResponse from './MemberEventResponse.js';
// Official tournaments relations
OfficialTournament.hasMany(OfficialCompetition, { foreignKey: 'tournamentId', as: 'competitions' });
OfficialCompetition.belongsTo(OfficialTournament, { foreignKey: 'tournamentId', as: 'tournament' });
@@ -500,6 +502,20 @@ ClubSepaMandate.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
Member.hasMany(ClubSepaMandate, { foreignKey: 'memberId', as: 'sepaMandates' });
ClubSepaMandate.belongsTo(Member, { foreignKey: 'memberId', as: 'member', constraints: false });
Club.hasMany(MemberProfileChangeRequest, { foreignKey: 'clubId', as: 'memberProfileChangeRequests' });
MemberProfileChangeRequest.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
Member.hasMany(MemberProfileChangeRequest, { foreignKey: 'memberId', as: 'profileChangeRequests' });
MemberProfileChangeRequest.belongsTo(Member, { foreignKey: 'memberId', as: 'member' });
User.hasMany(MemberProfileChangeRequest, { foreignKey: 'requestedByUserId', as: 'requestedMemberProfileChanges' });
MemberProfileChangeRequest.belongsTo(User, { foreignKey: 'requestedByUserId', as: 'requestedByUser' });
Club.hasMany(MemberEventResponse, { foreignKey: 'clubId', as: 'memberEventResponses' });
MemberEventResponse.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
CalendarEvent.hasMany(MemberEventResponse, { foreignKey: 'eventId', as: 'memberResponses' });
MemberEventResponse.belongsTo(CalendarEvent, { foreignKey: 'eventId', as: 'event' });
Member.hasMany(MemberEventResponse, { foreignKey: 'memberId', as: 'eventResponses' });
MemberEventResponse.belongsTo(Member, { foreignKey: 'memberId', as: 'member' });
Club.hasMany(ClubPaymentClaim, { foreignKey: 'clubId', as: 'paymentClaims' });
ClubPaymentClaim.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
Member.hasMany(ClubPaymentClaim, { foreignKey: 'memberId', as: 'paymentClaims' });
@@ -688,5 +704,7 @@ export {
ClubCommunicationDeliveryLog,
ClubCommunicationTemplate,
ClubDistributionGroup,
MemberProfileChangeRequest,
MemberEventResponse,
ClubDistributionGroupMember,
};