Added dayplanning
This commit is contained in:
73
backend/controllers/diaryDateActivityController.js
Normal file
73
backend/controllers/diaryDateActivityController.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import diaryDateActivityService from '../services/diaryDateActivityService.js';
|
||||
|
||||
export const createDiaryDateActivity = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId } = req.params;
|
||||
const { diaryDateId, activity, duration, durationText, orderId } = req.body;
|
||||
const activityItem = await diaryDateActivityService.createActivity(userToken, clubId, {
|
||||
diaryDateId,
|
||||
activity,
|
||||
duration,
|
||||
durationText,
|
||||
orderId,
|
||||
});
|
||||
res.status(201).json(activityItem);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ error: 'Error creating activity' });
|
||||
}
|
||||
};
|
||||
|
||||
export const updateDiaryDateActivity = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId, id } = req.params;
|
||||
const { predefinedActivityId, customActivityName, duration, durationText, orderId } = req.body;
|
||||
const updatedActivity = await diaryDateActivityService.updateActivity(userToken, clubId, id, {
|
||||
predefinedActivityId,
|
||||
customActivityName,
|
||||
duration,
|
||||
durationText,
|
||||
orderId,
|
||||
});
|
||||
res.status(200).json(updatedActivity);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Error updating activity' });
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteDiaryDateActivity = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId, id } = req.params;
|
||||
await diaryDateActivityService.deleteActivity(userToken, clubId, id);
|
||||
res.status(200).json({ message: 'Activity deleted' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Error deleting activity' });
|
||||
}
|
||||
};
|
||||
|
||||
export const updateDiaryDateActivityOrder = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId, id } = req.params;
|
||||
const { orderId } = req.body;
|
||||
const updatedActivity = await diaryDateActivityService.updateActivityOrder(userToken, clubId, id, orderId);
|
||||
res.status(200).json(updatedActivity);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ error: 'Error updating activity order' });
|
||||
}
|
||||
};
|
||||
|
||||
export const getDiaryDateActivities = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId, diaryDateId } = req.params;
|
||||
const activities = await diaryDateActivityService.getActivities(userToken, clubId, diaryDateId);
|
||||
res.status(200).json(activities);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Error getting activities' });
|
||||
}
|
||||
}
|
||||
@@ -58,8 +58,8 @@ const addMemberTag = async (req, res) => {
|
||||
const removeMemberNote = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { memberId, diaryDateId, content } = req.body;
|
||||
await DiaryMemberService.removeNoteFromMember(userToken, req.params.clubId, diaryDateId, memberId, content);
|
||||
const { clubId, noteId } = req.params;
|
||||
await DiaryMemberService.removeNoteFromMember(userToken, clubId, noteId);
|
||||
const notes = await DiaryMemberService.getNotesForMember(userToken, req.params.clubId, diaryDateId, memberId);
|
||||
res.status(200).json(notes);
|
||||
} catch (error) {
|
||||
|
||||
48
backend/controllers/predefinedActivityController.js
Normal file
48
backend/controllers/predefinedActivityController.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import predefinedActivityService from '../services/predefinedActivityService.js';
|
||||
|
||||
export const createPredefinedActivity = async (req, res) => {
|
||||
try {
|
||||
const { name, description, durationText, duration } = req.body;
|
||||
const predefinedActivity = await predefinedActivityService.createPredefinedActivity({ name, description, durationText, duration });
|
||||
res.status(201).json(predefinedActivity);
|
||||
} catch (error) {
|
||||
console.error('[createPredefinedActivity] - Error:', error);
|
||||
res.status(500).json({ error: 'Error creating predefined activity' });
|
||||
}
|
||||
};
|
||||
|
||||
export const getAllPredefinedActivities = async (req, res) => {
|
||||
try {
|
||||
const predefinedActivities = await predefinedActivityService.getAllPredefinedActivities();
|
||||
res.status(200).json(predefinedActivities);
|
||||
} catch (error) {
|
||||
console.error('[getAllPredefinedActivities] - Error:', error);
|
||||
res.status(500).json({ error: 'Error fetching predefined activities' });
|
||||
}
|
||||
};
|
||||
|
||||
export const getPredefinedActivityById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const predefinedActivity = await predefinedActivityService.getPredefinedActivityById(id);
|
||||
if (!predefinedActivity) {
|
||||
return res.status(404).json({ error: 'Predefined activity not found' });
|
||||
}
|
||||
res.status(200).json(predefinedActivity);
|
||||
} catch (error) {
|
||||
console.error('[getPredefinedActivityById] - Error:', error);
|
||||
res.status(500).json({ error: 'Error fetching predefined activity' });
|
||||
}
|
||||
};
|
||||
|
||||
export const updatePredefinedActivity = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { name, description, durationText, duration } = req.body;
|
||||
const updatedActivity = await predefinedActivityService.updatePredefinedActivity(id, { name, description, durationText, duration });
|
||||
res.status(200).json(updatedActivity);
|
||||
} catch (error) {
|
||||
console.error('[updatePredefinedActivity] - Error:', error);
|
||||
res.status(500).json({ error: 'Error updating predefined activity' });
|
||||
}
|
||||
};
|
||||
52
backend/models/DiaryDateActivity.js
Normal file
52
backend/models/DiaryDateActivity.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
import DiaryDate from './DiaryDates.js';
|
||||
import PredefinedActivity from './PredefinedActivity.js';
|
||||
|
||||
const DiaryDateActivity = sequelize.define('DiaryDateActivity', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
diaryDateId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: DiaryDate,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
predefinedActivityId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: PredefinedActivity,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'SET NULL',
|
||||
},
|
||||
customActivityName: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
duration: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
},
|
||||
durationText: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
orderId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
}, {
|
||||
tableName: 'diary_date_activities',
|
||||
underscored: true,
|
||||
timestamps: true,
|
||||
});
|
||||
|
||||
export default DiaryDateActivity;
|
||||
@@ -30,5 +30,4 @@ const DiaryDate = sequelize.define('DiaryDate', {
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
|
||||
export default DiaryDate;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
import Member from './Member.js';
|
||||
import DiaryDate from './DiaryDates.js';
|
||||
import { encryptData, decryptData } from '../utils/encrypt.js';
|
||||
|
||||
const DiaryMemberNote = sequelize.define('DiaryMemberNote', {
|
||||
memberId: {
|
||||
@@ -25,6 +26,14 @@ const DiaryMemberNote = sequelize.define('DiaryMemberNote', {
|
||||
content: {
|
||||
type: DataTypes.STRING(4096),
|
||||
allowNull: false,
|
||||
set(value) {
|
||||
const encryptedValue = encryptData(value);
|
||||
this.setDataValue('content', encryptedValue);
|
||||
},
|
||||
get() {
|
||||
const encryptedValue = this.getDataValue('content');
|
||||
return decryptData(encryptedValue);
|
||||
}
|
||||
},
|
||||
}, {
|
||||
underscored: true,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
import Member from './Member.js';
|
||||
import DiaryDate from './DiaryDates.js';
|
||||
import { encryptData, decryptData } from '../utils/encrypt.js';
|
||||
|
||||
const Participant = sequelize.define('Participant', {
|
||||
id: {
|
||||
@@ -29,6 +30,18 @@ const Participant = sequelize.define('Participant', {
|
||||
notes: {
|
||||
type: DataTypes.STRING(4096),
|
||||
allowNull: true,
|
||||
set(value) {
|
||||
const encryptedValue = encryptData(value);
|
||||
this.setDataValue('notes', encryptedValue);
|
||||
},
|
||||
get() {
|
||||
try {
|
||||
const encryptedValue = this.getDataValue('notes');
|
||||
return encryptData ? decryptData(encryptedValue) : null;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
underscored: true,
|
||||
|
||||
32
backend/models/PredefinedActivity.js
Normal file
32
backend/models/PredefinedActivity.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
|
||||
const PredefinedActivity = sequelize.define('PredefinedActivity', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
},
|
||||
durationText: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
duration: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
},
|
||||
}, {
|
||||
tableName: 'predefined_activities',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
});
|
||||
|
||||
export default PredefinedActivity;
|
||||
@@ -12,6 +12,8 @@ import MemberNote from './MemberNote.js';
|
||||
import DiaryDateTag from './DiaryDateTag.js';
|
||||
import DiaryMemberNote from './DiaryMemberNote.js';
|
||||
import DiaryMemberTag from './DiaryMemberTag.js';
|
||||
import PredefinedActivity from './PredefinedActivity.js';
|
||||
import DiaryDateActivity from './DiaryDateActivity.js';
|
||||
|
||||
User.hasMany(Log, { foreignKey: 'userId' });
|
||||
Log.belongsTo(User, { foreignKey: 'userId' });
|
||||
@@ -45,10 +47,16 @@ DiaryTag.belongsToMany(DiaryDate, { through: DiaryDateTag, as: 'diaryDates', for
|
||||
|
||||
DiaryDate.belongsToMany(Member, { through: DiaryMemberNote, as: 'noteMembers', foreignKey: 'diaryDateId' });
|
||||
Member.belongsToMany(DiaryDate, { through: DiaryMemberNote, as: 'noteDates', foreignKey: 'memberId' });
|
||||
|
||||
|
||||
DiaryTag.hasMany(DiaryMemberTag, { foreignKey: 'tagId', as: 'diaryMemberTags' });
|
||||
DiaryMemberTag.belongsTo(DiaryTag, { foreignKey: 'tagId', as: 'tag' });
|
||||
|
||||
DiaryDate.hasMany(DiaryDateActivity, { foreignKey: 'diaryDateId', as: 'diaryDateActivities' });
|
||||
DiaryDateActivity.belongsTo(DiaryDate, { foreignKey: 'diaryDateId', as: 'diaryDate' });
|
||||
|
||||
PredefinedActivity.hasMany(DiaryDateActivity, { foreignKey: 'predefinedActivityId', as: 'predefinedActivities' });
|
||||
DiaryDateActivity.belongsTo(PredefinedActivity, { foreignKey: 'predefinedActivityId', as: 'predefinedActivity' });
|
||||
|
||||
export {
|
||||
User,
|
||||
Log,
|
||||
@@ -65,4 +73,6 @@ export {
|
||||
DiaryDateTag,
|
||||
DiaryMemberNote,
|
||||
DiaryMemberTag,
|
||||
PredefinedActivity,
|
||||
DiaryDateActivity,
|
||||
};
|
||||
|
||||
19
backend/routes/diaryDateActivityRoutes.js
Normal file
19
backend/routes/diaryDateActivityRoutes.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import express from 'express';
|
||||
import {
|
||||
createDiaryDateActivity,
|
||||
updateDiaryDateActivity,
|
||||
deleteDiaryDateActivity,
|
||||
updateDiaryDateActivityOrder,
|
||||
getDiaryDateActivities,
|
||||
} from '../controllers/diaryDateActivityController.js';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
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);
|
||||
|
||||
export default router;
|
||||
@@ -9,7 +9,7 @@ router.get('/:clubId/tag', authenticate, getMemberTags);
|
||||
router.get('/:clubId/note', authenticate, getMemberNotes);
|
||||
router.post('/:clubId/note', authenticate, addMemberNote);
|
||||
router.post('/:clubId/tag', authenticate, addMemberTag);
|
||||
router.post('/:clubId/note/remove', authenticate, removeMemberNote);
|
||||
router.delete('/:clubId/note/:noteId', authenticate, removeMemberNote);
|
||||
router.post('/:clubId/tag/remove', authenticate, removeMemberTag);
|
||||
|
||||
export default router;
|
||||
|
||||
16
backend/routes/predefinedActivityRoutes.js
Normal file
16
backend/routes/predefinedActivityRoutes.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import express from 'express';
|
||||
import {
|
||||
createPredefinedActivity,
|
||||
getAllPredefinedActivities,
|
||||
getPredefinedActivityById,
|
||||
updatePredefinedActivity,
|
||||
} from '../controllers/predefinedActivityController.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/', createPredefinedActivity);
|
||||
router.get('/', getAllPredefinedActivities);
|
||||
router.get('/:id', getPredefinedActivityById);
|
||||
router.put('/:id', updatePredefinedActivity);
|
||||
|
||||
export default router;
|
||||
@@ -15,10 +15,14 @@ import activityRoutes from './routes/activityRoutes.js';
|
||||
import memberNoteRoutes from './routes/memberNoteRoutes.js';
|
||||
import diaryTagRoutes from './routes/diaryTagRoutes.js';
|
||||
import diaryNoteRoutes from './routes/diaryNoteRoutes.js';
|
||||
import diaryMemberRoutes from './routes/diaryMemberRoutes.js'; // Neue Route
|
||||
import diaryMemberRoutes from './routes/diaryMemberRoutes.js';
|
||||
import predefinedActivityRoutes from './routes/predefinedActivityRoutes.js';
|
||||
import PredefinedActivity from './models/PredefinedActivity.js';
|
||||
import DiaryDateActivity from './models/DiaryDateActivity.js';
|
||||
import diaryDateActivityRoutes from './routes/diaryDateActivityRoutes.js';
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3000;
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
@@ -35,6 +39,8 @@ app.use('/api/membernotes', memberNoteRoutes);
|
||||
app.use('/api/diarynotes', diaryNoteRoutes);
|
||||
app.use('/api/tags', diaryTagRoutes);
|
||||
app.use('/api/diarymember', diaryMemberRoutes); // Neue Route für Diary-Member-Funktionalität
|
||||
app.use('/api/predefined-activities', predefinedActivityRoutes);
|
||||
app.use('/api/diary-date-activities', diaryDateActivityRoutes);
|
||||
|
||||
app.use(express.static(path.join(__dirname, '../frontend/dist')));
|
||||
|
||||
@@ -61,6 +67,8 @@ app.get('*', (req, res) => {
|
||||
await DiaryDateTag.sync({ alter: true });
|
||||
await DiaryMemberTag.sync({ alter: true });
|
||||
await DiaryMemberNote.sync({ alter: true });
|
||||
await PredefinedActivity.sync({ alter: true });
|
||||
await DiaryDateActivity.sync({ alter: true });
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server is running on http://localhost:${port}`);
|
||||
|
||||
130
backend/services/diaryDateActivityService.js
Normal file
130
backend/services/diaryDateActivityService.js
Normal file
@@ -0,0 +1,130 @@
|
||||
import DiaryDateActivity from '../models/DiaryDateActivity.js';
|
||||
import PredefinedActivity from '../models/PredefinedActivity.js';
|
||||
import { checkAccess } from '../utils/userUtils.js';
|
||||
import { Op } from 'sequelize';
|
||||
|
||||
class DiaryDateActivityService {
|
||||
|
||||
async createActivity(userToken, clubId, data) {
|
||||
console.log('[DiaryDateActivityService::createActivity] - check user access');
|
||||
await checkAccess(userToken, clubId);
|
||||
console.log('[DiaryDateActivityService::createActivity] - add: ', data);
|
||||
let predefinedActivity = await PredefinedActivity.findOne({ where: { name: data.activity } });
|
||||
if (!predefinedActivity) {
|
||||
predefinedActivity = await PredefinedActivity.create({
|
||||
name: data.activity,
|
||||
description: '',
|
||||
duration: data.duration
|
||||
});
|
||||
}
|
||||
const maxOrderId = await DiaryDateActivity.max('orderId', {
|
||||
where: { diaryDateId: data.diaryDateId }
|
||||
});
|
||||
const newOrderId = maxOrderId !== null ? maxOrderId + 1 : 1;
|
||||
const { activity, ...restData } = data;
|
||||
restData.predefinedActivityId = predefinedActivity.id;
|
||||
restData.orderId = newOrderId;
|
||||
console.log('[DiaryDateActivityService::createActivity] - create diary date activity');
|
||||
return await DiaryDateActivity.create(restData);
|
||||
}
|
||||
|
||||
async updateActivity(userToken, clubId, id, data) {
|
||||
console.log('[DiaryDateActivityService::upateActivity] - check user access');
|
||||
await checkAccess(userToken, clubId);
|
||||
console.log('[DiaryDateActivityService::updateActivity] - load activit', id);
|
||||
const activity = await DiaryDateActivity.findByPk(id);
|
||||
if (!activity) {
|
||||
console.log('[DiaryDateActivityService::updateActivity] - activity not found');
|
||||
throw new Error('Activity not found');
|
||||
}
|
||||
console.log('[DiaryDateActivityService::updateActivity] - update activity');
|
||||
return await activity.update(data);
|
||||
}
|
||||
|
||||
async deleteActivity(userToken, clubId, id) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const activity = await DiaryDateActivity.findByPk(id);
|
||||
if (!activity) {
|
||||
throw new Error('Activity not found');
|
||||
}
|
||||
return await activity.destroy();
|
||||
}
|
||||
|
||||
async updateActivityOrder(userToken, clubId, id, newOrderId) {
|
||||
console.log(`[DiaryDateActivityService::updateActivityOrder] - Start update for activity id: ${id}`);
|
||||
console.log(`[DiaryDateActivityService::updateActivityOrder] - User token: ${userToken}, Club id: ${clubId}, New order id: ${newOrderId}`);
|
||||
|
||||
console.log('[DiaryDateActivityService::updateActivityOrder] - Checking user access');
|
||||
await checkAccess(userToken, clubId);
|
||||
console.log('[DiaryDateActivityService::updateActivityOrder] - User access confirmed');
|
||||
|
||||
console.log(`[DiaryDateActivityService::updateActivityOrder] - Finding activity with id: ${id}`);
|
||||
const activity = await DiaryDateActivity.findByPk(id);
|
||||
if (!activity) {
|
||||
console.error('[DiaryDateActivityService::updateActivityOrder] - Activity not found, throwing error');
|
||||
throw new Error('Activity not found');
|
||||
}
|
||||
console.log('[DiaryDateActivityService::updateActivityOrder] - Activity found:', activity);
|
||||
|
||||
const currentOrderId = activity.orderId;
|
||||
console.log(`[DiaryDateActivityService::updateActivityOrder] - Current order id: ${currentOrderId}`);
|
||||
|
||||
if (newOrderId < currentOrderId) {
|
||||
console.log(`[DiaryDateActivityService::updateActivityOrder] - Shifting items down. Moving activities with orderId between ${newOrderId} and ${currentOrderId - 1}`);
|
||||
await DiaryDateActivity.increment(
|
||||
{ orderId: 1 },
|
||||
{
|
||||
where: {
|
||||
diaryDateId: activity.diaryDateId,
|
||||
orderId: { [Op.gte]: newOrderId, [Op.lt]: currentOrderId },
|
||||
},
|
||||
}
|
||||
);
|
||||
console.log(`[DiaryDateActivityService::updateActivityOrder] - Items shifted down`);
|
||||
} else if (newOrderId > currentOrderId) {
|
||||
console.log(`[DiaryDateActivityService::updateActivityOrder] - Shifting items up. Moving activities with orderId between ${currentOrderId + 1} and ${newOrderId}`);
|
||||
await DiaryDateActivity.decrement(
|
||||
{ orderId: 1 },
|
||||
{
|
||||
where: {
|
||||
diaryDateId: activity.diaryDateId,
|
||||
orderId: { [Op.lte]: newOrderId, [Op.gt]: currentOrderId },
|
||||
},
|
||||
}
|
||||
);
|
||||
console.log(`[DiaryDateActivityService::updateActivityOrder] - Items shifted up`);
|
||||
} else {
|
||||
console.log('[DiaryDateActivityService::updateActivityOrder] - New order id is the same as the current order id. No shift required.');
|
||||
}
|
||||
|
||||
console.log(`[DiaryDateActivityService::updateActivityOrder] - Setting new order id for activity id: ${id}`);
|
||||
activity.orderId = newOrderId;
|
||||
|
||||
console.log('[DiaryDateActivityService::updateActivityOrder] - Saving activity with new order id');
|
||||
const savedActivity = await activity.save();
|
||||
console.log('[DiaryDateActivityService::updateActivityOrder] - Activity saved:', savedActivity);
|
||||
|
||||
console.log(`[DiaryDateActivityService::updateActivityOrder] - Finished update for activity id: ${id}`);
|
||||
return savedActivity;
|
||||
}
|
||||
|
||||
async getActivities(userToken, clubId, diaryDateId) {
|
||||
console.log('[DiaryDateActivityService::getActivities] - check user access');
|
||||
await checkAccess(userToken, clubId);
|
||||
console.log(`[DiaryDateActivityService::getActivities] - fetch activities for diaryDateId: ${diaryDateId}`);
|
||||
const activities = await DiaryDateActivity.findAll({
|
||||
where: { diaryDateId },
|
||||
order: [['orderId', 'ASC']],
|
||||
include: [
|
||||
{
|
||||
model: PredefinedActivity,
|
||||
as: 'predefinedActivity', // Assuming 'predefinedActivity' is the alias used in the model association
|
||||
}
|
||||
]
|
||||
});
|
||||
console.log(`[DiaryDateActivityService::getActivities] - found ${activities.length} activities`);
|
||||
return activities;
|
||||
}
|
||||
}
|
||||
|
||||
export default new DiaryDateActivityService();
|
||||
@@ -33,10 +33,9 @@ class DiaryMemberService {
|
||||
});
|
||||
}
|
||||
|
||||
async removeNoteFromMember(userToken, clubId, diaryDateId, memberId, content) {
|
||||
async removeNoteFromMember(userToken, clubId, noteId) {
|
||||
await checkAccess(userToken, clubId);
|
||||
|
||||
const note = await DiaryMemberNote.findOne({ where: { diaryDateId, memberId, content } });
|
||||
const note = await DiaryMemberNote.findOne({ where: { id: noteId } });
|
||||
if (note) {
|
||||
await note.destroy();
|
||||
} else {
|
||||
|
||||
45
backend/services/predefinedActivityService.js
Normal file
45
backend/services/predefinedActivityService.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import PredefinedActivity from '../models/PredefinedActivity.js';
|
||||
|
||||
class PredefinedActivityService {
|
||||
async createPredefinedActivity(data) {
|
||||
console.log('[PredefinedActivityService::createPredefinedActivity] - Creating predefined activity');
|
||||
return await PredefinedActivity.create({
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
durationText: data.durationText,
|
||||
duration: data.duration,
|
||||
});
|
||||
}
|
||||
|
||||
async updatePredefinedActivity(id, data) {
|
||||
console.log(`[PredefinedActivityService::updatePredefinedActivity] - Updating predefined activity with id: ${id}`);
|
||||
const activity = await PredefinedActivity.findByPk(id);
|
||||
if (!activity) {
|
||||
console.log('[PredefinedActivityService::updatePredefinedActivity] - Activity not found');
|
||||
throw new Error('Predefined activity not found');
|
||||
}
|
||||
return await activity.update({
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
durationText: data.durationText,
|
||||
duration: data.duration,
|
||||
});
|
||||
}
|
||||
|
||||
async getAllPredefinedActivities() {
|
||||
console.log('[PredefinedActivityService::getAllPredefinedActivities] - Fetching all predefined activities');
|
||||
return await PredefinedActivity.findAll();
|
||||
}
|
||||
|
||||
async getPredefinedActivityById(id) {
|
||||
console.log(`[PredefinedActivityService::getPredefinedActivityById] - Fetching predefined activity with id: ${id}`);
|
||||
const activity = await PredefinedActivity.findByPk(id);
|
||||
if (!activity) {
|
||||
console.log('[PredefinedActivityService::getPredefinedActivityById] - Activity not found');
|
||||
throw new Error('Predefined activity not found');
|
||||
}
|
||||
return activity;
|
||||
}
|
||||
}
|
||||
|
||||
export default new PredefinedActivityService();
|
||||
53
frontend/package-lock.json
generated
53
frontend/package-lock.json
generated
@@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"axios": "^1.7.3",
|
||||
"core-js": "^3.8.3",
|
||||
"sortablejs": "^1.15.3",
|
||||
"vue": "^3.2.13",
|
||||
"vue-multiselect": "^3.0.0",
|
||||
"vue-router": "^4.4.0",
|
||||
@@ -33,11 +34,18 @@
|
||||
"@babel/highlight": "^7.10.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
|
||||
"integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
|
||||
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@@ -129,9 +137,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.0.tgz",
|
||||
"integrity": "sha512-CzdIU9jdP0dg7HdyB+bHvDJGagUv+qtzZt5rYCWwW6tITNqV9odjp6Qu41gkG0ca5UfdDUWrKkiAnHHdGRnOrA==",
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz",
|
||||
"integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.25.6"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
@@ -139,6 +150,19 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz",
|
||||
"integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.24.8",
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
|
||||
@@ -1673,11 +1697,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.10",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz",
|
||||
"integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==",
|
||||
"version": "0.30.11",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz",
|
||||
"integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
@@ -2093,6 +2117,11 @@
|
||||
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/sortablejs": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.3.tgz",
|
||||
"integrity": "sha512-zdK3/kwwAK1cJgy1rwl1YtNTbRmc8qW/+vgXf75A7NHag5of4pyI6uK86ktmQETyWRH7IGaE73uZOOBcGxgqZg=="
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
|
||||
@@ -2201,6 +2230,14 @@
|
||||
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"axios": "^1.7.3",
|
||||
"core-js": "^3.8.3",
|
||||
"sortablejs": "^1.15.3",
|
||||
"vue": "^3.2.13",
|
||||
"vue-multiselect": "^3.0.0",
|
||||
"vue-router": "^4.4.0",
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
</div>
|
||||
<div>
|
||||
<label for="trainingStart">Trainingsbeginn:</label>
|
||||
<input type="time" id="trainingStart" v-model="trainingStart" />
|
||||
<input type="time" step="300" id="trainingStart" v-model="trainingStart" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="trainingEnd">Trainingsende:</label>
|
||||
<input type="time" id="trainingEnd" v-model="trainingEnd" />
|
||||
<input type="time" step="300" id="trainingEnd" v-model="trainingEnd" />
|
||||
</div>
|
||||
<button type="submit">Datum anlegen</button>
|
||||
</form>
|
||||
@@ -33,11 +33,11 @@
|
||||
<form @submit.prevent="updateTrainingTimes">
|
||||
<div>
|
||||
<label for="editTrainingStart">Trainingsbeginn:</label>
|
||||
<input type="time" id="editTrainingStart" v-model="trainingStart" />
|
||||
<input type="time" step="300" id="editTrainingStart" v-model="trainingStart" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="editTrainingEnd">Trainingsende:</label>
|
||||
<input type="time" id="editTrainingEnd" v-model="trainingEnd" />
|
||||
<input type="time" step="300" id="editTrainingEnd" v-model="trainingEnd" />
|
||||
</div>
|
||||
<button type="submit">Zeiten aktualisieren</button>
|
||||
</form>
|
||||
@@ -45,6 +45,52 @@
|
||||
|
||||
<div v-if="date !== 'new' && date !== null">
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<h3>Trainingsplan</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Uhrzeit</th>
|
||||
<th>Aktivität</th>
|
||||
<th>Länge / Gesamtzeit (Min)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody ref="sortableList">
|
||||
<tr v-for="(planItem, index) in trainingPlan" :key="planItem.id">
|
||||
<td class="drag-handle">☰</td>
|
||||
<td>{{ calculatePlanItemTime(index) }}</td>
|
||||
<td>{{ planItem.predefinedActivity.name }}</td>
|
||||
<td>
|
||||
<span @click="removePlanItem(planItem.id)" class="add-plan-item">-</span>
|
||||
{{ planItem.duration }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>{{ calculateNextTime }}</td>
|
||||
<td>
|
||||
<input type="text" v-model="newPlanItem.activity" @input="handleActivityInput"
|
||||
placeholder="Aktivität eingeben" />
|
||||
<div v-if="showDropdown" class="dropdown">
|
||||
<div v-for="activity in filteredPredefinedActivities" :key="activity.id"
|
||||
@click="selectPredefinedActivity(activity)">
|
||||
{{ activity.name }} ({{ activity.durationText || '' }} / {{
|
||||
activity.duration }} Minuten)
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" v-model="newPlanItem.durationInput" @input="calculateDuration"
|
||||
placeholder="z.B. 2x7 oder 3*5" style="width:10em" />
|
||||
<input type="number" v-model="newPlanItem.duration" placeholder="Minuten" />
|
||||
<span class="add-plan-item" @click="addPlanItem">+</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<h3>Teilnehmer</h3>
|
||||
<ul>
|
||||
@@ -52,13 +98,11 @@
|
||||
<label>
|
||||
<input type="checkbox" :value="member.id" @change="toggleParticipant(member.id)"
|
||||
:checked="isParticipant(member.id)">
|
||||
{{ member.firstName }} {{ member.lastName }}
|
||||
<span @click="openNotesModal(member)" class="clickable">{{ member.firstName }} {{
|
||||
member.lastName }}</span>
|
||||
</label>
|
||||
<button @click="openNotesModal(member)">Notizen</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h3>Aktivitäten</h3>
|
||||
<textarea v-model="newActivity"></textarea>
|
||||
<button @click="addActivity">Aktivität hinzufügen</button>
|
||||
@@ -100,6 +144,7 @@
|
||||
import { mapGetters } from 'vuex';
|
||||
import apiClient from '../apiClient.js';
|
||||
import Multiselect from 'vue-multiselect';
|
||||
import Sortable from 'sortablejs';
|
||||
|
||||
export default {
|
||||
name: 'DiaryView',
|
||||
@@ -125,22 +170,39 @@ export default {
|
||||
availableTags: [],
|
||||
previousActivityTags: [],
|
||||
previousMemberTags: [],
|
||||
trainingPlan: [],
|
||||
newPlanItem: {
|
||||
activity: '',
|
||||
duration: '',
|
||||
durationText: '',
|
||||
},
|
||||
predefinedActivities: [],
|
||||
showDropdown: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
selectedMemberTags(newTags, oldTags) {
|
||||
selectedMemberTags(newTags) {
|
||||
this.updateMemberTags(newTags);
|
||||
},
|
||||
selectedMemberNotes(newNotes, oldNotes) {
|
||||
const removedNotes = oldNotes.filter(note => !newNotes.includes(note));
|
||||
removedNotes.forEach(note => this.removeMemberNote(note.content));
|
||||
},
|
||||
selectedActivityTags(newTags, oldTags) {
|
||||
selectedActivityTags(newTags) {
|
||||
this.updateActivityTags(newTags);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['isAuthenticated', 'currentClub']),
|
||||
calculateNextTime() {
|
||||
let lastTime = this.trainingStart;
|
||||
for (let item of this.trainingPlan) {
|
||||
lastTime = this.addDurationToTime(lastTime, item.duration);
|
||||
}
|
||||
return lastTime;
|
||||
},
|
||||
filteredPredefinedActivities() {
|
||||
const input = this.newPlanItem.activity.toLowerCase();
|
||||
return this.predefinedActivities.filter(activity =>
|
||||
activity.name.toLowerCase().includes(input)
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async init() {
|
||||
@@ -148,16 +210,7 @@ export default {
|
||||
const response = await apiClient.get(`/diary/${this.currentClub}`);
|
||||
this.dates = response.data.map(entry => ({ id: entry.id, date: entry.date }));
|
||||
this.loadTags();
|
||||
}
|
||||
},
|
||||
handleEnterKey(event) {
|
||||
const newTagName = event.target.value.trim();
|
||||
if (newTagName) {
|
||||
if (this.showNotesModal) {
|
||||
this.addNewTagForMember(newTagName);
|
||||
} else {
|
||||
this.addNewTag(newTagName);
|
||||
}
|
||||
this.loadPredefinedActivities();
|
||||
}
|
||||
},
|
||||
async handleDateChange() {
|
||||
@@ -172,11 +225,16 @@ export default {
|
||||
id: tag.id,
|
||||
name: tag.name
|
||||
}));
|
||||
this.previousActivityTags = [...this.selectedActivityTags]; // Hier setzen
|
||||
this.previousActivityTags = [...this.selectedActivityTags]; // Hier setzen
|
||||
|
||||
await this.loadMembers();
|
||||
await this.loadParticipants(dateId);
|
||||
await this.loadActivities(dateId);
|
||||
this.trainingPlan = await apiClient
|
||||
.get(`/diary-date-activities/${this.currentClub}/${this.date.id}`)
|
||||
.then(response => response.data);
|
||||
|
||||
this.initializeSortable();
|
||||
} else {
|
||||
this.newDate = '';
|
||||
this.trainingStart = '';
|
||||
@@ -184,9 +242,12 @@ export default {
|
||||
this.participants = [];
|
||||
}
|
||||
},
|
||||
setCurrentDate() {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
this.newDate = today;
|
||||
initializeSortable() {
|
||||
const el = this.$refs.sortableList;
|
||||
Sortable.create(el, {
|
||||
handle: ".drag-handle",
|
||||
onEnd: this.onDragEnd,
|
||||
});
|
||||
},
|
||||
async createDate() {
|
||||
try {
|
||||
@@ -202,7 +263,6 @@ export default {
|
||||
this.trainingStart = '';
|
||||
this.trainingEnd = '';
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erstellen des Datums:', error);
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
}
|
||||
},
|
||||
@@ -236,6 +296,14 @@ export default {
|
||||
const response = await apiClient.get('/tags');
|
||||
this.availableTags = response.data;
|
||||
},
|
||||
async loadPredefinedActivities() {
|
||||
try {
|
||||
const response = await apiClient.get('/predefined-activities');
|
||||
this.predefinedActivities = response.data;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der vordefinierten Aktivitäten:', error);
|
||||
}
|
||||
},
|
||||
isParticipant(memberId) {
|
||||
return this.participants.includes(memberId);
|
||||
},
|
||||
@@ -284,7 +352,7 @@ export default {
|
||||
params: { diaryDateId, memberId }
|
||||
});
|
||||
this.selectedMemberTags = tagsResponse.data.map(tag => ({
|
||||
id: tag.tag.id,
|
||||
id: tag.tag.id,
|
||||
name: tag.tag.name
|
||||
}));
|
||||
} catch (error) {
|
||||
@@ -294,7 +362,7 @@ export default {
|
||||
},
|
||||
async addMemberNote() {
|
||||
if (this.newNoteContent) {
|
||||
const response = await apiClient.post(`/diarymember/${this.currentClub}/notes`, {
|
||||
const response = await apiClient.post(`/diarymember/${this.currentClub}/note`, {
|
||||
memberId: this.selectedMember.id,
|
||||
diaryDateId: this.date.id,
|
||||
content: this.newNoteContent
|
||||
@@ -305,8 +373,8 @@ export default {
|
||||
}
|
||||
},
|
||||
async deleteNote(noteId) {
|
||||
const response = await apiClient.delete(`/diarymember/note/${noteId}`, {
|
||||
data: { clubId: this.currentClub }
|
||||
const response = await apiClient.delete(`/diarymember/${this.currentClub}/note/${noteId}`, {
|
||||
clubId: this.currentClub
|
||||
});
|
||||
this.notes = response.data;
|
||||
},
|
||||
@@ -362,7 +430,6 @@ export default {
|
||||
}
|
||||
},
|
||||
async updateActivityTags(selectedTags) {
|
||||
console.log('test');
|
||||
try {
|
||||
for (let tag of selectedTags) {
|
||||
if (!this.previousActivityTags.includes(tag)) {
|
||||
@@ -426,8 +493,102 @@ export default {
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
}
|
||||
},
|
||||
handleActivityInput() {
|
||||
if (this.newPlanItem.activity) {
|
||||
this.showDropdown = true;
|
||||
} else {
|
||||
this.showDropdown = false;
|
||||
}
|
||||
},
|
||||
selectPredefinedActivity(activity) {
|
||||
this.newPlanItem.activity = activity.name;
|
||||
this.newPlanItem.durationText = activity.durationText;
|
||||
this.newPlanItem.duration = activity.duration || '';
|
||||
this.showDropdown = false;
|
||||
},
|
||||
async addPlanItem() {
|
||||
try {
|
||||
await apiClient.post(`/diary-date-activities/${this.currentClub}`, {
|
||||
diaryDateId: this.date.id,
|
||||
activity: this.newPlanItem.activity,
|
||||
duration: this.newPlanItem.duration,
|
||||
durationText: this.newPlanItem.durationText,
|
||||
orderId: this.trainingPlan.length
|
||||
});
|
||||
this.newPlanItem = { activity: '', duration: '', durationText: '' };
|
||||
this.trainingPlan = await apiClient.get(`/diary-date-activities/${this.currentClub}/${this.date.id}`).then(response => response.data);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Hinzufügen des Planungsitems:', error);
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
}
|
||||
},
|
||||
async removePlanItem(planItemId) {
|
||||
try {
|
||||
await apiClient.delete(`/diary-date-activities/${this.currentClub}`, {
|
||||
params: { planItemId }
|
||||
});
|
||||
this.planItems = this.planItems.filter(item => item.id !== planItemId);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Entfernen des Planungsitems:', error);
|
||||
}
|
||||
},
|
||||
calculatePlanItemTime(index) {
|
||||
let time = this.trainingStart;
|
||||
for (let i = 0; i < index; i++) {
|
||||
time = this.addDurationToTime(time, this.trainingPlan[i].duration);
|
||||
}
|
||||
return time;
|
||||
},
|
||||
addDurationToTime(startTime, duration) {
|
||||
let [hours, minutes] = startTime.split(':').map(Number);
|
||||
minutes += Number(duration);
|
||||
if (minutes >= 60) {
|
||||
hours += Math.floor(minutes / 60);
|
||||
minutes = minutes % 60;
|
||||
}
|
||||
hours = hours % 24;
|
||||
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
|
||||
},
|
||||
calculateDuration() {
|
||||
const input = this.newPlanItem.durationInput;
|
||||
let calculatedDuration = 0;
|
||||
const multiplyPattern = /(\d+)\s*[x*]\s*(\d+)/i;
|
||||
const match = input.match(multiplyPattern);
|
||||
if (match) {
|
||||
const [, num1, num2] = match;
|
||||
calculatedDuration = parseInt(num1) * parseInt(num2);
|
||||
} else if (!isNaN(input)) {
|
||||
calculatedDuration = parseInt(input);
|
||||
}
|
||||
calculatedDuration = Math.ceil(calculatedDuration / 5) * 5;
|
||||
if (!this.newPlanItem.durationText || this.newPlanItem.durationText === input) {
|
||||
this.newPlanItem.duration = calculatedDuration;
|
||||
this.newPlanItem = { ...this.newPlanItem, duration: calculatedDuration };
|
||||
}
|
||||
},
|
||||
async removePlanItem(planItemId) {
|
||||
try {
|
||||
await apiClient.delete(`/diary-date-activities/${this.currentClub}/${planItemId}`);
|
||||
this.trainingPlan = this.trainingPlan.filter(item => item.id !== planItemId);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Entfernen des Planungsitems:', error);
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
}
|
||||
},
|
||||
async onDragEnd(evt) {
|
||||
const movedItem = this.trainingPlan[evt.oldIndex];
|
||||
try {
|
||||
await apiClient.put(`/diary-date-activities/${this.currentClub}/${movedItem.id}/order`, {
|
||||
orderId: evt.newIndex
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Aktualisieren der Reihenfolge:', error);
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
}
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
|
||||
await this.init();
|
||||
}
|
||||
};
|
||||
@@ -532,4 +693,78 @@ li {
|
||||
margin-bottom: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input[type="time"] {
|
||||
width: 7em;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
width: 5em;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
border: 1px solid #ccc;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
z-index: 1000;
|
||||
width: calc(100% - 20px);
|
||||
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.dropdown div {
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown div:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
color: #45a049;
|
||||
}
|
||||
|
||||
.add-plan-item {
|
||||
border: 1px solid black;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
text-align: center;
|
||||
line-height: 1.2em;
|
||||
font-weight: bold;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.add-plan-item:hover {
|
||||
background-color: #45a049;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user