Skip to content

Commit b88a6c2

Browse files
committed
Able to change format in detail view and more easier to select the version album from mobile
1 parent 45ad0d1 commit b88a6c2

File tree

4 files changed

+130
-34
lines changed

4 files changed

+130
-34
lines changed

backend/src/controllers/collection.controller.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,4 +273,37 @@ export async function deleteFromCollection(req: Request, res: Response) {
273273
console.error('Error deleting from collection:', error);
274274
res.status(500).json({ message: 'Internal server error' });
275275
}
276+
}
277+
278+
export async function updateCollectionItem(req: Request, res: Response) {
279+
try {
280+
if (!req.user) {
281+
res.status(401).json({ message: 'Unauthorized' });
282+
return;
283+
}
284+
285+
const { itemId } = req.params;
286+
const { format } = req.body;
287+
288+
if (!format || !format.name) {
289+
res.status(400).json({ message: "Format name is required" });
290+
return;
291+
}
292+
293+
const updatedItem = await CollectionItem.findOneAndUpdate(
294+
{ _id: itemId, user: req.user._id },
295+
{ $set: { "format.name": format.name } },
296+
{ new: true }
297+
);
298+
299+
if (!updatedItem) {
300+
res.status(404).json({ message: 'Item not found in your collection' });
301+
return;
302+
}
303+
304+
res.status(200).json(updatedItem);
305+
} catch (error) {
306+
console.error('Error updating collection item:', error);
307+
res.status(500).json({ message: 'Internal server error' });
308+
}
276309
}

backend/src/routes/collection.route.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
downloadTemplate,
1010
getImportLogs,
1111
getImportLogById,
12-
downloadImportLog
12+
downloadImportLog,
13+
updateCollectionItem
1314
} from '../controllers/collection.controller';
1415
import protectRoute from '../middlewares/protectRoute';
1516

@@ -27,6 +28,7 @@ router.get('/import/logs/:logId/download', protectRoute, downloadImportLog);
2728
router.post('/', protectRoute, addToCollection);
2829
router.get('/', protectRoute, getMyCollection);
2930
router.get('/:itemId', protectRoute, getCollectionItemById);
31+
router.put('/:itemId', protectRoute, updateCollectionItem);
3032
router.delete('/:itemId', protectRoute, deleteFromCollection);
3133

3234
export default router;

frontend/src/pages/AlbumDetailPage.tsx

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { useEffect, useState } from 'react';
22
import { useNavigate, useParams } from 'react-router';
33
import axios from 'axios';
4+
import { toastService } from '../utils/toast';
45
import type { CollectionItem } from '../types/collection';
56

67
interface Track {
@@ -76,6 +77,21 @@ const AlbumDetailPage: React.FC = () => {
7677
}
7778
};
7879

