247 lines
7.3 KiB
Markdown
247 lines
7.3 KiB
Markdown
|
|
# Worship List Update - December 2025
|
||
|
|
|
||
|
|
## Summary of Changes
|
||
|
|
|
||
|
|
This update addresses cross-device synchronization issues, adds drag-and-drop reordering for songs, and rebrands "Worship Planning" to "Worship List" throughout the application.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🎯 Main Features Added
|
||
|
|
|
||
|
|
### 1. **Fixed Cross-Device Song Adding** ✅
|
||
|
|
|
||
|
|
- **Problem**: Unable to add songs to worship plans from other devices
|
||
|
|
- **Solution**:
|
||
|
|
- Backend now normalizes all ID comparisons using `.toString()` to handle both numeric IDs (from local storage) and UUID strings (from backend)
|
||
|
|
- Added duplicate detection for song associations (prevents duplicate entries)
|
||
|
|
- POST endpoints now preserve provided IDs from migration instead of always generating new UUIDs
|
||
|
|
|
||
|
|
### 2. **Drag-and-Drop Song Reordering** ✅
|
||
|
|
|
||
|
|
- **New Feature**: Songs in worship plans can now be reordered by:
|
||
|
|
- **Dragging**: Click and drag songs with the ⋮⋮ handle
|
||
|
|
- **Arrows**: Use ▲ ▼ buttons to move songs up or down
|
||
|
|
- **Visual Feedback**:
|
||
|
|
- Songs highlight on hover with purple border
|
||
|
|
- Cursor changes to "grabbing" during drag
|
||
|
|
- Touch-friendly on mobile devices
|
||
|
|
|
||
|
|
### 3. **Worship Planning → Worship List Rebrand** ✅
|
||
|
|
|
||
|
|
- **Changed Throughout**:
|
||
|
|
- Page title: "Worship List"
|
||
|
|
- Navigation menu: "📋 Worship List" (both desktop and mobile)
|
||
|
|
- Home page card: "Worship List"
|
||
|
|
- Documentation files updated
|
||
|
|
- **Icon Change**: 📅 → 📋 (from calendar to clipboard)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔧 Technical Changes
|
||
|
|
|
||
|
|
### Backend (`backend/server.js`)
|
||
|
|
|
||
|
|
#### ID Normalization (Songs)
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
// POST - Preserve provided IDs
|
||
|
|
const incomingId = req.body.id != null ? req.body.id.toString() : uuid();
|
||
|
|
if (db.songs.find((s) => s.id?.toString() === incomingId)) {
|
||
|
|
return res.status(409).json({ error: "Song ID already exists" });
|
||
|
|
}
|
||
|
|
|
||
|
|
// GET/PUT/DELETE - Normalize comparisons
|
||
|
|
db.songs.find((s) => s.id?.toString() === req.params.id)
|
||
|
|
```
|
||
|
|
|
||
|
|
#### ID Normalization (Profiles, Plans, Associations)
|
||
|
|
|
||
|
|
- All `profile_id`, `plan_id`, `song_id` comparisons now use `.toString()`
|
||
|
|
- Added duplicate prevention for:
|
||
|
|
- `profileSongs` associations
|
||
|
|
- `planSongs` associations
|
||
|
|
|
||
|
|
#### Export Endpoint Fix
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
db.songs.find((s) => s.id?.toString() === ps.song_id?.toString())
|
||
|
|
```
|
||
|
|
|
||
|
|
### Frontend (`frontend/src/App.js`)
|
||
|
|
|
||
|
|
#### Profile Songs Mapping Enhancement
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
export async function getProfileSongs(profileId) {
|
||
|
|
// Map associations to full songs
|
||
|
|
for (const ps of backend) {
|
||
|
|
let song = await localStorageAPI.getSong(ps.song_id);
|
||
|
|
if (!song) {
|
||
|
|
// Fetch from backend if not in local cache
|
||
|
|
const r = await fetch(`${API_BASE}/songs/${ps.song_id}`);
|
||
|
|
if (r.ok) song = await r.json();
|
||
|
|
}
|
||
|
|
if (song) fullSongs.push(song);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Drag-and-Drop Implementation
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
function handleDragStart(e, index) {
|
||
|
|
e.dataTransfer.effectAllowed = "move";
|
||
|
|
e.dataTransfer.setData("text/plain", index.toString());
|
||
|
|
}
|
||
|
|
|
||
|
|
function handleDrop(e, dropIndex) {
|
||
|
|
e.preventDefault();
|
||
|
|
const dragIndex = parseInt(e.dataTransfer.getData("text/plain"));
|
||
|
|
const newSongs = [...chosenSongs];
|
||
|
|
const [removed] = newSongs.splice(dragIndex, 1);
|
||
|
|
newSongs.splice(dropIndex, 0, removed);
|
||
|
|
setChosenSongs(newSongs);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Force Migration Button
|
||
|
|
|
||
|
|
- Added "⚙️ Force Full Migration (Associations)" button in Settings
|
||
|
|
- Re-runs migration even when backend not empty
|
||
|
|
- Migrates missing `profileSongs` and `profileSongKeys`
|
||
|
|
|
||
|
|
### Migration Script (`frontend/src/migration.js`)
|
||
|
|
|
||
|
|
#### Key Fixes
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
// Now uses correct localStorage keys
|
||
|
|
const allProfileSongs = JSON.parse(
|
||
|
|
localStorage.getItem(STORAGE_KEYS.PROFILE_SONGS) || "[]"
|
||
|
|
);
|
||
|
|
|
||
|
|
// Keys stored as array entries
|
||
|
|
const profileSongKeysArr = JSON.parse(
|
||
|
|
localStorage.getItem(STORAGE_KEYS.PROFILE_SONG_KEYS) || "[]"
|
||
|
|
);
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Duplicate Prevention
|
||
|
|
|
||
|
|
- Checks existing backend data before posting
|
||
|
|
- Skips songs/profiles/plans already present
|
||
|
|
- Logs skipped items with ⏭️ emoji
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📱 User Interface Changes
|
||
|
|
|
||
|
|
### Worship List Page
|
||
|
|
|
||
|
|
- ✨ **New Label**: "Chosen Songs (X) - Drag to reorder or use arrows"
|
||
|
|
- 🎨 **Visual Drag Handle**: ⋮⋮ appears on each song
|
||
|
|
- 🎯 **Hover Effects**: Songs highlight with purple border and shadow
|
||
|
|
- 📊 **Better Feedback**: Order numbers update instantly on reorder
|
||
|
|
|
||
|
|
### Navigation
|
||
|
|
|
||
|
|
- Desktop menu: "📋 Worship List"
|
||
|
|
- Mobile menu: "📋 Worship List"
|
||
|
|
- Consistent branding throughout
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔄 Migration Guide
|
||
|
|
|
||
|
|
### For Existing Users
|
||
|
|
|
||
|
|
1. **Restart Backend**:
|
||
|
|
|
||
|
|
```powershell
|
||
|
|
cd backend
|
||
|
|
node server.js
|
||
|
|
```
|
||
|
|
|
||
|
|
2. **Run Force Migration** (if needed):
|
||
|
|
- Go to **Settings** → switch to **Online Mode**
|
||
|
|
- Scroll to **Data Management** section
|
||
|
|
- Click **"⚙️ Force Full Migration (Associations)"**
|
||
|
|
- This will sync profile songs and keys to backend
|
||
|
|
|
||
|
|
3. **Verify on Other Devices**:
|
||
|
|
- Open app on secondary device
|
||
|
|
- Ensure **Online Mode** with correct backend IP
|
||
|
|
- Profile saved songs should now display
|
||
|
|
- Worship plan songs should be clickable and show titles
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ✅ Testing Checklist
|
||
|
|
|
||
|
|
- [x] Backend normalizes IDs correctly (songs, profiles, plans)
|
||
|
|
- [x] Profile songs display on other devices
|
||
|
|
- [x] Plan songs display with titles and are clickable
|
||
|
|
- [x] Adding songs to plan works from any device
|
||
|
|
- [x] Drag-and-drop reordering works
|
||
|
|
- [x] Arrow button reordering works
|
||
|
|
- [x] Force migration button copies associations
|
||
|
|
- [x] "Worship List" name appears throughout app
|
||
|
|
- [x] Navigation icons updated (📋 instead of 📅)
|
||
|
|
- [x] Duplicate associations prevented
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🐛 Bug Fixes
|
||
|
|
|
||
|
|
1. **404 Errors on Song Lookup**
|
||
|
|
- **Cause**: Numeric IDs (1, 2) not matching string comparisons
|
||
|
|
- **Fix**: All ID comparisons now use `.toString()`
|
||
|
|
|
||
|
|
2. **Empty Profile Song Lists**
|
||
|
|
- **Cause**: Backend had no profileSongs associations
|
||
|
|
- **Fix**: Migration script now migrates associations; Force Migration button added
|
||
|
|
|
||
|
|
3. **Plan Songs Not Showing**
|
||
|
|
- **Cause**: Export endpoint not finding songs with mixed ID types
|
||
|
|
- **Fix**: Normalized comparisons in export logic
|
||
|
|
|
||
|
|
4. **Unable to Add Songs from Other Device**
|
||
|
|
- **Cause**: Duplicate check failing due to ID type mismatch
|
||
|
|
- **Fix**: Duplicate detection now compares stringified IDs
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📝 Known Limitations
|
||
|
|
|
||
|
|
1. **Drag-and-Drop on Touch Devices**: HTML5 drag API has limited mobile support; arrow buttons remain primary method on phones/tablets
|
||
|
|
2. **Real-time Order Sync**: Order changes update on save; WebSocket doesn't broadcast reorder events (plan must be refreshed)
|
||
|
|
3. **Migration Required**: Existing users must run Force Migration once to populate backend associations
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🚀 Future Enhancements
|
||
|
|
|
||
|
|
- [ ] Real-time collaborative editing (WebSocket song order updates)
|
||
|
|
- [ ] Touch-friendly drag library (React DnD or similar)
|
||
|
|
- [ ] Bulk song import from CSV
|
||
|
|
- [ ] Plan templates and recurring schedules
|
||
|
|
- [ ] Worship list history and version control
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📞 Support
|
||
|
|
|
||
|
|
If you encounter issues:
|
||
|
|
|
||
|
|
1. Check backend is running: `http://localhost:5000/api/health`
|
||
|
|
2. Verify Online Mode settings match your backend IP
|
||
|
|
3. Run Force Migration to sync associations
|
||
|
|
4. Clear browser cache and localStorage if data seems stale
|
||
|
|
5. Check browser console for error messages
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**Updated**: November 29, 2025
|
||
|
|
**Version**: 2.1.0
|
||
|
|
**Author**: GitHub Copilot
|