mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-24 07:41:47 +00:00
Participants, context menus, budget rename, file types, UI polish
- Assignment participants: toggle who joins each activity - Chips with hover-to-remove (strikethrough effect) - Add button with dropdown for available members - Avatars in day plan sidebar - Side-by-side with reservation in place inspector - Right-click context menus for places, notes in day plan + places list - Budget categories can now be renamed (pencil icon inline edit) - Admin: configurable allowed file types (stored in app_settings) - File manager shows allowed types dynamically - Hotel picker: select place + save button (no auto-close) - Edit pencil opens full hotel popup with all options - Place inspector: opening hours + files side by side on desktop - Address clamped to 2 lines, coordinates hidden on mobile - Category shows icon only on mobile - Rating hidden on mobile in place inspector - Time validation: "10" becomes "10:00" - Climate weather: full hourly data from archive API - CustomSelect: grouped headers support (isHeader) - Various responsive fixes
This commit is contained in:
@@ -30,11 +30,19 @@ function getAssignmentWithPlace(assignmentId) {
|
||||
WHERE pt.place_id = ?
|
||||
`).all(a.place_id);
|
||||
|
||||
const participants = db.prepare(`
|
||||
SELECT ap.user_id, u.username, u.avatar
|
||||
FROM assignment_participants ap
|
||||
JOIN users u ON ap.user_id = u.id
|
||||
WHERE ap.assignment_id = ?
|
||||
`).all(a.id);
|
||||
|
||||
return {
|
||||
id: a.id,
|
||||
day_id: a.day_id,
|
||||
order_index: a.order_index,
|
||||
notes: a.notes,
|
||||
participants,
|
||||
created_at: a.created_at,
|
||||
place: {
|
||||
id: a.place_id,
|
||||
@@ -105,12 +113,25 @@ router.get('/trips/:tripId/days/:dayId/assignments', authenticate, (req, res) =>
|
||||
}
|
||||
}
|
||||
|
||||
// Load all participants for this day's assignments in one query
|
||||
const assignmentIds = assignments.map(a => a.id)
|
||||
const allParticipants = assignmentIds.length > 0
|
||||
? db.prepare(`SELECT ap.assignment_id, ap.user_id, u.username, u.avatar FROM assignment_participants ap JOIN users u ON ap.user_id = u.id WHERE ap.assignment_id IN (${assignmentIds.map(() => '?').join(',')})`)
|
||||
.all(...assignmentIds)
|
||||
: []
|
||||
const participantsByAssignment = {}
|
||||
for (const p of allParticipants) {
|
||||
if (!participantsByAssignment[p.assignment_id]) participantsByAssignment[p.assignment_id] = []
|
||||
participantsByAssignment[p.assignment_id].push({ user_id: p.user_id, username: p.username, avatar: p.avatar })
|
||||
}
|
||||
|
||||
const result = assignments.map(a => {
|
||||
return {
|
||||
id: a.id,
|
||||
day_id: a.day_id,
|
||||
order_index: a.order_index,
|
||||
notes: a.notes,
|
||||
participants: participantsByAssignment[a.id] || [],
|
||||
created_at: a.created_at,
|
||||
place: {
|
||||
id: a.place_id,
|
||||
@@ -241,4 +262,45 @@ router.put('/trips/:tripId/assignments/:id/move', authenticate, (req, res) => {
|
||||
broadcast(tripId, 'assignment:moved', { assignment: updated, oldDayId: Number(oldDayId), newDayId: Number(new_day_id) }, req.headers['x-socket-id']);
|
||||
});
|
||||
|
||||
// GET /api/trips/:tripId/assignments/:id/participants
|
||||
router.get('/trips/:tripId/assignments/:id/participants', authenticate, (req, res) => {
|
||||
const { tripId, id } = req.params;
|
||||
if (!canAccessTrip(Number(tripId), req.user.id)) return res.status(404).json({ error: 'Trip not found' });
|
||||
|
||||
const participants = db.prepare(`
|
||||
SELECT ap.user_id, u.username, u.avatar
|
||||
FROM assignment_participants ap
|
||||
JOIN users u ON ap.user_id = u.id
|
||||
WHERE ap.assignment_id = ?
|
||||
`).all(id);
|
||||
|
||||
res.json({ participants });
|
||||
});
|
||||
|
||||
// PUT /api/trips/:tripId/assignments/:id/participants — set participants (replace all)
|
||||
router.put('/trips/:tripId/assignments/:id/participants', authenticate, (req, res) => {
|
||||
const { tripId, id } = req.params;
|
||||
if (!canAccessTrip(Number(tripId), req.user.id)) return res.status(404).json({ error: 'Trip not found' });
|
||||
|
||||
const { user_ids } = req.body; // array of user IDs, empty array = everyone
|
||||
if (!Array.isArray(user_ids)) return res.status(400).json({ error: 'user_ids must be an array' });
|
||||
|
||||
// Delete existing and insert new
|
||||
db.prepare('DELETE FROM assignment_participants WHERE assignment_id = ?').run(id);
|
||||
if (user_ids.length > 0) {
|
||||
const insert = db.prepare('INSERT OR IGNORE INTO assignment_participants (assignment_id, user_id) VALUES (?, ?)');
|
||||
for (const userId of user_ids) insert.run(id, userId);
|
||||
}
|
||||
|
||||
const participants = db.prepare(`
|
||||
SELECT ap.user_id, u.username, u.avatar
|
||||
FROM assignment_participants ap
|
||||
JOIN users u ON ap.user_id = u.id
|
||||
WHERE ap.assignment_id = ?
|
||||
`).all(id);
|
||||
|
||||
res.json({ participants });
|
||||
broadcast(Number(tripId), 'assignment:participants', { assignmentId: Number(id), participants }, req.headers['x-socket-id']);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user