Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
d4e5c4d5ae
commit
e9a8682b31
@ -1,17 +1,23 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
import { IonApp, IonHeader, IonToolbar, IonTitle, IonContent, IonMenuButton, IonIcon } from '@ionic/react';
|
import { IonApp, IonHeader, IonToolbar, IonTitle, IonContent, IonMenuButton, IonIcon } from '@ionic/react';
|
||||||
import { logOut } from 'ionicons/icons';
|
import { logOut } from 'ionicons/icons';
|
||||||
import { selectControllerName } from '../../redux/authSlice';
|
import { selectControllerName } from '../../redux/authSlice';
|
||||||
import { logout } from '../../redux/authSlice';
|
import { logout } from '../../redux/authSlice';
|
||||||
import { ActionButton } from '../common';
|
import { ActionButton } from '../common';
|
||||||
import Navigation from '../Navigation/Navigation';
|
import Navigation from '../Navigation/Navigation';
|
||||||
|
import { getPageTitle } from '../../utils/routeUtils';
|
||||||
import type { LayoutProps } from '../../types';
|
import type { LayoutProps } from '../../types';
|
||||||
|
|
||||||
const Layout: React.FC<LayoutProps> = ({ children }) => {
|
const Layout: React.FC<LayoutProps> = ({ children }) => {
|
||||||
const controllerName = useSelector(selectControllerName);
|
const controllerName = useSelector(selectControllerName);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const location = useLocation();
|
||||||
const [isLargeScreen, setIsLargeScreen] = useState(false);
|
const [isLargeScreen, setIsLargeScreen] = useState(false);
|
||||||
|
|
||||||
|
// Get the current page title
|
||||||
|
const currentPageTitle = getPageTitle(location.pathname);
|
||||||
|
|
||||||
// Check screen size for responsive layout
|
// Check screen size for responsive layout
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -51,12 +57,7 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
|
|||||||
|
|
||||||
<IonTitle>
|
<IonTitle>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<span>Sings A Lot</span>
|
<span>{currentPageTitle}</span>
|
||||||
{controllerName && (
|
|
||||||
<span className="ml-4 text-sm text-gray-500">
|
|
||||||
: {controllerName}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</IonTitle>
|
</IonTitle>
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { IonSearchbar, IonList, IonItem, IonLabel, IonModal, IonHeader, IonToolbar, IonTitle, IonButton, IonIcon, IonContent } from '@ionic/react';
|
import { IonSearchbar, IonList, IonItem, IonLabel, IonModal, IonHeader, IonToolbar, IonTitle, IonButton, IonIcon, IonContent } from '@ionic/react';
|
||||||
import { close, add, heart, heartOutline, list } from 'ionicons/icons';
|
import { close, add, heart, heartOutline, list } from 'ionicons/icons';
|
||||||
import { InfiniteScrollList, PageHeader } from '../../components/common';
|
import { InfiniteScrollList } from '../../components/common';
|
||||||
import { useArtists } from '../../hooks';
|
import { useArtists } from '../../hooks';
|
||||||
import { useAppSelector } from '../../redux';
|
import { useAppSelector } from '../../redux';
|
||||||
import { selectSongs } from '../../redux';
|
import { selectSongs } from '../../redux';
|
||||||
@ -56,10 +56,6 @@ const Artists: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHeader
|
|
||||||
title="Artists"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="max-w-4xl mx-auto p-6">
|
<div className="max-w-4xl mx-auto p-6">
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
{/* Search Input */}
|
{/* Search Input */}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { InfiniteScrollList, PageHeader, SongItem } from '../../components/common';
|
import { InfiniteScrollList, SongItem } from '../../components/common';
|
||||||
import { useFavorites } from '../../hooks';
|
import { useFavorites } from '../../hooks';
|
||||||
import { useAppSelector } from '../../redux';
|
import { useAppSelector } from '../../redux';
|
||||||
import { selectFavorites } from '../../redux';
|
import { selectFavorites } from '../../redux';
|
||||||
@ -23,11 +23,6 @@ const Favorites: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHeader
|
|
||||||
title="Favorites"
|
|
||||||
subtitle={`${favoritesCount} items loaded`}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InfiniteScrollList<Song>
|
<InfiniteScrollList<Song>
|
||||||
items={favoritesItems}
|
items={favoritesItems}
|
||||||
isLoading={favoritesCount === 0}
|
isLoading={favoritesCount === 0}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { IonChip, IonIcon } from '@ionic/react';
|
import { IonChip, IonIcon } from '@ionic/react';
|
||||||
import { time } from 'ionicons/icons';
|
import { time } from 'ionicons/icons';
|
||||||
import { InfiniteScrollList, PageHeader, SongItem } from '../../components/common';
|
import { InfiniteScrollList, SongItem } from '../../components/common';
|
||||||
import { useHistory } from '../../hooks';
|
import { useHistory } from '../../hooks';
|
||||||
import { useAppSelector } from '../../redux';
|
import { useAppSelector } from '../../redux';
|
||||||
import { selectHistory } from '../../redux';
|
import { selectHistory } from '../../redux';
|
||||||
@ -39,11 +39,6 @@ const History: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHeader
|
|
||||||
title="Recently Played"
|
|
||||||
subtitle={`${historyCount} items loaded`}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InfiniteScrollList<Song>
|
<InfiniteScrollList<Song>
|
||||||
items={historyItems}
|
items={historyItems}
|
||||||
isLoading={historyCount === 0}
|
isLoading={historyCount === 0}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { InfiniteScrollList, PageHeader, SongItem } from '../../components/common';
|
import { InfiniteScrollList, SongItem } from '../../components/common';
|
||||||
import { useNewSongs } from '../../hooks';
|
import { useNewSongs } from '../../hooks';
|
||||||
import { useAppSelector } from '../../redux';
|
import { useAppSelector } from '../../redux';
|
||||||
import { selectNewSongs } from '../../redux';
|
import { selectNewSongs } from '../../redux';
|
||||||
@ -23,11 +23,6 @@ const NewSongs: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHeader
|
|
||||||
title="New Songs"
|
|
||||||
subtitle={`${newSongsCount} items loaded`}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InfiniteScrollList<Song>
|
<InfiniteScrollList<Song>
|
||||||
items={newSongsItems}
|
items={newSongsItems}
|
||||||
isLoading={newSongsCount === 0}
|
isLoading={newSongsCount === 0}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { IonButton, IonIcon, IonReorderGroup, IonReorder, IonItem, IonLabel, IonItemSliding, IonItemOptions, IonItemOption } from '@ionic/react';
|
import { IonButton, IonIcon, IonReorderGroup, IonReorder, IonItem, IonLabel, IonItemSliding, IonItemOptions, IonItemOption } from '@ionic/react';
|
||||||
import { trash, reorderThreeOutline, reorderTwoOutline, playCircle } from 'ionicons/icons';
|
import { trash, reorderThreeOutline, reorderTwoOutline, playCircle } from 'ionicons/icons';
|
||||||
import { PageHeader, ActionButton } from '../../components/common';
|
import { ActionButton } from '../../components/common';
|
||||||
import { useQueue } from '../../hooks';
|
import { useQueue } from '../../hooks';
|
||||||
import { useAppSelector } from '../../redux';
|
import { useAppSelector } from '../../redux';
|
||||||
import { selectQueue, selectPlayerState, selectIsAdmin, selectControllerName } from '../../redux';
|
import { selectQueue, selectPlayerState, selectIsAdmin, selectControllerName } from '../../redux';
|
||||||
@ -219,22 +219,19 @@ const Queue: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHeader
|
<div className="flex justify-between items-center mb-4">
|
||||||
title="Queue"
|
|
||||||
subtitle={`${queueStats.totalSongs} song${queueStats.totalSongs !== 1 ? 's' : ''} in queue`}
|
{isAdmin && (
|
||||||
action={
|
<IonButton
|
||||||
isAdmin && (
|
onClick={toggleQueueMode}
|
||||||
<IonButton
|
fill="outline"
|
||||||
onClick={toggleQueueMode}
|
size="small"
|
||||||
fill="outline"
|
className="flex items-center gap-2"
|
||||||
size="small"
|
>
|
||||||
className="flex items-center gap-2"
|
<IonIcon icon={queueMode === 'delete' ? reorderThreeOutline : trash} />
|
||||||
>
|
</IonButton>
|
||||||
<IonIcon icon={queueMode === 'delete' ? reorderThreeOutline : trash} />
|
)}
|
||||||
</IonButton>
|
</div>
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
<div className="max-w-4xl mx-auto p-6">
|
<div className="max-w-4xl mx-auto p-6">
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { IonSearchbar } from '@ionic/react';
|
import { IonSearchbar } from '@ionic/react';
|
||||||
import { InfiniteScrollList, PageHeader, SongItem } from '../../components/common';
|
import { InfiniteScrollList, SongItem } from '../../components/common';
|
||||||
import { useSearch } from '../../hooks';
|
import { useSearch } from '../../hooks';
|
||||||
import { useAppSelector } from '../../redux';
|
import { useAppSelector } from '../../redux';
|
||||||
import { selectIsAdmin, selectSongs } from '../../redux';
|
import { selectIsAdmin, selectSongs } from '../../redux';
|
||||||
@ -39,10 +39,6 @@ const Search: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-4xl mx-auto p-6">
|
<div className="max-w-4xl mx-auto p-6">
|
||||||
<PageHeader
|
|
||||||
title="Search Songs"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
{/* Search Input */}
|
{/* Search Input */}
|
||||||
<IonSearchbar
|
<IonSearchbar
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { IonToggle, IonItem, IonLabel, IonList } from '@ionic/react';
|
import { IonToggle, IonItem, IonLabel, IonList } from '@ionic/react';
|
||||||
import { PageHeader } from '../../components/common';
|
import { } from '../../components/common';
|
||||||
import { useAppSelector } from '../../redux';
|
import { useAppSelector } from '../../redux';
|
||||||
import { selectControllerName } from '../../redux';
|
import { selectControllerName } from '../../redux';
|
||||||
import { settingsService } from '../../firebase/services';
|
import { settingsService } from '../../firebase/services';
|
||||||
@ -62,10 +62,9 @@ const Settings: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHeader
|
<div className="text-sm text-gray-500 text-center mb-4">
|
||||||
title="Settings"
|
Configure player behavior
|
||||||
subtitle="Configure player behavior"
|
</div>
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="max-w-4xl mx-auto p-6">
|
<div className="max-w-4xl mx-auto p-6">
|
||||||
<IonList>
|
<IonList>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { IonItem, IonLabel, IonIcon, IonModal, IonHeader, IonToolbar, IonTitle, IonButton, IonContent, IonInput, IonLabel as IonInputLabel } from '@ionic/react';
|
import { IonItem, IonLabel, IonIcon, IonModal, IonHeader, IonToolbar, IonTitle, IonButton, IonContent, IonInput, IonLabel as IonInputLabel } from '@ionic/react';
|
||||||
import { trash, add, close } from 'ionicons/icons';
|
import { trash, add, close } from 'ionicons/icons';
|
||||||
import { InfiniteScrollList, PageHeader, ActionButton } from '../../components/common';
|
import { InfiniteScrollList, ActionButton } from '../../components/common';
|
||||||
import { useSingers } from '../../hooks';
|
import { useSingers } from '../../hooks';
|
||||||
import { useAppSelector } from '../../redux';
|
import { useAppSelector } from '../../redux';
|
||||||
import { selectSingers } from '../../redux';
|
import { selectSingers } from '../../redux';
|
||||||
@ -70,21 +70,17 @@ const Singers: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHeader
|
<div className="flex justify-between items-center mb-4">
|
||||||
title="Singers"
|
{isAdmin && (
|
||||||
subtitle={`${singersCount} singers in the party`}
|
<IonButton
|
||||||
action={
|
fill="clear"
|
||||||
isAdmin && (
|
onClick={handleOpenAddModal}
|
||||||
<IonButton
|
className="text-primary"
|
||||||
fill="clear"
|
>
|
||||||
onClick={handleOpenAddModal}
|
<IonIcon icon={add} slot="icon-only" />
|
||||||
className="text-primary"
|
</IonButton>
|
||||||
>
|
)}
|
||||||
<IonIcon icon={add} slot="icon-only" />
|
</div>
|
||||||
</IonButton>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="max-w-4xl mx-auto p-6">
|
<div className="max-w-4xl mx-auto p-6">
|
||||||
<InfiniteScrollList<Singer>
|
<InfiniteScrollList<Singer>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useMemo, useCallback } from 'react';
|
import React, { useState, useMemo, useCallback } from 'react';
|
||||||
import { IonItem, IonLabel, IonModal, IonHeader, IonToolbar, IonTitle, IonButton, IonIcon, IonChip, IonContent, IonList, IonAccordionGroup, IonAccordion } from '@ionic/react';
|
import { IonItem, IonLabel, IonModal, IonHeader, IonToolbar, IonTitle, IonButton, IonIcon, IonChip, IonContent, IonList, IonAccordionGroup, IonAccordion } from '@ionic/react';
|
||||||
import { close, list } from 'ionicons/icons';
|
import { close, list } from 'ionicons/icons';
|
||||||
import { InfiniteScrollList, PageHeader, SongItem } from '../../components/common';
|
import { InfiniteScrollList, SongItem } from '../../components/common';
|
||||||
import { useSongLists } from '../../hooks';
|
import { useSongLists } from '../../hooks';
|
||||||
import { useAppSelector } from '../../redux';
|
import { useAppSelector } from '../../redux';
|
||||||
import { selectSongList } from '../../redux';
|
import { selectSongList } from '../../redux';
|
||||||
@ -73,11 +73,6 @@ const SongLists: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHeader
|
|
||||||
title="Song Lists"
|
|
||||||
subtitle={`${songListCount} items loaded`}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="max-w-4xl mx-auto p-6">
|
<div className="max-w-4xl mx-auto p-6">
|
||||||
<InfiniteScrollList<SongList>
|
<InfiniteScrollList<SongList>
|
||||||
items={songLists}
|
items={songLists}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { close, list } from 'ionicons/icons';
|
|||||||
import { useTopPlayed } from '../../hooks';
|
import { useTopPlayed } from '../../hooks';
|
||||||
import { useAppSelector } from '../../redux';
|
import { useAppSelector } from '../../redux';
|
||||||
import { selectTopPlayed, selectSongsArray } from '../../redux';
|
import { selectTopPlayed, selectSongsArray } from '../../redux';
|
||||||
import { InfiniteScrollList, PageHeader, SongItem } from '../../components/common';
|
import { InfiniteScrollList, SongItem } from '../../components/common';
|
||||||
import { filterSongs } from '../../utils/dataProcessing';
|
import { filterSongs } from '../../utils/dataProcessing';
|
||||||
import { useSongOperations } from '../../hooks';
|
import { useSongOperations } from '../../hooks';
|
||||||
import { useToast } from '../../hooks';
|
import { useToast } from '../../hooks';
|
||||||
@ -97,11 +97,6 @@ const Top100: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHeader
|
|
||||||
title="Top 100 Played"
|
|
||||||
subtitle={`${displayCount} items loaded`}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InfiniteScrollList<TopPlayed>
|
<InfiniteScrollList<TopPlayed>
|
||||||
items={displayItems}
|
items={displayItems}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
|||||||
18
src/utils/routeUtils.ts
Normal file
18
src/utils/routeUtils.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// Route to title mapping for dynamic navigation
|
||||||
|
export const getPageTitle = (pathname: string): string => {
|
||||||
|
const routeMap: { [key: string]: string } = {
|
||||||
|
'/': 'Search Songs',
|
||||||
|
'/search': 'Search Songs',
|
||||||
|
'/queue': 'Queue',
|
||||||
|
'/singers': 'Singers',
|
||||||
|
'/artists': 'Artists',
|
||||||
|
'/top-played': 'Top 100 Played',
|
||||||
|
'/favorites': 'Favorites',
|
||||||
|
'/history': 'History',
|
||||||
|
'/new-songs': 'New Songs',
|
||||||
|
'/song-lists': 'Song Lists',
|
||||||
|
'/settings': 'Settings'
|
||||||
|
};
|
||||||
|
|
||||||
|
return routeMap[pathname] || 'Search Songs';
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user