Work on form and basic tools I need

This commit is contained in:
Edouard DUPIN 2024-09-01 11:59:02 +02:00
parent 873f0acff5
commit ec93c31acb
19 changed files with 1562 additions and 454 deletions

View File

@ -53,7 +53,7 @@
"react-popper": "2.3.0",
"react-router-dom": "6.26.1",
"react-select": "5.8.0",
"react-simple-keyboard": "3.7.147",
"react-simple-keyboard": "3.7.148",
"react-sticky-el": "2.1.1",
"react-use": "17.5.1",
"react-use-draggable-scroll": "0.4.7",
@ -74,16 +74,16 @@
"@storybook/react-vite": "8.2.9",
"@storybook/theming": "8.2.9",
"@testing-library/jest-dom": "6.5.0",
"@testing-library/react": "16.0.0",
"@testing-library/react": "16.0.1",
"@testing-library/user-event": "14.5.2",
"@trivago/prettier-plugin-sort-imports": "4.3.0",
"@types/jest": "29.5.12",
"@types/node": "22.5.0",
"@types/react": "18.3.4",
"@types/node": "22.5.1",
"@types/react": "18.3.5",
"@types/react-dom": "18.3.0",
"@types/react-sticky-el": "1.0.7",
"@typescript-eslint/eslint-plugin": "8.2.0",
"@typescript-eslint/parser": "8.2.0",
"@typescript-eslint/eslint-plugin": "8.3.0",
"@typescript-eslint/parser": "8.3.0",
"@vitejs/plugin-react": "4.3.1",
"eslint": "9.9.1",
"eslint-plugin-codeceptjs": "1.3.0",
@ -93,16 +93,16 @@
"eslint-plugin-storybook": "0.8.0",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"knip": "5.27.4",
"knip": "5.27.5",
"lint-staged": "15.2.9",
"npm-check-updates": "^17.1.0",
"prettier": "3.3.3",
"puppeteer": "23.1.1",
"puppeteer": "23.2.1",
"react-is": "18.3.1",
"storybook": "8.2.9",
"ts-node": "10.9.2",
"typescript": "5.5.4",
"vite": "5.4.2",
"vitest": "2.0.5",
"npm-check-updates": "^17.1.0"
"vitest": "2.0.5"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -32,12 +32,6 @@ export const SearchInput = ({
width: '250px',
});
}
const ref = React.useRef(null);
// TODO: find a better way...
useOutsideClick({
ref: ref,
handler: onFocusLost,
});
function onChange(event): void {
const data =
event.target.value.length === 0 ? undefined : event.target.value;
@ -57,8 +51,8 @@ export const SearchInput = ({
<MdSearch color="gray.300" />
</InputLeftElement>
<Input
ref={ref}
onFocus={onFocusKeep}
onBlur={() => setTimeout(() => onFocusLost(), 200)}
onChange={onChange}
onSubmit={onSubmit}
/>

View File

@ -41,6 +41,11 @@ import { useThemeMode } from '@/utils/theme-tools';
export const TOP_BAR_HEIGHT = '50px';
export const BUTTON_TOP_BAR_PROPERTY = {
variant: '@menu',
height: TOP_BAR_HEIGHT,
};
export type TopBarProps = {
children?: ReactNode;
title?: string;
@ -48,10 +53,7 @@ export type TopBarProps = {
export const TopBar = ({ title, children }: TopBarProps) => {
const { mode, colorMode, toggleColorMode } = useThemeMode();
const buttonProperty = {
variant: '@menu',
height: TOP_BAR_HEIGHT,
};
const { session } = useServiceContext();
const backColor = mode('back.100', 'back.800');
const drawerDisclose = useDisclosure();
@ -86,7 +88,7 @@ export const TopBar = ({ title, children }: TopBarProps) => {
boxShadow={'0px 2px 4px ' + colors.back[900]}
zIndex={200}
>
<Button {...buttonProperty} onClick={onChangeTheme}>
<Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onChangeTheme}>
<LuAlignJustify />
<Text paddingLeft="3px" fontWeight="bold">
Karusic
@ -107,13 +109,17 @@ export const TopBar = ({ title, children }: TopBarProps) => {
<Flex right="0">
{session?.state !== SessionState.CONNECTED && (
<>
<Button {...buttonProperty} onClick={onSignIn}>
<Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onSignIn}>
<LuLogIn />
<Text paddingLeft="3px" fontWeight="bold">
Sign-in
</Text>
</Button>
<Button {...buttonProperty} onClick={onSignUp} disabled={true}>
<Button
{...BUTTON_TOP_BAR_PROPERTY}
onClick={onSignUp}
disabled={true}
>
<LuPlusCircle />
<Text paddingLeft="3px" fontWeight="bold">
Sign-up
@ -127,14 +133,13 @@ export const TopBar = ({ title, children }: TopBarProps) => {
as={IconButton}
aria-label="Options"
icon={<LuUserCircle />}
{...buttonProperty}
{...BUTTON_TOP_BAR_PROPERTY}
width={TOP_BAR_HEIGHT}
/>
<MenuList>
<MenuItem _hover={{}} color={mode('brand.800', 'brand.200')}>
Sign in as {session?.login ?? 'Fail'}
</MenuItem>
<MenuItem icon={<LuArrowUpSquare />}>Add Media</MenuItem>
<MenuItem icon={<LuSettings />}>Settings</MenuItem>
<MenuItem icon={<LuHelpCircle />}>Help</MenuItem>
<MenuItem icon={<LuLogOut onClick={onSignOut} />}>
@ -176,16 +181,30 @@ export const TopBar = ({ title, children }: TopBarProps) => {
</Text>
</HStack>
</DrawerHeader>
<DrawerBody>
<Button {...buttonProperty} onClick={onSelectHome} width="fill">
<DrawerBody paddingX="0px">
<Button
background="#00000000"
borderRadius="0px"
onClick={onSelectHome}
width="full"
>
<LuHome />
<Text paddingLeft="3px" fontWeight="bold">
<Text paddingLeft="3px" fontWeight="bold" marginRight="auto">
Home
</Text>
</Button>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
<hr />
<Button
background="#00000000"
borderRadius="0px"
onClick={onSelectHome}
width="full"
>
<LuArrowUpSquare />
<Text paddingLeft="3px" fontWeight="bold" marginRight="auto">
Add Media
</Text>
</Button>
</DrawerBody>
</DrawerContent>
</Drawer>

View File

@ -0,0 +1,42 @@
import { useState } from 'react';
import {
IconButton,
Menu,
MenuButton,
MenuItem,
MenuList,
} from '@chakra-ui/react';
import { LuMenu } from 'react-icons/lu';
export type MenuElement = {
name: string;
onClick: () => void;
};
export type ContextMenuProps = {
elements?: MenuElement[];
};
export const ContextMenu = ({ elements }: ContextMenuProps) => {
if (!elements) {
return <></>;
}
return (
<Menu>
<MenuButton
as={IconButton}
aria-label="Options"
icon={<LuMenu />}
marginY="auto"
/>
<MenuList>
{elements?.map((data) => (
<MenuItem key={data.name} onClick={data.onClick}>
{data.name}
</MenuItem>
))}
</MenuList>
</Menu>
);
};

View File

@ -0,0 +1,35 @@
import { ReactNode } from 'react';
import {
FormControl,
FormControlProps,
FormErrorMessage,
FormHelperText,
FormLabel,
} from '@chakra-ui/react';
import { MdLabelImportant } from 'react-icons/md';
export type FormGroupProps = FormControlProps & {
error?: ReactNode;
help?: ReactNode;
label?: ReactNode;
};
export const FormGroup = ({
children,
error,
help,
label,
...props
}: FormGroupProps) => (
<FormControl marginTop="4px" {...props}>
{!!label && <FormLabel>{label}</FormLabel>}
{children}
{!!help && <FormHelperText>{help}</FormHelperText>}
<FormErrorMessage>
<MdLabelImportant />
{error}
</FormErrorMessage>
</FormControl>
);

View File

@ -0,0 +1,87 @@
import { Box, Button, Flex, Text } from '@chakra-ui/react';
import { isNullOrUndefined } from '@/utils/validator';
export type SelectMultipleValueDisplayType = {
id: any;
name: any;
isSelected: boolean;
};
const optionToOptionDisplay = (
data: SelectMultipleValueDisplayType[] | undefined,
selectedOptions: SelectMultipleValueDisplayType[],
search?: string
): SelectMultipleValueDisplayType[] => {
if (isNullOrUndefined(data)) {
return [];
}
const out: SelectMultipleValueDisplayType[] = [];
data.forEach((element) => {
if (search) {
if (!element.name.toLowerCase().includes(search.toLowerCase())) {
return;
}
}
out.push({
...element,
isSelected:
selectedOptions.find((elem) => elem.id === element.id) !== undefined,
});
});
return out;
};
export type FormSelectListProps = {
options?: SelectMultipleValueDisplayType[];
selected: SelectMultipleValueDisplayType[];
onSelectValue: (data: SelectMultipleValueDisplayType) => void;
search?: string;
};
export const FormSelectList = ({
options,
selected,
onSelectValue,
search,
}: FormSelectListProps) => {
const displayedValue = optionToOptionDisplay(options, selected, search);
return (
<Box position="relative">
<Flex
direction="column"
width="full"
position="absolute"
border="1px"
borderColor="black"
backgroundColor="gray.700"
overflowY="auto"
overflowX="hidden"
maxHeight="300px"
zIndex={300}
transform="translateY(1px)"
>
{displayedValue.length === 0 && (
<Text marginX="auto" color="red.500" fontWeight="bold" marginY="10px">
... No element found...
</Text>
)}
{displayedValue.map((data) => (
<Button
key={data.id}
marginY="1px"
borderRadius="0px"
autoFocus={false}
backgroundColor={data.isSelected ? 'green.800' : '0x00000000'}
_hover={{ backgroundColor: 'gray.400' }}
onClick={() => onSelectValue(data)}
>
<Text marginRight="auto" autoFocus={false}>
{data.name}
</Text>
</Button>
))}
</Flex>
</Box>
);
};

View File

@ -0,0 +1,126 @@
import { useMemo, useState } from 'react';
import {
Flex,
Input,
InputGroup,
InputRightElement,
Spinner,
Tag,
TagCloseButton,
TagLabel,
Wrap,
WrapItem,
} from '@chakra-ui/react';
import { MdSearch } from 'react-icons/md';
import {
FormSelectList,
SelectMultipleValueDisplayType,
} from '@/components/form/FormSelectList';
import { isNullOrUndefined } from '@/utils/validator';
export type SelectMultipleProps = {
options?: object[];
values?: (number | string)[];
onChange?: (value: (number | string)[] | undefined) => void;
keyKey?: string;
keyValue?: string;
};
export const SelectMultiple = ({
options,
onChange,
values,
keyKey = 'id',
keyValue = keyKey,
}: SelectMultipleProps) => {
const [showList, setShowList] = useState(false);
const transformedOption = useMemo(() => {
return options?.map((element) => {
return {
id: element[keyKey],
name: element[keyValue],
isSelected: false,
} as SelectMultipleValueDisplayType;
});
}, [options, keyKey, keyValue]);
const [currentSearch, setCurrentSearch] = useState<string | undefined>(
undefined
);
const selectedOptions = useMemo(() => {
if (isNullOrUndefined(values) || !transformedOption) {
return [];
}
return transformedOption.filter((element) => {
return values.includes(element[keyKey]);
});
}, [values, transformedOption]);
const selectValue = (data: SelectMultipleValueDisplayType) => {
const newValues = values?.includes(data.id)
? values.filter((elem) => data.id === elem)
: [...(values ?? []), data.id];
setShowList(false);
if (onChange) {
if (newValues.length == 0) {
onChange(undefined);
} else {
onChange(newValues);
}
}
};
if (!options) {
return <Spinner />;
}
function onChangeInput(value: string): void {
if (value === '') {
setCurrentSearch(undefined);
} else {
setCurrentSearch(value);
}
}
return (
<Flex direction="column" width="full" gap="0px">
{selectedOptions && (
<Wrap spacing="5px" justify="left" width="full" marginBottom="2px">
{selectedOptions.map((data) => (
<WrapItem key={data[keyKey]}>
<Tag
size="md"
key="md"
borderRadius="5px"
variant="solid"
backgroundColor="green.500"
>
<TagLabel>{data[keyValue] ?? `id=${data[keyKey]}`}</TagLabel>
<TagCloseButton onClick={() => selectValue(data)} />
</Tag>
</WrapItem>
))}
</Wrap>
)}
<InputGroup minWidth="50%" marginLeft="auto">
<InputRightElement pointerEvents="none">
<MdSearch color="gray.300" />
</InputRightElement>
<Input
onChange={(e) => onChangeInput(e.target.value)}
//onSubmit={onSubmit}
onFocus={() => setShowList(true)}
onBlur={() => setTimeout(() => setShowList(false), 200)}
/>
</InputGroup>
{showList && (
<FormSelectList
options={transformedOption}
selected={selectedOptions}
search={currentSearch}
onSelectValue={selectValue}
/>
)}
</Flex>
);
};

View File

@ -0,0 +1,137 @@
import { useMemo, useRef, useState } from 'react';
import {
Badge,
Box,
Button,
Flex,
Input,
InputGroup,
InputRightElement,
Spinner,
Tag,
TagCloseButton,
TagLabel,
} from '@chakra-ui/react';
import {
MdClose,
MdKeyboardArrowDown,
MdKeyboardArrowUp,
MdSearch,
} from 'react-icons/md';
import {
FormSelectList,
SelectMultipleValueDisplayType,
} from '@/components/form/FormSelectList';
import { isNullOrUndefined } from '@/utils/validator';
export type SelectSingleProps = {
options?: object[];
value?: number | string;
onChange?: (value: number | string | undefined) => void;
keyKey?: string;
keyValue?: string;
};
export const SelectSingle = ({
options,
onChange,
value,
keyKey = 'id',
keyValue = keyKey,
}: SelectSingleProps) => {
const [showList, setShowList] = useState(false);
const transformedOption = useMemo(() => {
return options?.map((element) => {
return {
id: element[keyKey],
name: element[keyValue],
isSelected: false,
} as SelectMultipleValueDisplayType;
});
}, [options, keyKey, keyValue]);
const [currentSearch, setCurrentSearch] = useState<string | undefined>(
undefined
);
const ref = useRef<HTMLInputElement | null>(null);
const selectedOptions = useMemo(() => {
if (isNullOrUndefined(value)) {
return undefined;
}
return transformedOption?.find((data) => data.id === value[keyKey]);
}, [value, transformedOption]);
const selectValue = (data?: SelectMultipleValueDisplayType) => {
const tmpData = data?.id == selectedOptions?.id ? undefined : data;
setShowList(false);
if (onChange) {
onChange(tmpData?.id);
}
};
if (!transformedOption) {
return <Spinner />;
}
function onChangeInput(value: string): void {
if (value === '') {
setCurrentSearch(undefined);
} else {
setCurrentSearch(value);
}
}
const onRemoveItem = () => {
if (showList || !selectedOptions) {
ref?.current?.focus();
return;
}
console.log('Remove item ...');
if (onChange) {
onChange(undefined);
}
};
return (
<Flex direction="column" width="full" gap="0px">
<Flex>
<Input
ref={ref}
width="full"
onChange={(e) => onChangeInput(e.target.value)}
//onSubmit={onSubmit}
onFocus={() => setShowList(true)}
onBlur={() => setTimeout(() => setShowList(false), 200)}
value={
showList ? (currentSearch ?? '') : (selectedOptions?.name ?? '')
}
backgroundColor={
showList || !selectedOptions ? undefined : 'green.500'
}
borderRadius="5px 0 0 5px"
/>
<Button
onClick={onRemoveItem}
variant="outline"
borderRadius="0 5px 5px 0"
borderWidth="1px 1px 1px 0"
isDisabled={showList}
>
{selectedOptions ? (
<MdClose color="gray.300" />
) : showList ? (
<MdKeyboardArrowUp color="gray.300" />
) : (
<MdKeyboardArrowDown color="gray.300" />
)}
</Button>
</Flex>
{showList && (
<FormSelectList
options={transformedOption}
selected={selectedOptions ? [selectedOptions] : []}
search={currentSearch}
onSelectValue={selectValue}
/>
)}
</Flex>
);
};

View File

@ -0,0 +1,179 @@
import { ChangeEvent, useEffect, useRef, useState } from 'react';
import {
Button,
Flex,
FormControl,
FormLabel,
Input,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
NumberInput,
Select,
Text,
Textarea,
} from '@chakra-ui/react';
import { useNavigate, useParams } from 'react-router-dom';
import { Album, Track } from '@/back-api';
import { FormGroup } from '@/components/form/FormGroup';
import { SelectMultiple } from '@/components/form/SelectMultiple';
import { SelectSingle } from '@/components/form/SelectSingle';
import { useOrderedAlbums } from '@/service/Album';
import { useOrderedArtists } from '@/service/Artist';
import { useOrderedGenders } from '@/service/Gender';
import { useSpecificTrack } from '@/service/Track';
import { isNullOrUndefined } from '@/utils/validator';
export type TrackEditPopUpProps = {};
const getDifferences = (obj1: any, obj2: any): { [key: string]: boolean } => {
const result: { [key: string]: boolean } = {};
for (const key in obj1) {
if (obj1.hasOwnProperty(key)) {
result[key] = obj1[key] !== obj2[key];
}
}
return result;
};
const hasAnyTrue = (obj: { [key: string]: boolean }): boolean => {
for (const key in obj) {
if (obj.hasOwnProperty(key) && obj[key] === true) {
return true;
}
}
return false;
};
export const TrackEditPopUp = ({}: TrackEditPopUpProps) => {
const { trackId } = useParams();
const { dataGenders } = useOrderedGenders(undefined);
const { dataArtist } = useOrderedArtists(undefined);
const { dataAlbums } = useOrderedAlbums(undefined);
const { dataTrack } = useSpecificTrack(
isNullOrUndefined(trackId) ? undefined : parseInt(trackId, 10)
);
const navigate = useNavigate();
const onClose = () => {
navigate('../../', { relative: 'path' });
};
const initialRef = useRef(null);
const finalRef = useRef(null);
const [newData, setNewData] = useState<Track>();
const [changeData, setChangeData] = useState<{ [key: string]: boolean }>();
const [changeOneData, setChangeOneData] = useState<boolean>(false);
// initialize value when ready
useEffect(() => {
setNewData(dataTrack);
}, [dataTrack, setNewData, setChangeData]);
useEffect(() => {
if (!newData) {
return;
}
const ret = getDifferences(dataTrack, newData);
setChangeData(ret);
setChangeOneData(hasAnyTrue(ret));
console.log(
`ChangeData=${JSON.stringify(ret, null, 2)} ChangeOneData=${hasAnyTrue(ret)}`
);
}, [dataTrack, newData, setChangeData, setChangeOneData]);
const onChangeValue = (key: string, data) => {
console.log(`[${key}] data: ${data}`);
setNewData((previous) => {
if (previous === undefined) {
return previous;
}
if (previous[key] === data) {
return previous;
}
const tmp = { ...previous };
tmp[key] = data;
console.log(`data = ${JSON.stringify(tmp, null, 2)}`);
return tmp;
});
};
return (
<Modal
initialFocusRef={initialRef}
finalFocusRef={finalRef}
closeOnOverlayClick={false}
onClose={onClose}
isOpen={true}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>Edit Track</ModalHeader>
<ModalCloseButton ref={finalRef} />
<ModalBody pb={6}>
<FormGroup isRequired>
<FormLabel>Title</FormLabel>
<Input
ref={initialRef}
value={dataTrack?.name}
onChange={(e) => onChangeValue('name', e.target.value)}
/>
</FormGroup>
<FormGroup>
<label>Description</label>
<Textarea
value={dataTrack?.description}
onChange={(e) => onChangeValue('description', e.target.value)}
/>
</FormGroup>
<FormGroup>
<label>Gender</label>
<SelectSingle
value={dataTrack?.genderId}
options={dataGenders}
onChange={(value) => onChangeValue('genderId', value)}
keyValue="name"
/>
</FormGroup>
<FormGroup>
<label>Artist(s)</label>
<SelectMultiple
options={dataArtist}
onChange={(value) => onChangeValue('artists', value)}
keyValue="name"
/>
</FormGroup>
<FormGroup>
<label>Album</label>
<SelectSingle
options={dataAlbums}
onChange={(value) => onChangeValue('albumId', value)}
keyValue="name"
/>
</FormGroup>
<FormControl>
<FormLabel>track n°</FormLabel>
<NumberInput
value={dataTrack?.track}
onChange={(e) => onChangeValue('track', e)}
/>
</FormControl>
</ModalBody>
<ModalFooter>
{changeOneData && (
<Button colorScheme="blue" mr={3}>
Save
</Button>
)}
<Button onClick={onClose}>Cancel</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
};

View File

@ -5,6 +5,7 @@ import { LuMusic2, LuPlay } from 'react-icons/lu';
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';
import { useSpecificAlbum } from '@/service/Album';
@ -14,8 +15,13 @@ import { useSpecificGender } from '@/service/Gender';
export type DisplayTrackProps = {
track: Track;
onClick?: () => void;
contextMenu?: MenuElement[];
};
export const DisplayTrackFull = ({ track, onClick }: DisplayTrackProps) => {
export const DisplayTrackFull = ({
track,
onClick,
contextMenu,
}: DisplayTrackProps) => {
const { trackActive } = useActivePlaylistService();
const { dataAlbum } = useSpecificAlbum(track?.albumId);
const { dataGender } = useSpecificGender(track?.genderId);
@ -106,6 +112,7 @@ export const DisplayTrackFull = ({ track, onClick }: DisplayTrackProps) => {
</Text>
)}
</Flex>
<ContextMenu elements={contextMenu} />
</Flex>
);
};

View File

@ -1,13 +1,15 @@
import { Box, Flex, Text } from '@chakra-ui/react';
import { LuDisc3 } from 'react-icons/lu';
import { useParams } from 'react-router-dom';
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 { TrackEditPopUp } from '@/components/popup/TrackEditPopUp';
import { DisplayTrack } from '@/components/track/DisplayTrack';
import { DisplayTrackFull } from '@/components/track/DisplayTrackFull';
import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { useSpecificAlbum } from '@/service/Album';
import { useTracksOfAnAlbum } from '@/service/Track';
@ -20,6 +22,7 @@ export const AlbumDetailPage = () => {
const { playInList } = useActivePlaylistService();
const { dataAlbum } = useSpecificAlbum(albumIdInt);
const { tracksOnAnAlbum } = useTracksOfAnAlbum(albumIdInt);
const navigate = useNavigate();
const onSelectItem = (trackId: number) => {
//navigate(`/artist/${artistIdInt}/album/${albumId}`);
let currentPlay = 0;
@ -86,7 +89,7 @@ export const AlbumDetailPage = () => {
{tracksOnAnAlbum?.map((data) => (
<Box
minWidth="100%"
height="60px"
//height="60px"
border="1px"
borderColor="brand.900"
backgroundColor={mode('#FFFFFF88', '#00000088')}
@ -98,14 +101,26 @@ export const AlbumDetailPage = () => {
bgColor: mode('#FFFFFFF7', '#000000F7'),
}}
>
<DisplayTrack
<DisplayTrackFull
track={data}
onClick={() => onSelectItem(data.id)}
contextMenu={[
{
name: 'Edit',
onClick: () => {
navigate(`/album/${albumId}/edit/${data.id}`);
},
},
{ name: 'Add Playlist', onClick: () => {} },
]}
/>
</Box>
))}
<EmptyEnd />
</Flex>
<Routes>
<Route path="edit/:trackId" element={<TrackEditPopUp />} />
</Routes>
</PageLayout>
</>
);

View File

@ -9,7 +9,7 @@ export const AlbumRoutes = () => {
<Routes>
<Route path="/" element={<Navigate to="all" replace />} />
<Route path="all" element={<AlbumsPage />} />
<Route path=":albumId" element={<AlbumDetailPage />} />
<Route path=":albumId/*" element={<AlbumDetailPage />} />
<Route path="*" element={<Error404 />} />
</Routes>
);

View File

@ -1,17 +1,6 @@
import { useState } from 'react';
import React from 'react';
import {
Input,
InputGroup,
InputLeftElement,
InputRightElement,
Text,
Wrap,
WrapItem,
useOutsideClick,
} from '@chakra-ui/react';
import { MdSearch } from 'react-icons/md';
import { Wrap, WrapItem } from '@chakra-ui/react';
import { useNavigate } from 'react-router-dom';
import { EmptyEnd } from '@/components/EmptyEnd';
@ -20,7 +9,7 @@ import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
import { SearchInput } from '@/components/SearchInput';
import { TopBar } from '@/components/TopBar/TopBar';
import { DisplayAlbum } from '@/components/album/DisplayAlbum';
import { useAlbumService, useOrderedAlbums } from '@/service/Album';
import { useOrderedAlbums } from '@/service/Album';
import { useThemeMode } from '@/utils/theme-tools';
export const AlbumsPage = () => {

View File

@ -1,12 +1,13 @@
import { Box, Flex, Text } from '@chakra-ui/react';
import { Box, Button, Flex, Text } from '@chakra-ui/react';
import { LuDisc3, LuUser } from 'react-icons/lu';
import { MdPerson } 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 { TrackEditPopUp } from '@/components/popup/TrackEditPopUp';
import { DisplayTrack } from '@/components/track/DisplayTrack';
import { useActivePlaylistService } from '@/service/ActivePlaylist';
@ -54,34 +55,26 @@ export const ArtistAlbumDetailPage = () => {
}
return (
<>
<TopBar title="Album detail" />
<PageLayout>
<Flex
direction="row"
width="80%"
marginX="auto"
padding="10px"
gap="10px"
>
<Covers
data={dataArtist?.covers}
iconEmpty={<LuUser size="100" height="full" />}
/>
<Flex direction="column" width="80%" marginRight="auto">
<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>
{dataArtist?.description && (
<Text>Description: {dataArtist?.description}</Text>
)}
{dataArtist?.firstName && (
<Text>first name: {dataArtist?.firstName}</Text>
)}
{dataArtist?.surname && <Text>surname: {dataArtist?.surname}</Text>}
{dataArtist?.birth && <Text>birth: {dataArtist?.birth}</Text>}
</Flex>
</Flex>
</Button>
)}
</TopBar>
<PageLayout>
<Flex
direction="row"
width="80%"

View File

@ -1,12 +1,13 @@
import { Flex, Text, Wrap, WrapItem } from '@chakra-ui/react';
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 { 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 { DisplayAlbumId } from '@/components/album/DisplayAlbumId';
import { useSpecificArtist } from '@/service/Artist';
import { useAlbumIdsOfAnArtist } from '@/service/Track';
@ -35,7 +36,18 @@ export const ArtistDetailPage = () => {
}
return (
<>
<TopBar title="Artist detail" />
<TopBar>
<Button
{...BUTTON_TOP_BAR_PROPERTY}
marginRight="auto"
onClick={() => navigate(`/artist/all`)}
>
<MdGroup height="full" />
<Text fontSize="24px" fontWeight="bold">
Artists
</Text>
</Button>
</TopBar>
<PageLayout>
<Flex
direction="row"

View File

@ -1,13 +1,15 @@
import { Box, Flex, Text } from '@chakra-ui/react';
import { LuDisc3 } from 'react-icons/lu';
import { useParams } from 'react-router-dom';
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 { TrackEditPopUp } from '@/components/popup/TrackEditPopUp';
import { DisplayTrack } from '@/components/track/DisplayTrack';
import { DisplayTrackFull } from '@/components/track/DisplayTrackFull';
import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { useSpecificGender } from '@/service/Gender';
import { useTracksOfAGender } from '@/service/Track';
@ -21,6 +23,7 @@ export const GenderDetailPage = () => {
const { playInList } = useActivePlaylistService();
const { dataGender } = useSpecificGender(genderIdInt);
const { tracksOnAGender } = useTracksOfAGender(genderIdInt);
const navigate = useNavigate();
const onSelectItem = (trackId: number) => {
//navigate(`/artist/${artistIdInt}/gender/${genderId}`);
let currentPlay = 0;
@ -84,7 +87,7 @@ export const GenderDetailPage = () => {
{tracksOnAGender?.map((data) => (
<Box
minWidth="100%"
height="60px"
//height="60px"
border="1px"
borderColor="brand.900"
backgroundColor={mode('#FFFFFF88', '#00000088')}
@ -96,14 +99,26 @@ export const GenderDetailPage = () => {
bgColor: mode('#FFFFFFF7', '#000000F7'),
}}
>
<DisplayTrack
<DisplayTrackFull
track={data}
onClick={() => onSelectItem(data.id)}
contextMenu={[
{
name: 'Edit',
onClick: () => {
navigate(`/gender/${genderId}/edit/${data.id}`);
},
},
{ name: 'Add Playlist', onClick: () => {} },
]}
/>
</Box>
))}
<EmptyEnd />
</Flex>
<Routes>
<Route path="edit/:trackId" element={<TrackEditPopUp />} />
</Routes>
</PageLayout>
</>
);

View File

@ -9,7 +9,7 @@ export const GenderRoutes = () => {
<Routes>
<Route path="/" element={<Navigate to="all" replace />} />
<Route path="all" element={<GendersPage />} />
<Route path=":genderId" element={<GenderDetailPage />} />
<Route path=":genderId/*" element={<GenderDetailPage />} />
<Route path="*" element={<Error404 />} />
</Routes>
);

View File

@ -2,6 +2,7 @@ import { ReactElement } from 'react';
import { Center, Flex, Text, Wrap, WrapItem } from '@chakra-ui/react';
import { LuCrown, LuDisc3, LuEar, LuFileAudio, LuUser } from 'react-icons/lu';
import { MdGroup } from 'react-icons/md';
import { useNavigate } from 'react-router-dom';
import { PageLayout } from '@/components/Layout/PageLayout';
@ -24,7 +25,7 @@ const homeList: HomeListType[] = [
{
id: 2,
name: 'Artists',
icon: <LuUser size="60%" height="full" />,
icon: <MdGroup size="60%" height="full" />,
to: 'artist',
},
{