From c79ff4335e880c2a606a2e113546f8b772f0465d Mon Sep 17 00:00:00 2001 From: gs0428 Date: Wed, 14 Feb 2024 02:31:58 +0900 Subject: [PATCH 01/14] =?UTF-8?q?feature-065:=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=93=9C?= =?UTF-8?q?=EB=A1=AD=EB=8B=A4=EC=9A=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/CategoryPage.tsx | 34 ++++++++++------ src/styles/category/index.style.ts | 65 ++++++++++++++++++++++-------- 2 files changed, 70 insertions(+), 29 deletions(-) diff --git a/src/pages/CategoryPage.tsx b/src/pages/CategoryPage.tsx index bbbfcc9..49fb689 100644 --- a/src/pages/CategoryPage.tsx +++ b/src/pages/CategoryPage.tsx @@ -4,7 +4,6 @@ import { useParams } from 'react-router-dom'; import ChangeBottomSvg from '@/assets/icons/change-bottom.svg?react'; import ChangeTopSvg from '@/assets/icons/change-top.svg?react'; import GarbageSvg from '@/assets/icons/garbage.svg?react'; -import FolderSvg from '@/assets/icons/open-file.svg?react'; import CloseSvg from '@/assets/icons/close.svg?react'; import * as CategoryPageStyles from '@/styles/category/index.style'; import Card from '@/components/category/Card'; @@ -16,6 +15,7 @@ import { deleteVideos, getRecentVideos, getVideoById } from '@/apis/videos'; import { IVideoProps } from 'types/videos'; import { sortVideos } from '@/utils/sortVideos'; import { CardContainer } from '@/styles/category/Card.style'; +import { CategorySelectBox } from '@/components/SummaryPage/SummaryDetailBox/CategorySelectBox'; const CategoryPage = () => { const params = useParams(); @@ -26,6 +26,14 @@ const CategoryPage = () => { const [checkedVideos, setCheckedVideos] = useState([]); const categories = useRecoilValue(categoryState); + const [selectedCategoryId, setSelectedCategoryId] = useState( + categories.length ? categories[0].categoryId : -1, + ); + + const handleSelectCategory = (categoryId: number) => { + setSelectedCategoryId(categoryId); + }; + const toggleRecentRegisterMode = () => setRecentRegisterMode(!recentRegisterMode); @@ -73,8 +81,9 @@ const CategoryPage = () => { } }; - const dirMoveHanlder = () => { - console.log(checkedVideos); + const onFileClick = (e: React.MouseEvent) => { + e.stopPropagation(); + // 비디오 이동 API 호출 후 모든 비디오 받아오는 API 재호출로 최신화하기 }; return ( @@ -82,7 +91,7 @@ const CategoryPage = () => { {checkedVideos.length > 0 ? ( - <> +
{checkedVideos.length === sortedVideos.length @@ -94,14 +103,13 @@ const CategoryPage = () => {
- - {menus.map((menu) => ( - - ))} - - - - + + + @@ -117,7 +125,7 @@ const CategoryPage = () => { /> - +
) : ( <>
diff --git a/src/styles/category/index.style.ts b/src/styles/category/index.style.ts index f03b5b4..1b05659 100644 --- a/src/styles/category/index.style.ts +++ b/src/styles/category/index.style.ts @@ -2,6 +2,17 @@ import styled from 'styled-components'; import theme from '../theme'; import { Link } from 'react-router-dom'; +const CommonIconBackground = styled.div` + cursor: pointer; + width: 40px; + height: 40px; + border-radius: 8px; + + display: flex; + align-items: center; + justify-content: center; +`; + export const Container = styled.div` padding: 60px 60px 0px 120px; width: 100%; @@ -40,6 +51,13 @@ export const Mode = styled.span` color: ${theme.color.gray400}; `; +export const SelectModeWrap = styled.div` + display: flex; + justify-content: space-between; + width: 100%; + align-items: center; +`; + export const CardManagement = styled.div` display: flex; flex-direction: row; @@ -55,29 +73,16 @@ export const SelectManagement = styled.select` color: ${theme.color.gray400}; `; -export const ManagementBoxGray = styled.div` - width: 36px; - height: 34px; - border-radius: 8px; - - display: flex; - align-items: center; - justify-content: center; +export const ManagementBoxGray = styled(CommonIconBackground)` background: ${theme.color.gray100}; `; -export const ManagementBox = styled.div` - width: 36px; - height: 34px; - border-radius: 8px; - - display: flex; - align-items: center; - justify-content: center; +export const ManagementBox = styled(CommonIconBackground)` background: ${theme.color.white}; `; export const AllSelectBtn = styled.button` + cursor: pointer; width: 90px; height: 28px; background: ${theme.color.gray500}; @@ -94,3 +99,31 @@ export const SelectedCount = styled.span` padding: 0px 10px; color: ${theme.color.gray400}; `; + +export const DropdownWrap = styled.div` + margin: 0; + display: flex; + flex-direction: column; + z-index: 10; + & div.select-box { + padding: 8px 16px; + width: 202px; + display: flex; + align-items: center; + justify-content: space-between; + height: 40px; + border-radius: 8px; + border: solid 1px ${theme.color.gray200}; + color: ${theme.color.gray400}; + ${theme.typography.Body3}; + cursor: pointer; + } + & span.icon-button { + padding: 5px 6px; + width: 40px; + height: 40px; + border-radius: 8px; + cursor: pointer; + background-color: ${theme.color.gray100}; + } +`; From cee49b39f6cb62d9ca8894cee870890383344424 Mon Sep 17 00:00:00 2001 From: gs0428 Date: Wed, 14 Feb 2024 03:04:24 +0900 Subject: [PATCH 02/14] =?UTF-8?q?feature-065:=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EC=9D=B4=EB=8F=99=20=EC=8B=9C=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=EB=90=9C=20=EB=B9=84=EB=94=94=EC=98=A4=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/CategoryPage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/CategoryPage.tsx b/src/pages/CategoryPage.tsx index 49fb689..980c9e5 100644 --- a/src/pages/CategoryPage.tsx +++ b/src/pages/CategoryPage.tsx @@ -57,6 +57,7 @@ const CategoryPage = () => { setMenus(categories[index].subFolders); }); } + setCheckedVideos([]); }, [categories, params.top_folder]); const handleDeleteVideos = async () => { From 7bc75cf48b48dc6116dbcc2c99db229a0837cac3 Mon Sep 17 00:00:00 2001 From: gs0428 Date: Wed, 14 Feb 2024 14:41:50 +0900 Subject: [PATCH 03/14] =?UTF-8?q?feature-065:=20=ED=95=98=EC=9C=84=20?= =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=EB=A1=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=ED=96=88=EC=9D=84=20=EB=95=8C=20=EB=B9=84=EB=94=94?= =?UTF-8?q?=EC=98=A4=20=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8A=94=20API=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/videos.ts | 2 +- src/pages/CategoryPage.tsx | 28 +++++++++++++++++++++------- src/styles/category/index.style.ts | 2 +- src/utils/sortVideos.ts | 4 ++-- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/apis/videos.ts b/src/apis/videos.ts index e6e81d7..06bf8b4 100644 --- a/src/apis/videos.ts +++ b/src/apis/videos.ts @@ -31,7 +31,7 @@ export const getRecentVideos = async (): Promise< export const getVideoById = async ( videoId: number, ): Promise>> => { - const response = await axiosInstance.get(`/videos/${videoId}`); + const response = await axiosInstance.get(`/videos/${videoId}/get`); return response.data; }; diff --git a/src/pages/CategoryPage.tsx b/src/pages/CategoryPage.tsx index 980c9e5..1f3615c 100644 --- a/src/pages/CategoryPage.tsx +++ b/src/pages/CategoryPage.tsx @@ -47,18 +47,36 @@ const CategoryPage = () => { setName('최근 읽은 영상'); }) .catch((err) => console.log(err)); - } else { + } else if (!params.sub_folder) { getVideoById(Number(params.top_folder)).then((res) => { const index = categories.findIndex( (category) => category.categoryId === Number(params.top_folder), ); - setVideos(res.result.videos); + if (!res.isSuccess) { + setName(categories[index].name); + setMenus([]); + setVideos([]); + return; + } setName(categories[index].name); setMenus(categories[index].subFolders); + setVideos(res.isSuccess ? res.result.videos : []); + }); + } else { + getVideoById(Number(params.sub_folder)).then((res) => { + const index = categories.findIndex( + (category) => category.categoryId === Number(params.top_folder), + ); + const subIndex = categories[index].subFolders.findIndex( + (subFolder) => subFolder.categoryId === Number(params.sub_folder), + ); + setName(categories[index].subFolders[subIndex].name); + setMenus([]); + setVideos(res.isSuccess ? res.result.videos : []); }); } setCheckedVideos([]); - }, [categories, params.top_folder]); + }, [categories, params.sub_folder, params.top_folder]); const handleDeleteVideos = async () => { const res = await deleteVideos(checkedVideos); @@ -133,10 +151,6 @@ const CategoryPage = () => { {menus.map((menu) => ( {menu.name} diff --git a/src/styles/category/index.style.ts b/src/styles/category/index.style.ts index 1b05659..faef665 100644 --- a/src/styles/category/index.style.ts +++ b/src/styles/category/index.style.ts @@ -31,7 +31,7 @@ export const Menu = styled(Link)` color: ${theme.color.gray300}; margin-right: 20px; - &.activated { + &:hover { color: ${theme.color.gray500}; ${theme.typography.Subheader1}; } diff --git a/src/utils/sortVideos.ts b/src/utils/sortVideos.ts index c711277..62af05b 100644 --- a/src/utils/sortVideos.ts +++ b/src/utils/sortVideos.ts @@ -9,13 +9,13 @@ export const sortVideos = ( prevVideo[isRecentRegisterMode ? 'created_at' : 'youtube_created_at'] > nextVideo[isRecentRegisterMode ? 'created_at' : 'youtube_created_at'] ) - return 1; + return -1; if ( prevVideo[isRecentRegisterMode ? 'created_at' : 'youtube_created_at'] === nextVideo[isRecentRegisterMode ? 'created_at' : 'youtube_created_at'] ) return 0; - return -1; + return 1; }); return sortedVideos; }; From c5728592a6a5c5fd98a2696b396d4ce65ee54ada Mon Sep 17 00:00:00 2001 From: gs0428 Date: Wed, 14 Feb 2024 16:19:41 +0900 Subject: [PATCH 04/14] =?UTF-8?q?feature-065:=20=ED=95=98=EC=9C=84=20?= =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=ED=83=9C=EA=B7=B8=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8A=94=20API=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/category.ts | 6 +++ src/apis/videos.ts | 2 +- src/components/category/Card.tsx | 3 +- src/components/common/chip/Chip.style.ts | 23 +++++++++ src/components/common/chip/Chip.tsx | 19 ++++++++ src/pages/CategoryPage.tsx | 60 ++++++++++++++++-------- src/styles/category/Card.style.ts | 10 ---- types/category.ts | 5 ++ 8 files changed, 97 insertions(+), 31 deletions(-) create mode 100644 src/components/common/chip/Chip.style.ts create mode 100644 src/components/common/chip/Chip.tsx diff --git a/src/apis/category.ts b/src/apis/category.ts index be348b5..62479aa 100644 --- a/src/apis/category.ts +++ b/src/apis/category.ts @@ -8,6 +8,12 @@ export const getCategories = async () => { return response.data; }; +// 카테고리 별 태그 가져오는 API +export const getCategoryTags = async (categoryId: string) => { + const response = await axiosInstance.get(`/category/${categoryId}/`); + return response.data; +}; + // 카테고리 이동1 API export const putSubToOtherTop = async ( categoryId: number, diff --git a/src/apis/videos.ts b/src/apis/videos.ts index 06bf8b4..83393b9 100644 --- a/src/apis/videos.ts +++ b/src/apis/videos.ts @@ -29,7 +29,7 @@ export const getRecentVideos = async (): Promise< }; export const getVideoById = async ( - videoId: number, + videoId: string, ): Promise>> => { const response = await axiosInstance.get(`/videos/${videoId}/get`); return response.data; diff --git a/src/components/category/Card.tsx b/src/components/category/Card.tsx index f4302f0..4b38c5a 100644 --- a/src/components/category/Card.tsx +++ b/src/components/category/Card.tsx @@ -7,6 +7,7 @@ import { CategorySelectBox } from '@/components/SummaryPage/SummaryDetailBox/Cat import { categoryState } from '@/stores/category'; import * as CardStyles from '@/styles/category/Card.style'; +import Chip from '../common/chip/Chip'; interface ICardProps { mode: 'default' | 'category' | 'recommend'; @@ -65,7 +66,7 @@ const Card: React.FC = ({ {video.description} {video.tag.map((tag) => ( - {`# ${tag.name}`} + ))} diff --git a/src/components/common/chip/Chip.style.ts b/src/components/common/chip/Chip.style.ts new file mode 100644 index 0000000..891e912 --- /dev/null +++ b/src/components/common/chip/Chip.style.ts @@ -0,0 +1,23 @@ +import theme from '@/styles/theme'; +import styled from 'styled-components'; + +export const ChipContainer = styled.div` + cursor: pointer; + margin-right: 18px; + margin-bottom: 18px; + padding: 3px 9.5px; + background-color: ${theme.color.gray100}; + border-radius: 8px; + color: ${theme.color.gray400}; + ${theme.typography.Caption1}; + + &.light { + border: 1px solid ${theme.color.gray200}; + background-color: ${theme.color.white}; + } + + &.selected { + border-color: ${theme.color.gray300}; + background-color: ${theme.color.gray100}; + } +`; diff --git a/src/components/common/chip/Chip.tsx b/src/components/common/chip/Chip.tsx new file mode 100644 index 0000000..02a224c --- /dev/null +++ b/src/components/common/chip/Chip.tsx @@ -0,0 +1,19 @@ +import { ChipContainer } from './Chip.style'; + +interface IChipProps { + name: string; + light?: boolean; + selected?: boolean; + onSelectTag?: (name: string) => void; +} + +const Chip = ({ name, light, selected, onSelectTag }: IChipProps) => { + return ( + onSelectTag && onSelectTag(name)} + >{`# ${name}`} + ); +}; + +export default Chip; diff --git a/src/pages/CategoryPage.tsx b/src/pages/CategoryPage.tsx index 1f3615c..221a35f 100644 --- a/src/pages/CategoryPage.tsx +++ b/src/pages/CategoryPage.tsx @@ -9,27 +9,38 @@ import * as CategoryPageStyles from '@/styles/category/index.style'; import Card from '@/components/category/Card'; import { useRecoilValue } from 'recoil'; import { categoryState } from '@/stores/category'; -import { ISubFolderProps } from 'types/category'; +import { ISubFolderProps, ITagProps } from 'types/category'; import EmptyCard from '@/components/category/EmptyCard'; import { deleteVideos, getRecentVideos, getVideoById } from '@/apis/videos'; import { IVideoProps } from 'types/videos'; import { sortVideos } from '@/utils/sortVideos'; import { CardContainer } from '@/styles/category/Card.style'; import { CategorySelectBox } from '@/components/SummaryPage/SummaryDetailBox/CategorySelectBox'; +import Chip from '@/components/common/chip/Chip'; +import { getCategoryTags } from '@/apis/category'; const CategoryPage = () => { const params = useParams(); const [name, setName] = useState(''); - const [menus, setMenus] = useState([]); + const [menus, setMenus] = useState([]); const [videos, setVideos] = useState([]); const [recentRegisterMode, setRecentRegisterMode] = useState(false); const [checkedVideos, setCheckedVideos] = useState([]); + const [selectedTags, setSelectedTags] = useState([]); const categories = useRecoilValue(categoryState); const [selectedCategoryId, setSelectedCategoryId] = useState( categories.length ? categories[0].categoryId : -1, ); + const onSelectTag = (name: string) => { + if (selectedTags.includes(name)) { + setSelectedTags(selectedTags.filter((tag) => tag !== name)); + } else { + setSelectedTags([...selectedTags, name]); + } + }; + const handleSelectCategory = (categoryId: number) => { setSelectedCategoryId(categoryId); }; @@ -45,33 +56,31 @@ const CategoryPage = () => { .then((res) => { setVideos(res.result.videos); setName('최근 읽은 영상'); + setMenus([]); }) .catch((err) => console.log(err)); } else if (!params.sub_folder) { - getVideoById(Number(params.top_folder)).then((res) => { + getVideoById(params.top_folder).then((res) => { const index = categories.findIndex( (category) => category.categoryId === Number(params.top_folder), ); - if (!res.isSuccess) { - setName(categories[index].name); - setMenus([]); - setVideos([]); - return; - } setName(categories[index].name); setMenus(categories[index].subFolders); setVideos(res.isSuccess ? res.result.videos : []); }); } else { - getVideoById(Number(params.sub_folder)).then((res) => { + getVideoById(params.sub_folder).then((res) => { const index = categories.findIndex( (category) => category.categoryId === Number(params.top_folder), ); const subIndex = categories[index].subFolders.findIndex( (subFolder) => subFolder.categoryId === Number(params.sub_folder), ); + getCategoryTags(params.sub_folder!).then((res) => + setMenus(res.result.tags), + ); setName(categories[index].subFolders[subIndex].name); - setMenus([]); + setVideos(res.isSuccess ? res.result.videos : []); }); } @@ -147,14 +156,27 @@ const CategoryPage = () => { ) : ( <> -
- {menus.map((menu) => ( - - {menu.name} - +
+ {menus.map((menu: ISubFolderProps | ITagProps) => ( + <> + {'tag_id' in menu && ( + + )} + {!('tag_id' in menu) && ( + + {menu.name} + + )} + ))}
diff --git a/src/styles/category/Card.style.ts b/src/styles/category/Card.style.ts index 33760df..32a6263 100644 --- a/src/styles/category/Card.style.ts +++ b/src/styles/category/Card.style.ts @@ -143,13 +143,3 @@ export const DropdownWrap = styled.div` } } `; - -export const Chip = styled.div` - margin-right: 18px; - margin-bottom: 18px; - padding: 3px 9.5px; - background-color: ${theme.color.gray100}; - border-radius: 8px; - color: ${theme.color.gray400}; - ${theme.typography.Caption1}; -`; diff --git a/types/category.ts b/types/category.ts index 760296a..ac6a12b 100644 --- a/types/category.ts +++ b/types/category.ts @@ -15,3 +15,8 @@ export interface ISelectedCategoryProps { name: string; categoryId: number; } + +export interface ITagProps { + tag_id: number; + name: string; +} From 572926ceff96e66c68ff036c3ef0e03e4905b3de Mon Sep 17 00:00:00 2001 From: gs0428 Date: Wed, 14 Feb 2024 16:22:08 +0900 Subject: [PATCH 05/14] =?UTF-8?q?feature-065:=20Home=20Empty=20item=20styl?= =?UTF-8?q?e=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Home/RecentVideos.tsx | 4 ++-- src/styles/HomepageStyle.ts | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/Home/RecentVideos.tsx b/src/components/Home/RecentVideos.tsx index aeeea48..7909941 100644 --- a/src/components/Home/RecentVideos.tsx +++ b/src/components/Home/RecentVideos.tsx @@ -20,7 +20,7 @@ const RecentVideos = ({ videos }: IRecentVideosProp) => { 최근 읽은 영상 {videos.length === 0 && ( - <> +
비어있는 비디오 이미지
@@ -30,7 +30,7 @@ const RecentVideos = ({ videos }: IRecentVideosProp) => {

영상 정리해보기

- +
)} {videos.length > 0 && ( diff --git a/src/styles/HomepageStyle.ts b/src/styles/HomepageStyle.ts index 5573b4a..e9a7bca 100644 --- a/src/styles/HomepageStyle.ts +++ b/src/styles/HomepageStyle.ts @@ -142,6 +142,13 @@ export const RecentVideosContainer = styled.div` width: 910px; } + .empty-container { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + } + .empty-video img { width: 155.56px; height: 155.56px; From 3acf9cba12b9c5a601e21953cf04bc5feeec2f18 Mon Sep 17 00:00:00 2001 From: gs0428 Date: Thu, 15 Feb 2024 01:37:27 +0900 Subject: [PATCH 06/14] =?UTF-8?q?feature-065:=20=ED=95=98=EC=9C=84=20?= =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=ED=83=9C=EA=B7=B8=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EB=B9=84?= =?UTF-8?q?=EB=94=94=EC=98=A4=20=EB=B3=B4=EC=9D=B4=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useMoveCategory.ts | 1 - src/pages/CategoryPage.tsx | 67 ++++++++++++++---------------- src/styles/category/index.style.ts | 1 + src/utils/handleVideo.ts | 36 ++++++++++++++++ 4 files changed, 69 insertions(+), 36 deletions(-) create mode 100644 src/utils/handleVideo.ts diff --git a/src/hooks/useMoveCategory.ts b/src/hooks/useMoveCategory.ts index 3be900b..e1d61f2 100644 --- a/src/hooks/useMoveCategory.ts +++ b/src/hooks/useMoveCategory.ts @@ -25,7 +25,6 @@ const useMoveCategory = () => { grabedCategory.current!.categoryId, topId, ); - console.log(res); if (res.isSuccess) { await updateCategories(); navigate(`/category/${grabedCategory.current?.topCategoryId}`); diff --git a/src/pages/CategoryPage.tsx b/src/pages/CategoryPage.tsx index 221a35f..d115d1f 100644 --- a/src/pages/CategoryPage.tsx +++ b/src/pages/CategoryPage.tsx @@ -11,13 +11,13 @@ import { useRecoilValue } from 'recoil'; import { categoryState } from '@/stores/category'; import { ISubFolderProps, ITagProps } from 'types/category'; import EmptyCard from '@/components/category/EmptyCard'; -import { deleteVideos, getRecentVideos, getVideoById } from '@/apis/videos'; +import { deleteVideos, getRecentVideos } from '@/apis/videos'; import { IVideoProps } from 'types/videos'; import { sortVideos } from '@/utils/sortVideos'; import { CardContainer } from '@/styles/category/Card.style'; import { CategorySelectBox } from '@/components/SummaryPage/SummaryDetailBox/CategorySelectBox'; import Chip from '@/components/common/chip/Chip'; -import { getCategoryTags } from '@/apis/category'; +import handleVideo from '@/utils/handleVideo'; const CategoryPage = () => { const params = useParams(); @@ -59,30 +59,15 @@ const CategoryPage = () => { setMenus([]); }) .catch((err) => console.log(err)); - } else if (!params.sub_folder) { - getVideoById(params.top_folder).then((res) => { - const index = categories.findIndex( - (category) => category.categoryId === Number(params.top_folder), - ); - setName(categories[index].name); - setMenus(categories[index].subFolders); - setVideos(res.isSuccess ? res.result.videos : []); - }); } else { - getVideoById(params.sub_folder).then((res) => { - const index = categories.findIndex( - (category) => category.categoryId === Number(params.top_folder), - ); - const subIndex = categories[index].subFolders.findIndex( - (subFolder) => subFolder.categoryId === Number(params.sub_folder), - ); - getCategoryTags(params.sub_folder!).then((res) => - setMenus(res.result.tags), - ); - setName(categories[index].subFolders[subIndex].name); - - setVideos(res.isSuccess ? res.result.videos : []); - }); + handleVideo( + categories, + params.top_folder, + params.sub_folder!, + setMenus, + setName, + setVideos, + ); } setCheckedVideos([]); }, [categories, params.sub_folder, params.top_folder]); @@ -104,7 +89,6 @@ const CategoryPage = () => { if (checkedVideos.length === videos.length) { handleDeleteVideos(); } else { - console.log('모두 선택'); setCheckedVideos(videos.map((video) => video.video_id)); } }; @@ -198,15 +182,28 @@ const CategoryPage = () => { )} {sortedVideos.length > 0 && ( - {sortedVideos.map((video) => ( - - ))} + {sortedVideos.map((video) => { + const matchedTagCount = video.tag.reduce((acc, cur) => { + if (selectedTags.includes(cur.name)) return (acc += 1); + return acc; + }, 0); + if ( + params.sub_folder && + selectedTags.length && + matchedTagCount !== selectedTags.length + ) + return; + + return ( + + ); + })} )} diff --git a/src/styles/category/index.style.ts b/src/styles/category/index.style.ts index faef665..399d20f 100644 --- a/src/styles/category/index.style.ts +++ b/src/styles/category/index.style.ts @@ -20,6 +20,7 @@ export const Container = styled.div` export const MenuWrap = styled.div` display: flex; + min-width: 'fit-content'; justify-content: space-between; align-items: center; margin-bottom: 40px; diff --git a/src/utils/handleVideo.ts b/src/utils/handleVideo.ts new file mode 100644 index 0000000..ab90820 --- /dev/null +++ b/src/utils/handleVideo.ts @@ -0,0 +1,36 @@ +import { getCategoryTags } from '@/apis/category'; +import { getVideoById } from '@/apis/videos'; +import { IFolderProps, ISubFolderProps, ITagProps } from 'types/category'; +import { IVideoProps } from 'types/videos'; + +const handleVideo = async ( + categories: IFolderProps[], + topCategoryId: string, + subCategoryId: string, + setMenus: React.Dispatch< + React.SetStateAction + >, + setName: React.Dispatch>, + setVideos: React.Dispatch>, +) => { + await getVideoById(topCategoryId).then((res) => { + const topCategory = categories.find( + (category) => category.categoryId === Number(topCategoryId), + ); + if (subCategoryId) { + const subName = topCategory?.subFolders.find( + (subFolder) => subFolder.categoryId === Number(subCategoryId), + ); + setName(subName!.name); + getCategoryTags(subCategoryId!).then((res) => { + if (res.isSuccess) setMenus(res.result.tags); + else setMenus([]); + }); + } else { + setName(topCategory!.name); + setMenus(topCategory!.subFolders); + } + setVideos(res.isSuccess ? res.result.videos : []); + }); +}; +export default handleVideo; From 6524053cc0ac363f0b327740d209fd951fb3ba89 Mon Sep 17 00:00:00 2001 From: gs0428 Date: Wed, 14 Feb 2024 02:31:58 +0900 Subject: [PATCH 07/14] =?UTF-8?q?feature-065:=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=93=9C?= =?UTF-8?q?=EB=A1=AD=EB=8B=A4=EC=9A=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/CategoryPage.tsx | 34 ++++++++++------ src/styles/category/index.style.ts | 65 ++++++++++++++++++++++-------- 2 files changed, 70 insertions(+), 29 deletions(-) diff --git a/src/pages/CategoryPage.tsx b/src/pages/CategoryPage.tsx index bbbfcc9..49fb689 100644 --- a/src/pages/CategoryPage.tsx +++ b/src/pages/CategoryPage.tsx @@ -4,7 +4,6 @@ import { useParams } from 'react-router-dom'; import ChangeBottomSvg from '@/assets/icons/change-bottom.svg?react'; import ChangeTopSvg from '@/assets/icons/change-top.svg?react'; import GarbageSvg from '@/assets/icons/garbage.svg?react'; -import FolderSvg from '@/assets/icons/open-file.svg?react'; import CloseSvg from '@/assets/icons/close.svg?react'; import * as CategoryPageStyles from '@/styles/category/index.style'; import Card from '@/components/category/Card'; @@ -16,6 +15,7 @@ import { deleteVideos, getRecentVideos, getVideoById } from '@/apis/videos'; import { IVideoProps } from 'types/videos'; import { sortVideos } from '@/utils/sortVideos'; import { CardContainer } from '@/styles/category/Card.style'; +import { CategorySelectBox } from '@/components/SummaryPage/SummaryDetailBox/CategorySelectBox'; const CategoryPage = () => { const params = useParams(); @@ -26,6 +26,14 @@ const CategoryPage = () => { const [checkedVideos, setCheckedVideos] = useState([]); const categories = useRecoilValue(categoryState); + const [selectedCategoryId, setSelectedCategoryId] = useState( + categories.length ? categories[0].categoryId : -1, + ); + + const handleSelectCategory = (categoryId: number) => { + setSelectedCategoryId(categoryId); + }; + const toggleRecentRegisterMode = () => setRecentRegisterMode(!recentRegisterMode); @@ -73,8 +81,9 @@ const CategoryPage = () => { } }; - const dirMoveHanlder = () => { - console.log(checkedVideos); + const onFileClick = (e: React.MouseEvent) => { + e.stopPropagation(); + // 비디오 이동 API 호출 후 모든 비디오 받아오는 API 재호출로 최신화하기 }; return ( @@ -82,7 +91,7 @@ const CategoryPage = () => { {checkedVideos.length > 0 ? ( - <> +
{checkedVideos.length === sortedVideos.length @@ -94,14 +103,13 @@ const CategoryPage = () => {
- - {menus.map((menu) => ( - - ))} - - - - + + + @@ -117,7 +125,7 @@ const CategoryPage = () => { /> - +
) : ( <>
diff --git a/src/styles/category/index.style.ts b/src/styles/category/index.style.ts index f03b5b4..1b05659 100644 --- a/src/styles/category/index.style.ts +++ b/src/styles/category/index.style.ts @@ -2,6 +2,17 @@ import styled from 'styled-components'; import theme from '../theme'; import { Link } from 'react-router-dom'; +const CommonIconBackground = styled.div` + cursor: pointer; + width: 40px; + height: 40px; + border-radius: 8px; + + display: flex; + align-items: center; + justify-content: center; +`; + export const Container = styled.div` padding: 60px 60px 0px 120px; width: 100%; @@ -40,6 +51,13 @@ export const Mode = styled.span` color: ${theme.color.gray400}; `; +export const SelectModeWrap = styled.div` + display: flex; + justify-content: space-between; + width: 100%; + align-items: center; +`; + export const CardManagement = styled.div` display: flex; flex-direction: row; @@ -55,29 +73,16 @@ export const SelectManagement = styled.select` color: ${theme.color.gray400}; `; -export const ManagementBoxGray = styled.div` - width: 36px; - height: 34px; - border-radius: 8px; - - display: flex; - align-items: center; - justify-content: center; +export const ManagementBoxGray = styled(CommonIconBackground)` background: ${theme.color.gray100}; `; -export const ManagementBox = styled.div` - width: 36px; - height: 34px; - border-radius: 8px; - - display: flex; - align-items: center; - justify-content: center; +export const ManagementBox = styled(CommonIconBackground)` background: ${theme.color.white}; `; export const AllSelectBtn = styled.button` + cursor: pointer; width: 90px; height: 28px; background: ${theme.color.gray500}; @@ -94,3 +99,31 @@ export const SelectedCount = styled.span` padding: 0px 10px; color: ${theme.color.gray400}; `; + +export const DropdownWrap = styled.div` + margin: 0; + display: flex; + flex-direction: column; + z-index: 10; + & div.select-box { + padding: 8px 16px; + width: 202px; + display: flex; + align-items: center; + justify-content: space-between; + height: 40px; + border-radius: 8px; + border: solid 1px ${theme.color.gray200}; + color: ${theme.color.gray400}; + ${theme.typography.Body3}; + cursor: pointer; + } + & span.icon-button { + padding: 5px 6px; + width: 40px; + height: 40px; + border-radius: 8px; + cursor: pointer; + background-color: ${theme.color.gray100}; + } +`; From c4b2336eabd2811f0465f591aad2f515585c0e31 Mon Sep 17 00:00:00 2001 From: gs0428 Date: Wed, 14 Feb 2024 03:04:24 +0900 Subject: [PATCH 08/14] =?UTF-8?q?feature-065:=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EC=9D=B4=EB=8F=99=20=EC=8B=9C=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=EB=90=9C=20=EB=B9=84=EB=94=94=EC=98=A4=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/CategoryPage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/CategoryPage.tsx b/src/pages/CategoryPage.tsx index 49fb689..980c9e5 100644 --- a/src/pages/CategoryPage.tsx +++ b/src/pages/CategoryPage.tsx @@ -57,6 +57,7 @@ const CategoryPage = () => { setMenus(categories[index].subFolders); }); } + setCheckedVideos([]); }, [categories, params.top_folder]); const handleDeleteVideos = async () => { From 9d841fd2276715a34b1d33ea1753508c2646b99b Mon Sep 17 00:00:00 2001 From: gs0428 Date: Wed, 14 Feb 2024 14:41:50 +0900 Subject: [PATCH 09/14] =?UTF-8?q?feature-065:=20=ED=95=98=EC=9C=84=20?= =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=EB=A1=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=ED=96=88=EC=9D=84=20=EB=95=8C=20=EB=B9=84=EB=94=94?= =?UTF-8?q?=EC=98=A4=20=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8A=94=20API=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/videos.ts | 2 +- src/pages/CategoryPage.tsx | 28 +++++++++++++++++++++------- src/styles/category/index.style.ts | 2 +- src/utils/sortVideos.ts | 4 ++-- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/apis/videos.ts b/src/apis/videos.ts index e6e81d7..06bf8b4 100644 --- a/src/apis/videos.ts +++ b/src/apis/videos.ts @@ -31,7 +31,7 @@ export const getRecentVideos = async (): Promise< export const getVideoById = async ( videoId: number, ): Promise>> => { - const response = await axiosInstance.get(`/videos/${videoId}`); + const response = await axiosInstance.get(`/videos/${videoId}/get`); return response.data; }; diff --git a/src/pages/CategoryPage.tsx b/src/pages/CategoryPage.tsx index 980c9e5..1f3615c 100644 --- a/src/pages/CategoryPage.tsx +++ b/src/pages/CategoryPage.tsx @@ -47,18 +47,36 @@ const CategoryPage = () => { setName('최근 읽은 영상'); }) .catch((err) => console.log(err)); - } else { + } else if (!params.sub_folder) { getVideoById(Number(params.top_folder)).then((res) => { const index = categories.findIndex( (category) => category.categoryId === Number(params.top_folder), ); - setVideos(res.result.videos); + if (!res.isSuccess) { + setName(categories[index].name); + setMenus([]); + setVideos([]); + return; + } setName(categories[index].name); setMenus(categories[index].subFolders); + setVideos(res.isSuccess ? res.result.videos : []); + }); + } else { + getVideoById(Number(params.sub_folder)).then((res) => { + const index = categories.findIndex( + (category) => category.categoryId === Number(params.top_folder), + ); + const subIndex = categories[index].subFolders.findIndex( + (subFolder) => subFolder.categoryId === Number(params.sub_folder), + ); + setName(categories[index].subFolders[subIndex].name); + setMenus([]); + setVideos(res.isSuccess ? res.result.videos : []); }); } setCheckedVideos([]); - }, [categories, params.top_folder]); + }, [categories, params.sub_folder, params.top_folder]); const handleDeleteVideos = async () => { const res = await deleteVideos(checkedVideos); @@ -133,10 +151,6 @@ const CategoryPage = () => { {menus.map((menu) => ( {menu.name} diff --git a/src/styles/category/index.style.ts b/src/styles/category/index.style.ts index 1b05659..faef665 100644 --- a/src/styles/category/index.style.ts +++ b/src/styles/category/index.style.ts @@ -31,7 +31,7 @@ export const Menu = styled(Link)` color: ${theme.color.gray300}; margin-right: 20px; - &.activated { + &:hover { color: ${theme.color.gray500}; ${theme.typography.Subheader1}; } diff --git a/src/utils/sortVideos.ts b/src/utils/sortVideos.ts index c711277..62af05b 100644 --- a/src/utils/sortVideos.ts +++ b/src/utils/sortVideos.ts @@ -9,13 +9,13 @@ export const sortVideos = ( prevVideo[isRecentRegisterMode ? 'created_at' : 'youtube_created_at'] > nextVideo[isRecentRegisterMode ? 'created_at' : 'youtube_created_at'] ) - return 1; + return -1; if ( prevVideo[isRecentRegisterMode ? 'created_at' : 'youtube_created_at'] === nextVideo[isRecentRegisterMode ? 'created_at' : 'youtube_created_at'] ) return 0; - return -1; + return 1; }); return sortedVideos; }; From eccd7fde10059c25f5b46bb89573aea71fbb9515 Mon Sep 17 00:00:00 2001 From: gs0428 Date: Wed, 14 Feb 2024 16:19:41 +0900 Subject: [PATCH 10/14] =?UTF-8?q?feature-065:=20=ED=95=98=EC=9C=84=20?= =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=ED=83=9C=EA=B7=B8=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8A=94=20API=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/category.ts | 6 +++ src/apis/videos.ts | 2 +- src/components/category/Card.tsx | 3 +- src/components/common/chip/Chip.style.ts | 23 +++++++++ src/components/common/chip/Chip.tsx | 19 ++++++++ src/pages/CategoryPage.tsx | 60 ++++++++++++++++-------- src/styles/category/Card.style.ts | 10 ---- types/category.ts | 5 ++ 8 files changed, 97 insertions(+), 31 deletions(-) create mode 100644 src/components/common/chip/Chip.style.ts create mode 100644 src/components/common/chip/Chip.tsx diff --git a/src/apis/category.ts b/src/apis/category.ts index be348b5..62479aa 100644 --- a/src/apis/category.ts +++ b/src/apis/category.ts @@ -8,6 +8,12 @@ export const getCategories = async () => { return response.data; }; +// 카테고리 별 태그 가져오는 API +export const getCategoryTags = async (categoryId: string) => { + const response = await axiosInstance.get(`/category/${categoryId}/`); + return response.data; +}; + // 카테고리 이동1 API export const putSubToOtherTop = async ( categoryId: number, diff --git a/src/apis/videos.ts b/src/apis/videos.ts index 06bf8b4..83393b9 100644 --- a/src/apis/videos.ts +++ b/src/apis/videos.ts @@ -29,7 +29,7 @@ export const getRecentVideos = async (): Promise< }; export const getVideoById = async ( - videoId: number, + videoId: string, ): Promise>> => { const response = await axiosInstance.get(`/videos/${videoId}/get`); return response.data; diff --git a/src/components/category/Card.tsx b/src/components/category/Card.tsx index f4302f0..4b38c5a 100644 --- a/src/components/category/Card.tsx +++ b/src/components/category/Card.tsx @@ -7,6 +7,7 @@ import { CategorySelectBox } from '@/components/SummaryPage/SummaryDetailBox/Cat import { categoryState } from '@/stores/category'; import * as CardStyles from '@/styles/category/Card.style'; +import Chip from '../common/chip/Chip'; interface ICardProps { mode: 'default' | 'category' | 'recommend'; @@ -65,7 +66,7 @@ const Card: React.FC = ({ {video.description} {video.tag.map((tag) => ( - {`# ${tag.name}`} + ))} diff --git a/src/components/common/chip/Chip.style.ts b/src/components/common/chip/Chip.style.ts new file mode 100644 index 0000000..891e912 --- /dev/null +++ b/src/components/common/chip/Chip.style.ts @@ -0,0 +1,23 @@ +import theme from '@/styles/theme'; +import styled from 'styled-components'; + +export const ChipContainer = styled.div` + cursor: pointer; + margin-right: 18px; + margin-bottom: 18px; + padding: 3px 9.5px; + background-color: ${theme.color.gray100}; + border-radius: 8px; + color: ${theme.color.gray400}; + ${theme.typography.Caption1}; + + &.light { + border: 1px solid ${theme.color.gray200}; + background-color: ${theme.color.white}; + } + + &.selected { + border-color: ${theme.color.gray300}; + background-color: ${theme.color.gray100}; + } +`; diff --git a/src/components/common/chip/Chip.tsx b/src/components/common/chip/Chip.tsx new file mode 100644 index 0000000..02a224c --- /dev/null +++ b/src/components/common/chip/Chip.tsx @@ -0,0 +1,19 @@ +import { ChipContainer } from './Chip.style'; + +interface IChipProps { + name: string; + light?: boolean; + selected?: boolean; + onSelectTag?: (name: string) => void; +} + +const Chip = ({ name, light, selected, onSelectTag }: IChipProps) => { + return ( + onSelectTag && onSelectTag(name)} + >{`# ${name}`} + ); +}; + +export default Chip; diff --git a/src/pages/CategoryPage.tsx b/src/pages/CategoryPage.tsx index 1f3615c..221a35f 100644 --- a/src/pages/CategoryPage.tsx +++ b/src/pages/CategoryPage.tsx @@ -9,27 +9,38 @@ import * as CategoryPageStyles from '@/styles/category/index.style'; import Card from '@/components/category/Card'; import { useRecoilValue } from 'recoil'; import { categoryState } from '@/stores/category'; -import { ISubFolderProps } from 'types/category'; +import { ISubFolderProps, ITagProps } from 'types/category'; import EmptyCard from '@/components/category/EmptyCard'; import { deleteVideos, getRecentVideos, getVideoById } from '@/apis/videos'; import { IVideoProps } from 'types/videos'; import { sortVideos } from '@/utils/sortVideos'; import { CardContainer } from '@/styles/category/Card.style'; import { CategorySelectBox } from '@/components/SummaryPage/SummaryDetailBox/CategorySelectBox'; +import Chip from '@/components/common/chip/Chip'; +import { getCategoryTags } from '@/apis/category'; const CategoryPage = () => { const params = useParams(); const [name, setName] = useState(''); - const [menus, setMenus] = useState([]); + const [menus, setMenus] = useState([]); const [videos, setVideos] = useState([]); const [recentRegisterMode, setRecentRegisterMode] = useState(false); const [checkedVideos, setCheckedVideos] = useState([]); + const [selectedTags, setSelectedTags] = useState([]); const categories = useRecoilValue(categoryState); const [selectedCategoryId, setSelectedCategoryId] = useState( categories.length ? categories[0].categoryId : -1, ); + const onSelectTag = (name: string) => { + if (selectedTags.includes(name)) { + setSelectedTags(selectedTags.filter((tag) => tag !== name)); + } else { + setSelectedTags([...selectedTags, name]); + } + }; + const handleSelectCategory = (categoryId: number) => { setSelectedCategoryId(categoryId); }; @@ -45,33 +56,31 @@ const CategoryPage = () => { .then((res) => { setVideos(res.result.videos); setName('최근 읽은 영상'); + setMenus([]); }) .catch((err) => console.log(err)); } else if (!params.sub_folder) { - getVideoById(Number(params.top_folder)).then((res) => { + getVideoById(params.top_folder).then((res) => { const index = categories.findIndex( (category) => category.categoryId === Number(params.top_folder), ); - if (!res.isSuccess) { - setName(categories[index].name); - setMenus([]); - setVideos([]); - return; - } setName(categories[index].name); setMenus(categories[index].subFolders); setVideos(res.isSuccess ? res.result.videos : []); }); } else { - getVideoById(Number(params.sub_folder)).then((res) => { + getVideoById(params.sub_folder).then((res) => { const index = categories.findIndex( (category) => category.categoryId === Number(params.top_folder), ); const subIndex = categories[index].subFolders.findIndex( (subFolder) => subFolder.categoryId === Number(params.sub_folder), ); + getCategoryTags(params.sub_folder!).then((res) => + setMenus(res.result.tags), + ); setName(categories[index].subFolders[subIndex].name); - setMenus([]); + setVideos(res.isSuccess ? res.result.videos : []); }); } @@ -147,14 +156,27 @@ const CategoryPage = () => { ) : ( <> -
- {menus.map((menu) => ( - - {menu.name} - +
+ {menus.map((menu: ISubFolderProps | ITagProps) => ( + <> + {'tag_id' in menu && ( + + )} + {!('tag_id' in menu) && ( + + {menu.name} + + )} + ))}
diff --git a/src/styles/category/Card.style.ts b/src/styles/category/Card.style.ts index 33760df..32a6263 100644 --- a/src/styles/category/Card.style.ts +++ b/src/styles/category/Card.style.ts @@ -143,13 +143,3 @@ export const DropdownWrap = styled.div` } } `; - -export const Chip = styled.div` - margin-right: 18px; - margin-bottom: 18px; - padding: 3px 9.5px; - background-color: ${theme.color.gray100}; - border-radius: 8px; - color: ${theme.color.gray400}; - ${theme.typography.Caption1}; -`; diff --git a/types/category.ts b/types/category.ts index 760296a..ac6a12b 100644 --- a/types/category.ts +++ b/types/category.ts @@ -15,3 +15,8 @@ export interface ISelectedCategoryProps { name: string; categoryId: number; } + +export interface ITagProps { + tag_id: number; + name: string; +} From a0f87d64c283c748abf213cdad32eee0fcb22d68 Mon Sep 17 00:00:00 2001 From: gs0428 Date: Wed, 14 Feb 2024 16:22:08 +0900 Subject: [PATCH 11/14] =?UTF-8?q?feature-065:=20Home=20Empty=20item=20styl?= =?UTF-8?q?e=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Home/RecentVideos.tsx | 38 +++++++++++++--------------- src/styles/HomepageStyle.ts | 8 +++--- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/components/Home/RecentVideos.tsx b/src/components/Home/RecentVideos.tsx index 0e910b3..52a3967 100644 --- a/src/components/Home/RecentVideos.tsx +++ b/src/components/Home/RecentVideos.tsx @@ -19,31 +19,29 @@ const RecentVideos = ({ videos }: IRecentVideosProp) => { return (
-
- 최근 읽은 영상 - {videos.length >= 4 && ( - -
- -
- - )} +
+ 최근 읽은 영상 + {videos.length >= 4 && ( + +
+ +
+ + )}
- + {videos.length === 0 && ( - <> +
비어있는 비디오 이미지
-
- - 처음 방문하셨나요?
아직 정리해본 영상이 없어요! -
- -

영상 정리해보기

-
-
- + + 처음 방문하셨나요?
아직 정리해본 영상이 없어요! +
+ +

영상 정리해보기

+
+
)} {videos.length > 0 && ( diff --git a/src/styles/HomepageStyle.ts b/src/styles/HomepageStyle.ts index f3bd6fc..a152ce2 100644 --- a/src/styles/HomepageStyle.ts +++ b/src/styles/HomepageStyle.ts @@ -142,10 +142,11 @@ export const RecentVideosContainer = styled.div` width: 910px; } - .empty-video{ + .empty-container { display: flex; - justify-content: center; - align-content: center; + flex-direction: column; + align-items: center; + text-align: center; } .empty-video img { @@ -192,7 +193,6 @@ export const VideosTitle = styled.h2` width: 910px; height: 45px; font-weight: bold; - `; export const VideosSubtitle = styled.h4` From 3c36e6d892c1457d0ac92e9235d165efd4f1a078 Mon Sep 17 00:00:00 2001 From: gs0428 Date: Thu, 15 Feb 2024 01:37:27 +0900 Subject: [PATCH 12/14] =?UTF-8?q?feature-065:=20=ED=95=98=EC=9C=84=20?= =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=ED=83=9C=EA=B7=B8=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EB=B9=84?= =?UTF-8?q?=EB=94=94=EC=98=A4=20=EB=B3=B4=EC=9D=B4=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useMoveCategory.ts | 1 - src/pages/CategoryPage.tsx | 67 ++++++++++++++---------------- src/styles/category/index.style.ts | 1 + src/utils/handleVideo.ts | 36 ++++++++++++++++ 4 files changed, 69 insertions(+), 36 deletions(-) create mode 100644 src/utils/handleVideo.ts diff --git a/src/hooks/useMoveCategory.ts b/src/hooks/useMoveCategory.ts index 3be900b..e1d61f2 100644 --- a/src/hooks/useMoveCategory.ts +++ b/src/hooks/useMoveCategory.ts @@ -25,7 +25,6 @@ const useMoveCategory = () => { grabedCategory.current!.categoryId, topId, ); - console.log(res); if (res.isSuccess) { await updateCategories(); navigate(`/category/${grabedCategory.current?.topCategoryId}`); diff --git a/src/pages/CategoryPage.tsx b/src/pages/CategoryPage.tsx index 221a35f..d115d1f 100644 --- a/src/pages/CategoryPage.tsx +++ b/src/pages/CategoryPage.tsx @@ -11,13 +11,13 @@ import { useRecoilValue } from 'recoil'; import { categoryState } from '@/stores/category'; import { ISubFolderProps, ITagProps } from 'types/category'; import EmptyCard from '@/components/category/EmptyCard'; -import { deleteVideos, getRecentVideos, getVideoById } from '@/apis/videos'; +import { deleteVideos, getRecentVideos } from '@/apis/videos'; import { IVideoProps } from 'types/videos'; import { sortVideos } from '@/utils/sortVideos'; import { CardContainer } from '@/styles/category/Card.style'; import { CategorySelectBox } from '@/components/SummaryPage/SummaryDetailBox/CategorySelectBox'; import Chip from '@/components/common/chip/Chip'; -import { getCategoryTags } from '@/apis/category'; +import handleVideo from '@/utils/handleVideo'; const CategoryPage = () => { const params = useParams(); @@ -59,30 +59,15 @@ const CategoryPage = () => { setMenus([]); }) .catch((err) => console.log(err)); - } else if (!params.sub_folder) { - getVideoById(params.top_folder).then((res) => { - const index = categories.findIndex( - (category) => category.categoryId === Number(params.top_folder), - ); - setName(categories[index].name); - setMenus(categories[index].subFolders); - setVideos(res.isSuccess ? res.result.videos : []); - }); } else { - getVideoById(params.sub_folder).then((res) => { - const index = categories.findIndex( - (category) => category.categoryId === Number(params.top_folder), - ); - const subIndex = categories[index].subFolders.findIndex( - (subFolder) => subFolder.categoryId === Number(params.sub_folder), - ); - getCategoryTags(params.sub_folder!).then((res) => - setMenus(res.result.tags), - ); - setName(categories[index].subFolders[subIndex].name); - - setVideos(res.isSuccess ? res.result.videos : []); - }); + handleVideo( + categories, + params.top_folder, + params.sub_folder!, + setMenus, + setName, + setVideos, + ); } setCheckedVideos([]); }, [categories, params.sub_folder, params.top_folder]); @@ -104,7 +89,6 @@ const CategoryPage = () => { if (checkedVideos.length === videos.length) { handleDeleteVideos(); } else { - console.log('모두 선택'); setCheckedVideos(videos.map((video) => video.video_id)); } }; @@ -198,15 +182,28 @@ const CategoryPage = () => { )} {sortedVideos.length > 0 && ( - {sortedVideos.map((video) => ( - - ))} + {sortedVideos.map((video) => { + const matchedTagCount = video.tag.reduce((acc, cur) => { + if (selectedTags.includes(cur.name)) return (acc += 1); + return acc; + }, 0); + if ( + params.sub_folder && + selectedTags.length && + matchedTagCount !== selectedTags.length + ) + return; + + return ( + + ); + })} )} diff --git a/src/styles/category/index.style.ts b/src/styles/category/index.style.ts index faef665..399d20f 100644 --- a/src/styles/category/index.style.ts +++ b/src/styles/category/index.style.ts @@ -20,6 +20,7 @@ export const Container = styled.div` export const MenuWrap = styled.div` display: flex; + min-width: 'fit-content'; justify-content: space-between; align-items: center; margin-bottom: 40px; diff --git a/src/utils/handleVideo.ts b/src/utils/handleVideo.ts new file mode 100644 index 0000000..ab90820 --- /dev/null +++ b/src/utils/handleVideo.ts @@ -0,0 +1,36 @@ +import { getCategoryTags } from '@/apis/category'; +import { getVideoById } from '@/apis/videos'; +import { IFolderProps, ISubFolderProps, ITagProps } from 'types/category'; +import { IVideoProps } from 'types/videos'; + +const handleVideo = async ( + categories: IFolderProps[], + topCategoryId: string, + subCategoryId: string, + setMenus: React.Dispatch< + React.SetStateAction + >, + setName: React.Dispatch>, + setVideos: React.Dispatch>, +) => { + await getVideoById(topCategoryId).then((res) => { + const topCategory = categories.find( + (category) => category.categoryId === Number(topCategoryId), + ); + if (subCategoryId) { + const subName = topCategory?.subFolders.find( + (subFolder) => subFolder.categoryId === Number(subCategoryId), + ); + setName(subName!.name); + getCategoryTags(subCategoryId!).then((res) => { + if (res.isSuccess) setMenus(res.result.tags); + else setMenus([]); + }); + } else { + setName(topCategory!.name); + setMenus(topCategory!.subFolders); + } + setVideos(res.isSuccess ? res.result.videos : []); + }); +}; +export default handleVideo; From fabc6ebc79282637daf56b46108fd1c0b5e03fe9 Mon Sep 17 00:00:00 2001 From: gs0428 Date: Thu, 15 Feb 2024 02:02:32 +0900 Subject: [PATCH 13/14] =?UTF-8?q?feature-065:=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=A9=94?= =?UTF-8?q?=EB=89=B4=20=EB=B6=80=EB=B6=84=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/category/DefaultMenu.tsx | 68 ++++++++++++ src/components/category/VideoSelectMenu.tsx | 72 ++++++++++++ src/pages/CategoryPage.tsx | 115 +++----------------- 3 files changed, 158 insertions(+), 97 deletions(-) create mode 100644 src/components/category/DefaultMenu.tsx create mode 100644 src/components/category/VideoSelectMenu.tsx diff --git a/src/components/category/DefaultMenu.tsx b/src/components/category/DefaultMenu.tsx new file mode 100644 index 0000000..8521a92 --- /dev/null +++ b/src/components/category/DefaultMenu.tsx @@ -0,0 +1,68 @@ +import * as CategoryPageStyles from '@/styles/category/index.style'; +import { ISubFolderProps, ITagProps } from 'types/category'; +import Chip from '../common/chip/Chip'; +import ChangeBottomSvg from '@/assets/icons/change-bottom.svg?react'; +import ChangeTopSvg from '@/assets/icons/change-top.svg?react'; + +interface IDefaultMenuProps { + menus: ISubFolderProps[] | ITagProps[]; + recentRegisterMode: boolean; + selectedTags: string[]; + setSelectedTags: React.Dispatch>; + toggleRecentRegisterMode: () => void; +} + +const DefaultMenu = ({ + menus, + recentRegisterMode, + selectedTags, + setSelectedTags, + toggleRecentRegisterMode, +}: IDefaultMenuProps) => { + const onSelectTag = (name: string) => { + if (selectedTags.includes(name)) { + setSelectedTags(selectedTags.filter((tag) => tag !== name)); + } else { + setSelectedTags([...selectedTags, name]); + } + }; + return ( + <> +
+ {menus.map((menu: ISubFolderProps | ITagProps) => ( + <> + {'tag_id' in menu && ( + + )} + {!('tag_id' in menu) && ( + + {menu.name} + + )} + + ))} +
+ + + {recentRegisterMode ? '최근등록순' : '최근영상순'} + + {recentRegisterMode ? ( + + ) : ( + + )} + + + ); +}; + +export default DefaultMenu; diff --git a/src/components/category/VideoSelectMenu.tsx b/src/components/category/VideoSelectMenu.tsx new file mode 100644 index 0000000..3685bb9 --- /dev/null +++ b/src/components/category/VideoSelectMenu.tsx @@ -0,0 +1,72 @@ +import * as CategoryPageStyles from '@/styles/category/index.style'; +import GarbageSvg from '@/assets/icons/garbage.svg?react'; +import CloseSvg from '@/assets/icons/close.svg?react'; +import { useState } from 'react'; +import { CategorySelectBox } from '../SummaryPage/SummaryDetailBox/CategorySelectBox'; +import { IFolderProps } from 'types/category'; + +interface IVideoSelectMenuProps { + categories: IFolderProps[]; + totalVideoCount: number; + checkedVideos: number[]; + setCheckedVideos: React.Dispatch>; + handleDeleteVideos: () => void; + allCheckBtnHandler: () => void; +} + +const VideoSelectMenu = ({ + categories, + totalVideoCount, + checkedVideos, + setCheckedVideos, + handleDeleteVideos, + allCheckBtnHandler, +}: IVideoSelectMenuProps) => { + const [selectedCategoryId, setSelectedCategoryId] = useState( + categories.length ? categories[0].categoryId : -1, + ); + + const handleSelectCategory = (categoryId: number) => { + setSelectedCategoryId(categoryId); + }; + + const onFileClick = (e: React.MouseEvent) => { + e.stopPropagation(); + // 비디오 이동 API 호출 후 모든 비디오 받아오는 API 재호출로 최신화하기 + }; + return ( + +
+ + {checkedVideos.length === totalVideoCount ? '모두 삭제' : '모두 선택'} + + + {checkedVideos.length}개 선택 + +
+ + + + + + + + + { + setCheckedVideos([]); + }} + /> + + +
+ ); +}; + +export default VideoSelectMenu; diff --git a/src/pages/CategoryPage.tsx b/src/pages/CategoryPage.tsx index d115d1f..e48c5fd 100644 --- a/src/pages/CategoryPage.tsx +++ b/src/pages/CategoryPage.tsx @@ -1,10 +1,6 @@ import CategoryTitle from '@/components/category/CategoryTitle'; import { useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; -import ChangeBottomSvg from '@/assets/icons/change-bottom.svg?react'; -import ChangeTopSvg from '@/assets/icons/change-top.svg?react'; -import GarbageSvg from '@/assets/icons/garbage.svg?react'; -import CloseSvg from '@/assets/icons/close.svg?react'; import * as CategoryPageStyles from '@/styles/category/index.style'; import Card from '@/components/category/Card'; import { useRecoilValue } from 'recoil'; @@ -15,9 +11,9 @@ import { deleteVideos, getRecentVideos } from '@/apis/videos'; import { IVideoProps } from 'types/videos'; import { sortVideos } from '@/utils/sortVideos'; import { CardContainer } from '@/styles/category/Card.style'; -import { CategorySelectBox } from '@/components/SummaryPage/SummaryDetailBox/CategorySelectBox'; -import Chip from '@/components/common/chip/Chip'; import handleVideo from '@/utils/handleVideo'; +import VideoSelectMenu from '@/components/category/VideoSelectMenu'; +import DefaultMenu from '@/components/category/DefaultMenu'; const CategoryPage = () => { const params = useParams(); @@ -29,22 +25,6 @@ const CategoryPage = () => { const [selectedTags, setSelectedTags] = useState([]); const categories = useRecoilValue(categoryState); - const [selectedCategoryId, setSelectedCategoryId] = useState( - categories.length ? categories[0].categoryId : -1, - ); - - const onSelectTag = (name: string) => { - if (selectedTags.includes(name)) { - setSelectedTags(selectedTags.filter((tag) => tag !== name)); - } else { - setSelectedTags([...selectedTags, name]); - } - }; - - const handleSelectCategory = (categoryId: number) => { - setSelectedCategoryId(categoryId); - }; - const toggleRecentRegisterMode = () => setRecentRegisterMode(!recentRegisterMode); @@ -93,87 +73,27 @@ const CategoryPage = () => { } }; - const onFileClick = (e: React.MouseEvent) => { - e.stopPropagation(); - // 비디오 이동 API 호출 후 모든 비디오 받아오는 API 재호출로 최신화하기 - }; - return ( {checkedVideos.length > 0 ? ( - -
- - {checkedVideos.length === sortedVideos.length - ? '모두 삭제' - : '모두 선택'} - - - {checkedVideos.length}개 선택 - -
- - - - - - - - - { - setCheckedVideos([]); - }} - /> - - -
+ ) : ( - <> -
- {menus.map((menu: ISubFolderProps | ITagProps) => ( - <> - {'tag_id' in menu && ( - - )} - {!('tag_id' in menu) && ( - - {menu.name} - - )} - - ))} -
- - - {recentRegisterMode ? '최근등록순' : '최근영상순'} - - {recentRegisterMode ? ( - - ) : ( - - )} - - + )}
@@ -183,6 +103,7 @@ const CategoryPage = () => { {sortedVideos.length > 0 && ( {sortedVideos.map((video) => { + // 하위 카테고리에 있을 때 태그 선택된 것에 따라 비디오 보여지게하는 로직 const matchedTagCount = video.tag.reduce((acc, cur) => { if (selectedTags.includes(cur.name)) return (acc += 1); return acc; From 805d83d24a23b8e25337bbffeb3fc12cbe8a32dc Mon Sep 17 00:00:00 2001 From: gs0428 Date: Thu, 15 Feb 2024 02:09:13 +0900 Subject: [PATCH 14/14] =?UTF-8?q?feature-065:=20unique=20key=20=EB=B6=80?= =?UTF-8?q?=EC=97=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/category/DefaultMenu.tsx | 4 ++-- src/pages/CategoryPage.tsx | 2 +- src/utils/handleVideo.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/category/DefaultMenu.tsx b/src/components/category/DefaultMenu.tsx index 8521a92..eb355d2 100644 --- a/src/components/category/DefaultMenu.tsx +++ b/src/components/category/DefaultMenu.tsx @@ -30,7 +30,7 @@ const DefaultMenu = ({ <>
{menus.map((menu: ISubFolderProps | ITagProps) => ( - <> +
{'tag_id' in menu && ( )} - +
))}
diff --git a/src/pages/CategoryPage.tsx b/src/pages/CategoryPage.tsx index e48c5fd..ac9eb6b 100644 --- a/src/pages/CategoryPage.tsx +++ b/src/pages/CategoryPage.tsx @@ -121,7 +121,7 @@ const CategoryPage = () => { video={video} checkedVideos={checkedVideos} setCheckedVideos={setCheckedVideos} - key={video.category_id} + key={video.video_id} /> ); })} diff --git a/src/utils/handleVideo.ts b/src/utils/handleVideo.ts index ab90820..f6301a3 100644 --- a/src/utils/handleVideo.ts +++ b/src/utils/handleVideo.ts @@ -13,7 +13,7 @@ const handleVideo = async ( setName: React.Dispatch>, setVideos: React.Dispatch>, ) => { - await getVideoById(topCategoryId).then((res) => { + await getVideoById(topCategoryId).then(async (res) => { const topCategory = categories.find( (category) => category.categoryId === Number(topCategoryId), ); @@ -22,7 +22,7 @@ const handleVideo = async ( (subFolder) => subFolder.categoryId === Number(subCategoryId), ); setName(subName!.name); - getCategoryTags(subCategoryId!).then((res) => { + await getCategoryTags(subCategoryId!).then((res) => { if (res.isSuccess) setMenus(res.result.tags); else setMenus([]); });