Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>

This commit is contained in:
Matt Bruce 2025-07-18 10:24:36 -05:00
parent ead4252441
commit d2d9d9691b
3 changed files with 88 additions and 25 deletions

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { IonItem, IonLabel, IonIcon, IonItemSliding, IonItemOptions, IonItemOption } from '@ionic/react'; import { IonItem, IonLabel, IonIcon } from '@ionic/react';
import { trash } from 'ionicons/icons'; import { trash } from 'ionicons/icons';
import { InfiniteScrollList, PageHeader } from '../../components/common'; import { InfiniteScrollList, PageHeader } from '../../components/common';
import { useSingers } from '../../hooks'; import { useSingers } from '../../hooks';
@ -23,27 +23,24 @@ const Singers: React.FC = () => {
// Render singer item for InfiniteScrollList // Render singer item for InfiniteScrollList
const renderSingerItem = (singer: Singer) => ( const renderSingerItem = (singer: Singer) => (
<IonItemSliding key={singer.key}> <IonItem detail={false}>
<IonItem detail={false}> <IonLabel>
<IonLabel> <h3 className="text-sm font-medium text-gray-900">
<h3 className="text-sm font-medium text-gray-900"> {singer.name}
{singer.name} </h3>
</h3> </IonLabel>
</IonLabel>
</IonItem>
{/* Swipe to Remove (Admin Only) */} {/* Delete Icon (Admin Only) */}
{isAdmin && ( {isAdmin && (
<IonItemOptions side="end"> <IonIcon
<IonItemOption icon={trash}
color="danger" slot="end"
onClick={() => handleRemoveSinger(singer)} color="danger"
> className="cursor-pointer"
<IonIcon icon={trash} slot="icon-only" /> onClick={() => handleRemoveSinger(singer)}
</IonItemOption> />
</IonItemOptions>
)} )}
</IonItemSliding> </IonItem>
); );
return ( return (

View File

@ -9,7 +9,7 @@ import {
update update
} from 'firebase/database'; } from 'firebase/database';
import { database } from './config'; import { database } from './config';
import type { Song, QueueItem, Controller } from '../types'; import type { Song, QueueItem, Controller, Singer } from '../types';
// Basic CRUD operations for controllers // Basic CRUD operations for controllers
export const controllerService = { export const controllerService = {
@ -200,3 +200,61 @@ export const favoritesService = {
return () => off(favoritesRef); return () => off(favoritesRef);
} }
}; };
// Singer management operations
export const singerService = {
// Remove singer and all their queue items
removeSinger: async (controllerName: string, singerName: string) => {
// First, remove all queue items for this singer
const queueRef = ref(database, `controllers/${controllerName}/player/queue`);
const queueSnapshot = await get(queueRef);
if (queueSnapshot.exists()) {
const queue = queueSnapshot.val();
const updates: Record<string, QueueItem | null> = {};
// Find all queue items for this singer and mark them for removal
Object.entries(queue).forEach(([key, item]) => {
if (item && (item as QueueItem).singer.name === singerName) {
updates[key] = null; // Mark for removal
}
});
// Remove the queue items
if (Object.keys(updates).length > 0) {
await update(queueRef, updates);
}
}
// Then, remove the singer from the singers list
const singersRef = ref(database, `controllers/${controllerName}/player/singers`);
const singersSnapshot = await get(singersRef);
if (singersSnapshot.exists()) {
const singers = singersSnapshot.val();
const updates: Record<string, Singer | null> = {};
// Find the singer by name and mark for removal
Object.entries(singers).forEach(([key, singer]) => {
if (singer && (singer as Singer).name === singerName) {
updates[key] = null; // Mark for removal
}
});
// Remove the singer
if (Object.keys(updates).length > 0) {
await update(singersRef, updates);
}
}
},
// Listen to singers changes
subscribeToSingers: (controllerName: string, callback: (data: Record<string, Singer>) => void) => {
const singersRef = ref(database, `controllers/${controllerName}/player/singers`);
onValue(singersRef, (snapshot) => {
callback(snapshot.exists() ? snapshot.val() : {});
});
return () => off(singersRef);
}
};

View File

@ -1,11 +1,13 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useAppSelector, selectSingersArray, selectIsAdmin } from '../redux'; import { useAppSelector, selectSingersArray, selectIsAdmin, selectControllerName } from '../redux';
import { useToast } from './useToast'; import { useToast } from './useToast';
import { singerService } from '../firebase/services';
import type { Singer } from '../types'; import type { Singer } from '../types';
export const useSingers = () => { export const useSingers = () => {
const singers = useAppSelector(selectSingersArray); const singers = useAppSelector(selectSingersArray);
const isAdmin = useAppSelector(selectIsAdmin); const isAdmin = useAppSelector(selectIsAdmin);
const controllerName = useAppSelector(selectControllerName);
const { showSuccess, showError } = useToast(); const { showSuccess, showError } = useToast();
const handleRemoveSinger = useCallback(async (singer: Singer) => { const handleRemoveSinger = useCallback(async (singer: Singer) => {
@ -14,13 +16,19 @@ export const useSingers = () => {
return; return;
} }
if (!controllerName) {
showError('Controller not found');
return;
}
try { try {
// TODO: Implement remove singer functionality await singerService.removeSinger(controllerName, singer.name);
showSuccess(`${singer.name} removed from singers list`); showSuccess(`${singer.name} removed from singers list and queue`);
} catch { } catch (error) {
console.error('Failed to remove singer:', error);
showError('Failed to remove singer'); showError('Failed to remove singer');
} }
}, [isAdmin, showSuccess, showError]); }, [isAdmin, controllerName, showSuccess, showError]);
return { return {
singers, singers,