Skip to content

Commit

Permalink
Update Edit Series Modal (#707)
Browse files Browse the repository at this point in the history
* Update Edit Series Modal

* update change request

* update missing todo message

* update code to pass suffixes as a component no a render function

* Update missing code

---------

Co-authored-by: Harshith Mohan <[email protected]>
  • Loading branch information
duehoa1211 and harshithmohan authored Dec 7, 2023
1 parent 123dcb9 commit 073b6f8
Show file tree
Hide file tree
Showing 9 changed files with 451 additions and 42 deletions.
8 changes: 8 additions & 0 deletions src/components/Collection/Series/EditSeriesModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import NameTab from '@/components/Collection/Series/EditSeriesTabs/NameTab';
import SeriesActionsTab from '@/components/Collection/Series/EditSeriesTabs/SeriesActionsTab';
import ModalPanel from '@/components/Panels/ModalPanel';

// TODO: Add tabs after implementing back-end endpoint for GroupTab and PersonalStats
// import GroupTab from './EditSeriesTabs/GroupTab';
// import PersonalStats from './EditSeriesTabs/PersonalStats';

type Props = {
show: boolean;
onClose: () => void;
Expand All @@ -23,6 +27,10 @@ const renderTab = (activeTab: string, seriesId: number) => {
switch (activeTab) {
case 'actions':
return <SeriesActionsTab seriesId={seriesId} />;
// case 'group':
// return <GroupTab seriesId={seriesId} />;
// case 'stats':
// return <PersonalStats />;
case 'name':
default:
return <NameTab seriesId={seriesId} />;
Expand Down
121 changes: 121 additions & 0 deletions src/components/Collection/Series/EditSeriesTabs/GroupTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { mdiCheckUnderlineCircleOutline, mdiCloseCircleOutline, mdiMagnify, mdiPencilCircleOutline } from '@mdi/js';
import cx from 'classnames';
import { debounce } from 'lodash';

import Input from '@/components/Input/Input';
import { useLazyGetGroupInfiniteQuery } from '@/core/rtkQuery/splitV3Api/collectionApi';
import { useGetSeriesGroupQuery } from '@/core/rtkQuery/splitV3Api/seriesApi';

import type { CollectionGroupType } from '@/core/types/api/collection';

type Props = {
seriesId: number;
};

function GroupTab({ seriesId }: Props) {
const [name, setName] = useState('');
const [search, setSearch] = useState('');
const [nameEditable, setNameEditable] = useState(false);

const groupQuery = useGetSeriesGroupQuery({ seriesId: seriesId.toString(), topLevel: false }, {
refetchOnMountOrArgChange: false,
});

const [fetchGroup, groupResults] = useLazyGetGroupInfiniteQuery();

const getAniDbGroup = useMemo((): CollectionGroupType[] => {
const pages = groupResults.data?.pages;
if (!pages) return [];

const keys = Object.keys(pages);
if (!keys?.length) return [];

return pages[1];
}, [groupResults]);

const searchGroup = useMemo(() =>
debounce(async () => {
await fetchGroup({
startsWith: search,
pageSize: 5,
});
}, 250), [search, fetchGroup]);

useEffect(() => {
if (!search) return;
searchGroup()?.then()?.catch(console.error);
}, [search, searchGroup]);

useEffect(() => {
const { data } = groupQuery;
setName(data?.Name ?? '');
}, [groupQuery]);

const renderTitle = useCallback((group: CollectionGroupType) => (
<div
className="flex cursor-pointer justify-between"
key={group.IDs.MainSeries}
onClick={() => setName(group.Name)}
>
<div>{group.Name}</div>
{group.IDs.MainSeries}
</div>
), []);

const nameInputIcons = useCallback(() => {
if (!nameEditable) {
return [{
icon: mdiPencilCircleOutline,
className: 'text-panel-text-primary',
onClick: () => setNameEditable(_ => true),
}];
}

return [{
icon: mdiCloseCircleOutline,
className: 'text-red-500',
onClick: () => setName(_ => ''),
}, {
icon: mdiCheckUnderlineCircleOutline,
className: 'text-panel-text-primary',
onClick: () => {}, // TODO: Need endpoint to update series
}];
}, [nameEditable]);

return (
<div className="flex flex-col">
<Input
id="name"
type="text"
onChange={e => setName(e.target.value)}
value={name}
label="Name"
className="mb-4"
endIcons={nameInputIcons()}
disabled={!nameEditable}
/>
<Input
id="search"
type="text"
value={search}
onChange={e => setSearch(e.target.value)}
startIcon={mdiMagnify}
placeholder="Name Search..."
disabled={!nameEditable}
className={cx(!nameEditable && 'invisible')}
/>
<div
className={cx(
'mt-1 flex flex-col gap-y-2.5 rounded-md border border-panel-border bg-panel-background-alt p-4',
!nameEditable && 'invisible',
)}
>
{!search && groupQuery.isSuccess && renderTitle(groupQuery.data)}
{search && groupResults.isSuccess && getAniDbGroup.map(renderTitle)}
</div>
</div>
);
}

export default GroupTab;
101 changes: 83 additions & 18 deletions src/components/Collection/Series/EditSeriesTabs/NameTab.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import React, { useEffect, useState } from 'react';
import { mdiMagnify, mdiPencilCircleOutline } from '@mdi/js';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { mdiCheckUnderlineCircleOutline, mdiCloseCircleOutline, mdiMagnify, mdiPencilCircleOutline } from '@mdi/js';
import cx from 'classnames';
import { debounce } from 'lodash';

import Input from '@/components/Input/Input';
import { useGetSeriesQuery } from '@/core/rtkQuery/splitV3Api/seriesApi';
import { useGetSeriesQuery, useLazyGetSeriesInfiniteQuery } from '@/core/rtkQuery/splitV3Api/seriesApi';

import type { SeriesTitleType } from '@/core/types/api/series';

type Props = {
seriesId: number;
Expand All @@ -17,10 +21,65 @@ const NameTab = ({ seriesId }: Props) => {
refetchOnMountOrArgChange: false,
});

const [fetchSeries, seriesResults] = useLazyGetSeriesInfiniteQuery();
const getAniDbSeries = useMemo((): SeriesTitleType[] => {
const pages = seriesResults.data?.pages;
if (!pages) return [];

const keys = Object.keys(pages);
if (!keys?.length) return [];

return pages[1];
}, [seriesResults]);

const searchSeries = useMemo(() =>
debounce(async () => {
await fetchSeries({
startsWith: search,
pageSize: 5,
});
}, 250), [search, fetchSeries]);

useEffect(() => {
setName(seriesQuery.data?.Name ?? '');
}, [seriesQuery]);

useEffect(() => {
if (!search) return;
searchSeries()?.then()?.catch(console.error);
}, [search, searchSeries]);

const renderTitle = useCallback((title: SeriesTitleType) => (
<div
className="flex cursor-pointer justify-between"
key={title.Language}
onClick={() => setName(title.Name)}
>
<div>{title.Name}</div>
{title.Language}
</div>
), []);

const nameInputIcons = useCallback(() => {
if (!nameEditable) {
return [{
icon: mdiPencilCircleOutline,
className: 'text-panel-text-primary',
onClick: () => setNameEditable(_ => true),
}];
}

return [{
icon: mdiCloseCircleOutline,
className: 'text-red-500',
onClick: () => setName(_ => ''),
}, {
icon: mdiCheckUnderlineCircleOutline,
className: 'text-panel-text-primary',
onClick: () => {}, // TODO: Need endpoint to update series
}];
}, [nameEditable]);

return (
<div className="flex flex-col">
<Input
Expand All @@ -30,8 +89,7 @@ const NameTab = ({ seriesId }: Props) => {
value={name}
label="Name"
className="mb-4"
endIcon={mdiPencilCircleOutline}
endIconClick={() => setNameEditable(true)}
endIcons={nameInputIcons()}
disabled={!nameEditable}
/>
<Input
Expand All @@ -41,20 +99,27 @@ const NameTab = ({ seriesId }: Props) => {
onChange={e => setSearch(e.target.value)}
startIcon={mdiMagnify}
placeholder="Name Search..."
disabled
// TODO: Implement
disabled={!nameEditable}
className={cx(!nameEditable && 'invisible')}
/>
<div className="mt-1 flex flex-col gap-y-2.5 rounded-md border border-panel-border bg-panel-background-alt p-4">
{seriesQuery.data?.AniDB?.Titles.map(title => (
<div
className="flex cursor-pointer justify-between"
key={title.Language}
onClick={() => setName(title.Name)}
>
<div>{title.Name}</div>
{title.Language}
</div>
))}
<div
className={cx(
'mt-1 flex flex-col gap-y-2.5 rounded-md border border-panel-border bg-panel-background-alt p-4 overflow-hidden',
!nameEditable && 'invisible',
)}
>
{!search && seriesQuery.data?.AniDB?.Titles.reduce((acc, title) => {
if (!search) {
acc.push(renderTitle(title));
return acc;
}
if (title.Name.toLowerCase().includes(search.toLowerCase())) {
acc.push(renderTitle(title));
return acc;
}
return acc;
}, [] as React.ReactNode[])}
{search && getAniDbSeries.map(title => renderTitle(title))}
</div>
</div>
);
Expand Down
Loading

0 comments on commit 073b6f8

Please sign in to comment.