[FEAT] add edit of album and artist

Missing cover that might be studied
This commit is contained in:
Edouard DUPIN 2024-09-04 00:58:05 +02:00
parent 3377f80fbc
commit 091390e025
9 changed files with 449 additions and 58 deletions

View File

@ -0,0 +1,184 @@
import { useRef, useState } from 'react';
import {
Button,
Flex,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Text,
useDisclosure,
} from '@chakra-ui/react';
import {
MdAdminPanelSettings,
MdDeleteForever,
MdEdit,
MdWarning,
} from 'react-icons/md';
import { useNavigate, useParams } from 'react-router-dom';
import { Album, AlbumResource } from '@/back-api';
import { FormGroup } from '@/components/form/FormGroup';
import { FormInput } from '@/components/form/FormInput';
import { FormTextarea } from '@/components/form/FormTextarea';
import { useFormidable } from '@/components/form/Formidable';
import { ConfirmPopUp } from '@/components/popup/ConfirmPopUp';
import { useAlbumService, useSpecificAlbum } from '@/service/Album';
import { useServiceContext } from '@/service/ServiceContext';
import { useCountTracksWithAlbumId } from '@/service/Track';
import { isNullOrUndefined } from '@/utils/validator';
export type AlbumEditPopUpProps = {};
export const AlbumEditPopUp = ({}: AlbumEditPopUpProps) => {
const { albumId } = useParams();
const albumIdInt = isNullOrUndefined(albumId)
? undefined
: parseInt(albumId, 10);
const { session } = useServiceContext();
const { countTracksOfAnAlbum } = useCountTracksWithAlbumId(albumIdInt);
const { store } = useAlbumService();
const { dataAlbum } = useSpecificAlbum(albumIdInt);
const [admin, setAdmin] = useState(false);
const navigate = useNavigate();
const disclosure = useDisclosure();
const onClose = () => {
navigate('../../', { relative: 'path' });
};
const onRemove = () => {
if (isNullOrUndefined(albumIdInt)) {
return;
}
store.remove(
albumIdInt,
AlbumResource.remove({
restConfig: session.getRestConfig(),
params: {
id: albumIdInt,
},
})
);
onClose();
};
const initialRef = useRef(null);
const finalRef = useRef(null);
const form = useFormidable<Album>({
initialValues: dataAlbum,
});
const onSave = async () => {
if (isNullOrUndefined(albumIdInt)) {
return;
}
const dataThatNeedToBeUpdated = form.getDeltaData({ omit: ['covers'] });
console.log(`onSave = ${JSON.stringify(dataThatNeedToBeUpdated, null, 2)}`);
store.update(
AlbumResource.patch({
restConfig: session.getRestConfig(),
data: dataThatNeedToBeUpdated,
params: {
id: albumIdInt,
},
})
);
};
return (
<Modal
initialFocusRef={initialRef}
finalFocusRef={finalRef}
closeOnOverlayClick={false}
onClose={onClose}
isOpen={true}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>Edit Album</ModalHeader>
<ModalCloseButton ref={finalRef} />
<ModalBody pb={6} gap="0px" paddingLeft="18px">
{admin && (
<>
<FormGroup isRequired label="Id">
<Text>{dataAlbum?.id}</Text>
</FormGroup>
{countTracksOfAnAlbum !== 0 && (
<Flex paddingLeft="14px">
<MdWarning color="red.600" />
<Text paddingLeft="6px" color="red.600" fontWeight="bold">
Can not remove album {countTracksOfAnAlbum} track(s) depend
on it.
</Text>
</Flex>
)}
<FormGroup label="Action(s):">
<Button
onClick={disclosure.onOpen}
marginRight="auto"
variant="@danger"
isDisabled={countTracksOfAnAlbum !== 0}
>
<MdDeleteForever /> Remove Media
</Button>
</FormGroup>
<ConfirmPopUp
disclosure={disclosure}
title="Remove album"
body={`Remove Album [${dataAlbum?.id}] ${dataAlbum?.name}`}
confirmTitle="Remove"
onConfirm={onRemove}
/>
</>
)}
{!admin && (
<>
<FormInput
form={form}
variableName="name"
isRequired
label="Title"
ref={initialRef}
/>
<FormTextarea
form={form}
variableName="description"
label="Description"
/>
<FormInput
form={form}
variableName="publication"
label="Publication"
/>
</>
)}
</ModalBody>
<ModalFooter>
<Button
onClick={() => setAdmin((value) => !value)}
marginRight="auto"
>
{admin ? (
<>
<MdEdit />
Edit
</>
) : (
<>
<MdAdminPanelSettings />
Admin
</>
)}
</Button>
{!admin && form.isFormModified && (
<Button colorScheme="blue" mr={3} onClick={onSave}>
Save
</Button>
)}
<Button onClick={onClose}>Cancel</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
};

View File

@ -0,0 +1,187 @@
import { useRef, useState } from 'react';
import {
Button,
Flex,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Text,
useDisclosure,
} from '@chakra-ui/react';
import {
MdAdminPanelSettings,
MdDeleteForever,
MdEdit,
MdWarning,
} from 'react-icons/md';
import { useNavigate, useParams } from 'react-router-dom';
import { Artist, ArtistResource } from '@/back-api';
import { FormGroup } from '@/components/form/FormGroup';
import { FormInput } from '@/components/form/FormInput';
import { FormTextarea } from '@/components/form/FormTextarea';
import { useFormidable } from '@/components/form/Formidable';
import { ConfirmPopUp } from '@/components/popup/ConfirmPopUp';
import { useArtistService, useSpecificArtist } from '@/service/Artist';
import { useServiceContext } from '@/service/ServiceContext';
import { useCountTracksOfAnArtist } from '@/service/Track';
import { isNullOrUndefined } from '@/utils/validator';
export type ArtistEditPopUpProps = {};
export const ArtistEditPopUp = ({}: ArtistEditPopUpProps) => {
const { artistId } = useParams();
const artistIdInt = isNullOrUndefined(artistId)
? undefined
: parseInt(artistId, 10);
const { session } = useServiceContext();
const { countTracksOnAnArtist } = useCountTracksOfAnArtist(artistIdInt);
const { store } = useArtistService();
const { dataArtist } = useSpecificArtist(artistIdInt);
const [admin, setAdmin] = useState(false);
const navigate = useNavigate();
const disclosure = useDisclosure();
const onClose = () => {
navigate('../../', { relative: 'path' });
};
const onRemove = () => {
if (isNullOrUndefined(artistIdInt)) {
return;
}
store.remove(
artistIdInt,
ArtistResource.remove({
restConfig: session.getRestConfig(),
params: {
id: artistIdInt,
},
})
);
onClose();
};
const initialRef = useRef(null);
const finalRef = useRef(null);
const form = useFormidable<Artist>({
initialValues: dataArtist,
});
const onSave = async () => {
if (isNullOrUndefined(artistIdInt)) {
return;
}
const dataThatNeedToBeUpdated = form.getDeltaData({ omit: ['covers'] });
console.log(`onSave = ${JSON.stringify(dataThatNeedToBeUpdated, null, 2)}`);
store.update(
ArtistResource.patch({
restConfig: session.getRestConfig(),
data: dataThatNeedToBeUpdated,
params: {
id: artistIdInt,
},
})
);
};
return (
<Modal
initialFocusRef={initialRef}
finalFocusRef={finalRef}
closeOnOverlayClick={false}
onClose={onClose}
isOpen={true}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>Edit Artist</ModalHeader>
<ModalCloseButton ref={finalRef} />
<ModalBody pb={6} gap="0px" paddingLeft="18px">
{admin && (
<>
<FormGroup isRequired label="Id">
<Text>{dataArtist?.id}</Text>
</FormGroup>
{countTracksOnAnArtist !== 0 && (
<Flex paddingLeft="14px">
<MdWarning color="red.600" />
<Text paddingLeft="6px" color="red.600" fontWeight="bold">
Can not remove artist {countTracksOnAnArtist} track(s)
depend on it.
</Text>
</Flex>
)}
<FormGroup label="Action(s):">
<Button
onClick={disclosure.onOpen}
marginRight="auto"
variant="@danger"
isDisabled={countTracksOnAnArtist !== 0}
>
<MdDeleteForever /> Remove Media
</Button>
</FormGroup>
<ConfirmPopUp
disclosure={disclosure}
title="Remove artist"
body={`Remove Artist [${dataArtist?.id}] ${dataArtist?.name}`}
confirmTitle="Remove"
onConfirm={onRemove}
/>
</>
)}
{!admin && (
<>
<FormInput
form={form}
variableName="name"
isRequired
label="Artist name"
ref={initialRef}
/>
<FormTextarea
form={form}
variableName="description"
label="Description"
/>
<FormInput
form={form}
variableName="firstName"
label="First Name"
/>
<FormInput form={form} variableName="surname" label="SurName" />
<FormInput form={form} variableName="birth" label="Birth date" />
<FormInput form={form} variableName="death" label="Death date" />
</>
)}
</ModalBody>
<ModalFooter>
<Button
onClick={() => setAdmin((value) => !value)}
marginRight="auto"
>
{admin ? (
<>
<MdEdit />
Edit
</>
) : (
<>
<MdAdminPanelSettings />
Admin
</>
)}
</Button>
{!admin && form.isFormModified && (
<Button colorScheme="blue" mr={3} onClick={onSave}>
Save
</Button>
)}
<Button onClick={onClose}>Cancel</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
};

View File

@ -1,9 +1,7 @@
import { useEffect, useRef, useState } from 'react';
import { useRef, useState } from 'react';
import {
Button,
IconButton,
Input,
Modal,
ModalBody,
ModalCloseButton,
@ -11,21 +9,10 @@ import {
ModalFooter,
ModalHeader,
ModalOverlay,
NumberDecrementStepper,
NumberIncrementStepper,
NumberInput,
NumberInputField,
NumberInputStepper,
Text,
Textarea,
useDisclosure,
} from '@chakra-ui/react';
import {
MdAdminPanelSettings,
MdDeleteForever,
MdEdit,
MdRemove,
} from 'react-icons/md';
import { MdAdminPanelSettings, MdDeleteForever, MdEdit } from 'react-icons/md';
import { useNavigate, useParams } from 'react-router-dom';
import { Track, TrackResource } from '@/back-api';
@ -37,8 +24,6 @@ import { FormSelectMultiple } from '@/components/form/FormSelectMultiple';
import { FormTextarea } from '@/components/form/FormTextarea';
import { useFormidable } from '@/components/form/Formidable';
import { ConfirmPopUp } from '@/components/popup/ConfirmPopUp';
import { SelectMultiple } from '@/components/select/SelectMultiple';
import { SelectSingle } from '@/components/select/SelectSingle';
import { useOrderedAlbums } from '@/service/Album';
import { useOrderedArtists } from '@/service/Artist';
import { useOrderedGenders } from '@/service/Gender';

View File

@ -1,13 +1,9 @@
import { Suspense } from 'react';
import { Flex, Text } from '@chakra-ui/react';
import { LuMusic2, LuPlay } from 'react-icons/lu';
import { useNavigate } from 'react-router-dom';
import { Track } from '@/back-api';
import { Covers } from '@/components/Cover';
import { ContextMenu, MenuElement } from '@/components/contextMenu/ContextMenu';
import { DisplayTrackSkeleton } from '@/components/track/DisplayTrackSkeleton';
import { useActivePlaylistService } from '@/service/ActivePlaylist';
export type DisplayTrackProps = {

View File

@ -1,12 +1,14 @@
import { Box, Flex, Text } from '@chakra-ui/react';
import { Box, Button, Flex, Text } from '@chakra-ui/react';
import { LuDisc3 } from 'react-icons/lu';
import { MdEdit } from 'react-icons/md';
import { Route, Routes, useNavigate, useParams } from 'react-router-dom';
import { Covers } from '@/components/Cover';
import { EmptyEnd } from '@/components/EmptyEnd';
import { PageLayout } from '@/components/Layout/PageLayout';
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
import { TopBar } from '@/components/TopBar/TopBar';
import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar';
import { AlbumEditPopUp } from '@/components/popup/AlbumEditPopUp';
import { TrackEditPopUp } from '@/components/popup/TrackEditPopUp';
import { DisplayTrack } from '@/components/track/DisplayTrack';
import { DisplayTrackFull } from '@/components/track/DisplayTrackFull';
@ -53,7 +55,16 @@ export const AlbumDetailPage = () => {
}
return (
<>
<TopBar title="Album detail" />
<TopBar title="Album detail">
<Button
{...BUTTON_TOP_BAR_PROPERTY}
onClick={() =>
navigate(`/album/${albumId}/edit-album/${dataAlbum.id}`)
}
>
<MdEdit />
</Button>
</TopBar>
<PageLayout>
<Flex
direction="row"
@ -108,7 +119,7 @@ export const AlbumDetailPage = () => {
{
name: 'Edit',
onClick: () => {
navigate(`/album/${albumId}/edit/${data.id}`);
navigate(`/album/${albumId}/edit-track/${data.id}`);
},
},
{ name: 'Add Playlist', onClick: () => {} },
@ -119,7 +130,8 @@ export const AlbumDetailPage = () => {
<EmptyEnd />
</Flex>
<Routes>
<Route path="edit/:trackId" element={<TrackEditPopUp />} />
<Route path="edit-track/:trackId" element={<TrackEditPopUp />} />
<Route path="edit-album/:albumId" element={<AlbumEditPopUp />} />
</Routes>
</PageLayout>
</>

View File

@ -1,6 +1,6 @@
import { Box, Button, Flex, Text } from '@chakra-ui/react';
import { LuDisc3, LuUser } from 'react-icons/lu';
import { MdPerson } from 'react-icons/md';
import { MdEdit, MdPerson } from 'react-icons/md';
import { Route, Routes, useNavigate, useParams } from 'react-router-dom';
import { Covers } from '@/components/Cover';
@ -8,6 +8,7 @@ import { EmptyEnd } from '@/components/EmptyEnd';
import { PageLayout } from '@/components/Layout/PageLayout';
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar';
import { AlbumEditPopUp } from '@/components/popup/AlbumEditPopUp';
import { TrackEditPopUp } from '@/components/popup/TrackEditPopUp';
import { DisplayTrack } from '@/components/track/DisplayTrack';
import { useActivePlaylistService } from '@/service/ActivePlaylist';
@ -57,21 +58,31 @@ export const ArtistAlbumDetailPage = () => {
<>
<TopBar title={dataArtist ? undefined : 'Album detail'}>
{dataArtist && (
<Button
{...BUTTON_TOP_BAR_PROPERTY}
marginRight="auto"
onClick={() => navigate(`/artist/${dataArtist.id}`)}
>
<Covers
data={dataArtist?.covers}
size="35px"
borderRadius="full"
iconEmpty={<MdPerson height="full" />}
/>
<Text fontSize="24px" fontWeight="bold">
{dataArtist?.name}
</Text>
</Button>
<>
<Button
{...BUTTON_TOP_BAR_PROPERTY}
marginRight="auto"
onClick={() => navigate(`/artist/${dataArtist.id}`)}
>
<Covers
data={dataArtist?.covers}
size="35px"
borderRadius="full"
iconEmpty={<MdPerson height="full" />}
/>
<Text fontSize="24px" fontWeight="bold">
{dataArtist?.name}
</Text>
</Button>
<Button
{...BUTTON_TOP_BAR_PROPERTY}
onClick={() =>
navigate(`/album/${albumId}/edit-album/${dataAlbum.id}`)
}
>
<MdEdit />
</Button>
</>
)}
</TopBar>
<PageLayout>
@ -129,7 +140,7 @@ export const ArtistAlbumDetailPage = () => {
name: 'Edit',
onClick: () => {
navigate(
`/artist/${artistIdInt}/album/${albumId}/edit/${data.id}`
`/artist/${artistIdInt}/album/${albumId}/edit-track/${data.id}`
);
},
},
@ -141,7 +152,8 @@ export const ArtistAlbumDetailPage = () => {
<EmptyEnd />
</Flex>
<Routes>
<Route path="edit/:trackId" element={<TrackEditPopUp />} />
<Route path="edit-track/:trackId" element={<TrackEditPopUp />} />
<Route path="edit-album/:albumId" element={<AlbumEditPopUp />} />
</Routes>
</PageLayout>
</>

View File

@ -1,7 +1,7 @@
import { Button, Flex, Text, Wrap, WrapItem } from '@chakra-ui/react';
import { LuUser } from 'react-icons/lu';
import { MdGroup } from 'react-icons/md';
import { useNavigate, useParams } from 'react-router-dom';
import { MdEdit, MdGroup } from 'react-icons/md';
import { Route, Routes, useNavigate, useParams } from 'react-router-dom';
import { Covers } from '@/components/Cover';
import { EmptyEnd } from '@/components/EmptyEnd';
@ -9,6 +9,7 @@ import { PageLayout } from '@/components/Layout/PageLayout';
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar';
import { DisplayAlbumId } from '@/components/album/DisplayAlbumId';
import { ArtistEditPopUp } from '@/components/popup/ArtistEditPopUp';
import { useSpecificArtist } from '@/service/Artist';
import { useAlbumIdsOfAnArtist } from '@/service/Track';
import { useThemeMode } from '@/utils/theme-tools';
@ -37,16 +38,27 @@ export const ArtistDetailPage = () => {
return (
<>
<TopBar>
<Button
{...BUTTON_TOP_BAR_PROPERTY}
marginRight="auto"
onClick={() => navigate(`/artist/all`)}
>
<MdGroup height="full" />
<Text fontSize="24px" fontWeight="bold">
Artists
</Text>
</Button>
<>
<Button
{...BUTTON_TOP_BAR_PROPERTY}
marginRight="auto"
onClick={() => navigate(`/artist/all`)}
>
<MdGroup height="full" />
<Text fontSize="24px" fontWeight="bold">
Artists
</Text>
</Button>
<Button
{...BUTTON_TOP_BAR_PROPERTY}
onClick={() =>
navigate(`/artist/${artistId}/edit-artist/${artistId}`)
}
>
<MdEdit />
</Button>
</>
</TopBar>
<PageLayout>
<Flex
@ -97,6 +109,9 @@ export const ArtistDetailPage = () => {
))}
</Wrap>
<EmptyEnd />
<Routes>
<Route path="edit-artist/:artistId" element={<ArtistEditPopUp />} />
</Routes>
</PageLayout>
</>
);

View File

@ -10,7 +10,7 @@ export const ArtistRoutes = () => {
<Routes>
<Route path="/" element={<Navigate to="all" replace />} />
<Route path="all" element={<ArtistsPage />} />
<Route path=":artistId" element={<ArtistDetailPage />} />
<Route path=":artistId/*" element={<ArtistDetailPage />} />
<Route
path=":artistId/album/:albumId/*"
element={<ArtistAlbumDetailPage />}

View File

@ -73,7 +73,7 @@ export const useTracksOfAnArtist = (idArtist?: number) => {
* @param id - Id of the artist.
* @returns The number of track present in this artist
*/
export const useCountTracksOfAnArtist = (idArtist: number) => {
export const useCountTracksOfAnArtist = (idArtist?: number) => {
const { isLoading, tracksOnAnAlbum } = useTracksOfAnAlbum(idArtist);
const countTracksOnAnArtist = useMemo(
() => tracksOnAnAlbum?.length ?? 0,