feat: Add batch genre assignment functionality to song uploads, including UI for selection and post-upload API calls.
This commit is contained in:
@@ -106,6 +106,9 @@ export default function AdminPage() {
|
|||||||
const [uploadGenreIds, setUploadGenreIds] = useState<number[]>([]);
|
const [uploadGenreIds, setUploadGenreIds] = useState<number[]>([]);
|
||||||
const [uploadExcludeFromGlobal, setUploadExcludeFromGlobal] = useState(false);
|
const [uploadExcludeFromGlobal, setUploadExcludeFromGlobal] = useState(false);
|
||||||
|
|
||||||
|
// Batch upload genre selection
|
||||||
|
const [batchUploadGenreIds, setBatchUploadGenreIds] = useState<number[]>([]);
|
||||||
|
|
||||||
// AI Categorization state
|
// AI Categorization state
|
||||||
const [isCategorizing, setIsCategorizing] = useState(false);
|
const [isCategorizing, setIsCategorizing] = useState(false);
|
||||||
const [categorizationResults, setCategorizationResults] = useState<any>(null);
|
const [categorizationResults, setCategorizationResults] = useState<any>(null);
|
||||||
@@ -548,6 +551,28 @@ export default function AdminPage() {
|
|||||||
setUploadResults(results);
|
setUploadResults(results);
|
||||||
setFiles([]);
|
setFiles([]);
|
||||||
setIsUploading(false);
|
setIsUploading(false);
|
||||||
|
|
||||||
|
// Assign genres to successfully uploaded songs
|
||||||
|
if (batchUploadGenreIds.length > 0) {
|
||||||
|
const successfulUploads = results.filter(r => r.success && r.song);
|
||||||
|
for (const result of successfulUploads) {
|
||||||
|
try {
|
||||||
|
await fetch('/api/songs', {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: getAuthHeaders(),
|
||||||
|
body: JSON.stringify({
|
||||||
|
id: result.song.id,
|
||||||
|
title: result.song.title,
|
||||||
|
artist: result.song.artist,
|
||||||
|
genreIds: batchUploadGenreIds
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to assign genres to ${result.song.title}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fetchSongs();
|
fetchSongs();
|
||||||
fetchGenres();
|
fetchGenres();
|
||||||
fetchSpecials(); // Update special counts
|
fetchSpecials(); // Update special counts
|
||||||
@@ -564,6 +589,13 @@ export default function AdminPage() {
|
|||||||
if (failedCount > 0) {
|
if (failedCount > 0) {
|
||||||
msg += `\n❌ ${failedCount} failed`;
|
msg += `\n❌ ${failedCount} failed`;
|
||||||
}
|
}
|
||||||
|
if (batchUploadGenreIds.length > 0) {
|
||||||
|
const selectedGenreNames = genres
|
||||||
|
.filter(g => batchUploadGenreIds.includes(g.id))
|
||||||
|
.map(g => g.name)
|
||||||
|
.join(', ');
|
||||||
|
msg += `\n🏷️ Assigned genres: ${selectedGenreNames}`;
|
||||||
|
}
|
||||||
msg += '\n\n🤖 Starting auto-categorization...';
|
msg += '\n\n🤖 Starting auto-categorization...';
|
||||||
setMessage(msg);
|
setMessage(msg);
|
||||||
// Small delay to let user see the message
|
// Small delay to let user see the message
|
||||||
@@ -1219,6 +1251,48 @@ export default function AdminPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '1rem' }}>
|
||||||
|
<label style={{ fontWeight: '500', display: 'block', marginBottom: '0.5rem' }}>
|
||||||
|
Assign Genres (optional)
|
||||||
|
</label>
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem' }}>
|
||||||
|
{genres.map(genre => (
|
||||||
|
<label
|
||||||
|
key={genre.id}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '0.25rem',
|
||||||
|
padding: '0.25rem 0.5rem',
|
||||||
|
background: batchUploadGenreIds.includes(genre.id) ? '#dbeafe' : '#f3f4f6',
|
||||||
|
border: batchUploadGenreIds.includes(genre.id) ? '2px solid #3b82f6' : '2px solid transparent',
|
||||||
|
borderRadius: '0.25rem',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
transition: 'all 0.2s'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={batchUploadGenreIds.includes(genre.id)}
|
||||||
|
onChange={e => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
setBatchUploadGenreIds([...batchUploadGenreIds, genre.id]);
|
||||||
|
} else {
|
||||||
|
setBatchUploadGenreIds(batchUploadGenreIds.filter(id => id !== genre.id));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{ margin: 0 }}
|
||||||
|
/>
|
||||||
|
{genre.name}
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<p style={{ fontSize: '0.875rem', color: '#666', marginTop: '0.25rem' }}>
|
||||||
|
Selected genres will be assigned to all uploaded songs.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style={{ marginBottom: '1rem' }}>
|
<div style={{ marginBottom: '1rem' }}>
|
||||||
<label style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', cursor: 'pointer' }}>
|
<label style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', cursor: 'pointer' }}>
|
||||||
<input
|
<input
|
||||||
|
|||||||
Reference in New Issue
Block a user