80+
const handleDelete = async () => {
81+
if (!confirm('Are you sure you want to remove this album from your collection?')) {
82+
return;
83+
}
84+
85+
try {
86+
await axios.delete(`/api/collection/${item?._id}`, { withCredentials: true });
87+
toastService.success('Album removed from collection');
88+
navigate('/app');
89+
} catch (error) {
90+
console.error('Failed to delete album:', error);
91+
toastService.error('Failed to remove album');
92+
}
93+
};
94+
7995
if (loading) {
8096
return (
8197
<div className="flex justify-center items-center min-h-screen">
@@ -96,10 +112,22 @@ const AlbumDetailPage: React.FC = () => {
96112

97113
return (
98114
<div className="max-w-6xl mx-auto p-4">
99-
{/* Back button */}
100-
<button onClick={() => navigate(-1)} className="btn btn-ghost btn-sm mb-6">
101-
← Back
102-
</button>
115+
{/* Header Actions */}
116+
<div className="flex justify-between items-center mb-6">
117+
<button onClick={() => navigate(-1)} className="btn btn-ghost btn-sm">
118+
← Back
119+
</button>
120+
<button
121+
onClick={handleDelete}
122+
className="btn btn-ghost btn-sm text-error hover:bg-error/10"
123+
title="Remove from collection"
124+
>
125+
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
126+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
127+
</svg>
128+
Delete Album
129+
</button>
130+
</div>
103131

104132
{/* Header Section */}
105133
<div className="flex flex-col lg:flex-row gap-8 mb-8">
@@ -125,7 +153,35 @@ const AlbumDetailPage: React.FC = () => {
125153
</div>
126154
<div className="stat bg-base-200 rounded-lg p-4">
127155
<div className="stat-title">Format</div>
128-
<div className="stat-value text-2xl capitalize">{item.format.name}</div>
156+
<div className="stat-value text-2xl">
157+
<select
158+
className="select select-ghost w-full max-w-xs text-2xl font-bold p-0 h-auto min-h-0 focus:bg-transparent"
159+
value={item.format.name}
160+
onChange={async (e) => {
161+
const newFormat = e.target.value;
162+
try {
163+
await axios.put(`/api/collection/${item._id}`, {
164+
format: { name: newFormat }
165+
}, { withCredentials: true });
166+
167+
// Optimistic update
168+
setItem(prev => prev ? {
169+
...prev,
170+
format: { ...prev.format, name: newFormat }
171+
} : null);
172+
toastService.success(`Format updated to ${newFormat}`);
173+
} catch (error) {
174+
console.error('Failed to update format:', error);
175+
toastService.error('Failed to update format');
176+
}
177+
}}
178+
>
179+
<option value="Vinyl">Vinyl</option>
180+
<option value="CD">CD</option>
181+
<option value="Digital">Digital</option>
182+
<option value="Cassette">Cassette</option>
183+
</select>
184+
</div>
129185
</div>
130186
<div className="stat bg-base-200 rounded-lg p-4">
131187
<div className="stat-title">Added</div>

frontend/src/pages/VersionsPage.tsx

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ const VersionsPage: React.FC = () => {
107107
</div>
108108

109109
<div className="flex-1">
110-
<div className="flex justify-between items-center mb-4">
110+
<div className="flex flex-wrap gap-4 justify-between items-center mb-4">
111111
<div className="flex items-center gap-2">
112112
<p className="text-sm">Filter by:</p>
113113
<button onClick={() => setFilter('all')} className={`btn btn-xs ${filter === 'all' ? 'btn-active btn-neutral' : ''}`}>All</button>
@@ -125,34 +125,39 @@ const VersionsPage: React.FC = () => {
125125
<button onClick={() => navigate(-1)} className="btn btn-sm btn-outline">← Back</button>
126126
</div>
127127

128-
<div className="overflow-x-auto">
129-
<table className="table table-zebra w-full">
130-
<thead>
131-
<tr>
132-
<th>Released</th>
133-
<th>Format</th>
134-
<th>Label</th>
135-
<th>Country</th>
136-
<th></th>
137-
</tr>
138-
</thead>
139-
<tbody>
140-
{filteredVersions.map((version) => (
141-
<tr key={version.id} className="hover">
142-
<td>{version.released || 'N/A'}</td>
143-
<td>{version.majorFormat}</td>
144-
<td>{version.label}</td>
145-
<td>{version.country}</td>
146-
<td className="text-right">
147-
<button className="btn btn-sm btn-primary" onClick={() => handleShowDetails(version.id)}>
148-
Add
149-
</button>
150-
</td>
151-
</tr>
152-
))}
153-
</tbody>
154-
</table>
128+
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4">
129+
{filteredVersions.map((version) => (
130+
<div key={version.id} className="card bg-base-200 shadow-sm hover:shadow-md transition-shadow">
131+
<div className="card-body p-3">
132+
<h3 className="card-title text-xs flex justify-between items-start">
133+
<span>{version.released || 'N/A'}</span>
134+
<span className="badge badge-neutral badge-xs">{version.country || '?'}</span>
135+
</h3>
136+
137+
<div className="my-1 space-y-0.5">
138+
<div className="font-bold text-sm truncate" title={version.majorFormat}>{version.majorFormat}</div>
139+
<div className="text-xs text-gray-500 truncate" title={version.label}>
140+
{version.label}
141+
</div>
142+
</div>
143+
144+
<div className="card-actions mt-2">
145+
<button
146+
className="btn btn-primary btn-xs w-full"
147+
onClick={() => handleShowDetails(version.id)}
148+
>
149+
Select
150+
</button>
151+
</div>
152+
</div>
153+
</div>
154+
))}
155155
</div>
156+
{filteredVersions.length === 0 && (
157+
<div className="text-center py-10 opacity-50">
158+
No versions found matching your filter.
159+
</div>
160+
)}
156161
</div>
157162
</div>
158163

0 commit comments

Comments
 (0)