Skip to content

Commit

Permalink
Merge pull request #51 from teamViNO/feature-046
Browse files Browse the repository at this point in the history
feature-046: 카테고리 페이지에 있는 sub menu에서 이동하는 로직 추가
  • Loading branch information
whistleJs authored Feb 9, 2024
2 parents a67c6df + 509ba0b commit f3cd503
Show file tree
Hide file tree
Showing 27 changed files with 832 additions and 472 deletions.
16 changes: 15 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useRecoilValue } from 'recoil';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
import { ThemeProvider } from 'styled-components';

Expand Down Expand Up @@ -29,9 +29,23 @@ import { ToastList } from './components/common';

// Store
import { userTokenState } from './stores/user';
import { useEffect } from 'react';
import { categoryState } from './stores/category';
import { getCategories } from './apis/category';
import handleCategory from './utils/handleCategory';

const App = () => {
const setCategories = useSetRecoilState(categoryState);
const userToken = useRecoilValue(userTokenState);
const { initializeCategory } = handleCategory();
useEffect(() => {
userToken &&
getCategories()
.then((res) => {
setCategories(initializeCategory(res));
})
.catch((err) => console.log(err));
}, [userToken]);

return (
<ThemeProvider theme={theme}>
Expand Down
47 changes: 47 additions & 0 deletions src/apis/category.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import axiosInstance from './config/instance';

// 모든 카테고리 가져오는 API
export const getCategories = async () => {
const response = await axiosInstance.get('/category');
return response.data.result;
};

// 카테고리 이동1 API
export const putSubToOtherTop = async (
categoryId: number,
topCategoryId: number,
) => {
const response = await axiosInstance.put(
`/category/${categoryId}/${topCategoryId}`,
);
return response.data.result;
};

// 카테고리 이동2 API
export const putSubToTop = async (categoryId: number) => {
const response = await axiosInstance.put(`/category/up/${categoryId}`);
return response.data.result;
};

// 카테고리 이동3 API
export const putTopToOtherTop = async (
categoryId: number,
topCategoryId: number,
) => {
const response = await axiosInstance.put(
`/category/down/${categoryId}/${topCategoryId}`,
);
return response.data.result;
};

// 상위 카테고리 추가 API
export const postTopCategroy = async () => {
const response = await axiosInstance.post('/category');
return response.data.result;
};

// 상위 카테고리 추가 API
export const postSubCategroy = async (topCategoryId: number) => {
const response = await axiosInstance.post(`/category/${topCategoryId}`);
return response.data.result;
};
25 changes: 14 additions & 11 deletions src/components/Home/InsightVideos.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ interface InsightVideosProps {
popularHashtags: string[];
}

const InsightVideos: React.FC<InsightVideosProps> = ({ username, popularHashtags }) => {
const formattedHashtags = popularHashtags.map(tag => '#' + tag);
const [categoryItems] = useState<cardDummy[]>([]);
const [checkedItems, setCheckedItems] = useState<boolean[]>([]);
const InsightVideos: React.FC<InsightVideosProps> = ({
username,
popularHashtags,
}) => {
const formattedHashtags = popularHashtags.map((tag) => '#' + tag);
const [categoryItems] = useState<cardDummy[]>([]);
const [checkedItems, setCheckedItems] = useState<boolean[]>([]);

return (
<InsightVideosContainer>
Expand All @@ -23,16 +26,16 @@ const InsightVideos: React.FC<InsightVideosProps> = ({ username, popularHashtags
콘텐츠에요!
</h4>
</div>
<div className='insight-videos'>
<Card
categoryItems={categoryItems}
checkedItems={checkedItems}
setCheckedItems={setCheckedItems}
/>
<div className="insight-videos">
<Card
videos={categoryItems}
checkedVideos={checkedItems}
setCheckedVideos={setCheckedItems}
/>
</div>
</div>
</InsightVideosContainer>
);
};
};

export default InsightVideos;
120 changes: 72 additions & 48 deletions src/components/category/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,72 +3,97 @@ import VideoTag from '../common/videoTag';
import * as CardStyles from '@/styles/category/Card.style';

export interface cardDummy {
imageURL: string;
video_id: number;
category_id: number;
title: string;
summary: string;
tags: string[];
description: string;
image: string;
link: string;
created_at: string;
youtube_created_at: string;
tag: [{ name: string }];
}

interface CardInputProps {
categoryItems : Array<cardDummy>;
checkedItems : boolean[];
setCheckedItems : (value : boolean[]) => void;
interface ICardProps {
videos: cardDummy[];
checkedVideos: boolean[];
setCheckedVideos: (value: boolean[]) => void;
}

const Card : React.FC<CardInputProps> = ({ categoryItems, checkedItems, setCheckedItems}) => {
const [isShadow,setIsShadow] = useState<boolean[]>(new Array(6).fill(false));

const Card: React.FC<ICardProps> = ({
videos,
checkedVideos,
setCheckedVideos,
}) => {
const [isShadow, setIsShadow] = useState<boolean[]>(new Array(6).fill(false));

useEffect(() => {
if(checkedItems.includes(true)){ // 1개 이상 클릭 시 모든 hover event 활성화
setIsShadow(isShadow.map(() => true))
} else if(!isShadow.includes(false)){
if (checkedVideos.includes(true)) {
// 1개 이상 클릭 시 모든 hover event 활성화
setIsShadow(isShadow.map(() => true));
} else if (!isShadow.includes(false)) {
//모든 hover 활성화, 모든 체크 비활성화 시 모든 hover 활성화 제거
setIsShadow(isShadow.map(() => false))
setIsShadow(isShadow.map(() => false));
}
}, [checkedItems])
}, [checkedVideos]);

const handleMouseEnter = (id : number) => {
let prev = checkedItems.includes(true) ? [...isShadow] : new Array(isShadow.length).fill(false);
// 체크박스 미선택 이동 시 isshadow 중복 작동으로 인해 방식 변경
prev[id] = true;
setIsShadow(prev);
}
const handleMouseEnter = (id: number) => {
const prev = checkedVideos.includes(true)
? [...isShadow]
: new Array(isShadow.length).fill(false);
// 체크박스 미선택 이동 시 isshadow 중복 작동으로 인해 방식 변경
prev[id] = true;
setIsShadow(prev);
};

const handleMouseLeave = (id : number) => {
if(!checkedItems.includes(true)){ // 선택되면 유지
let prev = [...isShadow];
prev[id] = false;
setIsShadow(prev);
const handleMouseLeave = (id: number) => {
if (!checkedVideos.includes(true)) {
// 선택되면 유지
const prev = [...isShadow];
prev[id] = false;
setIsShadow(prev);
}
}
};

const checkBoxHandler = (id : number) => {
let prev = [...checkedItems];
prev[id] = !prev[id];
setCheckedItems(prev);
}

const checkBoxHandler = (id: number) => {
const prev = [...checkedVideos];
prev[id] = !prev[id];
setCheckedVideos(prev);
};
return (
<CardStyles.Container>
{categoryItems.map((categoryItem, idx) => (
<CardStyles.Wrap key={`${categoryItem.title}-wrap`} onMouseEnter={() => handleMouseEnter(idx)} onMouseLeave={() => handleMouseLeave(idx)}>
<img src={categoryItem.imageURL} alt="썸네일 이미지"
style={{filter : isShadow[idx]? 'brightness(50%)' : ''}}/>
{isShadow[idx] && <CardStyles.CheckBox type='checkbox' checked={checkedItems[idx]} onChange={() => checkBoxHandler(idx)}/>}
<CardStyles.Content key={`${categoryItem.title}-card-content`}>
<CardStyles.Title key={`${categoryItem.title}`}>
{categoryItem.title}
{videos.map((video, idx) => (
<CardStyles.Wrap
key={`${video.title}-wrap`}
onMouseEnter={() => handleMouseEnter(idx)}
onMouseLeave={() => handleMouseLeave(idx)}
>
<CardStyles.Image
src={video.image}
alt="썸네일 이미지"
key={`${video.title}-image`}
/>
{isShadow[idx] && (
<CardStyles.CheckBox
type="checkbox"
checked={checkedVideos[idx]}
onChange={() => checkBoxHandler(idx)}
/>
)}
<CardStyles.Content key={`${video.title}-card-content`}>
<CardStyles.Title key={`${video.title}`}>
{video.title}
</CardStyles.Title>
<CardStyles.Summary key={`${categoryItem.summary}`}>
{categoryItem.summary}
<CardStyles.Summary key={`${video.description}`}>
{video.description}
</CardStyles.Summary>
<CardStyles.ChipWrap key={`${categoryItem.title}-chip-wrap`}>
{categoryItem.tags.map((tag) => (
<CardStyles.ChipWrap key={`${video.title}-chip-wrap`}>
{video.tag.map((tag) => (
<VideoTag
content={`# ${tag}`}
content={`# ${tag.name}`}
color={'gray400'}
typography="Caption1"
key={`${categoryItem.title}-${tag}`}
key={`${video.title}-${tag.name}`}
/>
))}
</CardStyles.ChipWrap>
Expand All @@ -80,4 +105,3 @@ const Card : React.FC<CardInputProps> = ({ categoryItems, checkedItems, setCheck
};

export default Card;

6 changes: 3 additions & 3 deletions src/components/category/CategoryTitle.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import * as CategoryTitleStyles from '@/styles/category/CategoryTitle.style';

interface ICategoryTitleProps {
title: string;
name: string;
totalVideos: number;
}

const CategoryTitle = ({ title, totalVideos }: ICategoryTitleProps) => {
const CategoryTitle = ({ name, totalVideos }: ICategoryTitleProps) => {
return (
<CategoryTitleStyles.Container>
<CategoryTitleStyles.Title>{title}</CategoryTitleStyles.Title>
<CategoryTitleStyles.Title>{name}</CategoryTitleStyles.Title>
<CategoryTitleStyles.Count>{totalVideos}</CategoryTitleStyles.Count>
</CategoryTitleStyles.Container>
);
Expand Down
21 changes: 21 additions & 0 deletions src/components/category/EmptyCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import EmptyFile from '@/assets/empty-file.png';
import * as EmptyCardStyles from '@/styles/category/EmptyCard.style';

const EmptyCard = () => {
return (
<EmptyCardStyles.Container>
<img src={EmptyFile} alt="비어있는 폴더" />
<EmptyCardStyles.ContentWrap>
<EmptyCardStyles.Content>
아직 관련 영상이 없어요!
</EmptyCardStyles.Content>
<EmptyCardStyles.Content>
관련 영상들을 모아보세요
</EmptyCardStyles.Content>
</EmptyCardStyles.ContentWrap>
<EmptyCardStyles.Button>영상 정리해보기</EmptyCardStyles.Button>
</EmptyCardStyles.Container>
);
};

export default EmptyCard;
23 changes: 18 additions & 5 deletions src/components/layout/sideBar/AddCategory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,38 @@ import * as AddCategoryStyle from '@/styles/layout/sideBar/AddCategory.style';
import PlusSvg from '@/assets/icons/plus.svg?react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { topCategoryModalState } from '@/stores/modal';
import { userState } from '@/stores/user';
import { userTokenState } from '@/stores/user';
import { useState } from 'react';
import GuestNoticeModal from '@/components/modals/GuestNoticeModal';

const AddCategory = () => {
const isUser = useRecoilValue(userState);
const isUser = useRecoilValue(userTokenState);
const setTopCategoryModal = useSetRecoilState(topCategoryModalState);
const [isGuestNoticeModalOpen, setIsGuestNoticeModalOpen] = useState(false);

const openModal = (e: React.MouseEvent) => {
const openAddModal = (e: React.MouseEvent<HTMLButtonElement>) => {
setTopCategoryModal(true);
e.stopPropagation();
};

const handleClickedAdd = (e: React.MouseEvent) =>
isUser ? openModal(e) : alert('로그인을 해주세요');
const openGuestNoticeModal = (e: React.MouseEvent<HTMLButtonElement>) => {
setIsGuestNoticeModalOpen(true);
e.stopPropagation();
};

const handleClickedAdd = (e: React.MouseEvent<HTMLButtonElement>) =>
isUser ? openAddModal(e) : openGuestNoticeModal(e);
return (
<AddCategoryStyle.Wrap>
<AddCategoryStyle.Text>카테고리</AddCategoryStyle.Text>
<AddCategoryStyle.Button onClick={handleClickedAdd}>
<PlusSvg width={20} height={20} />
</AddCategoryStyle.Button>
{isGuestNoticeModalOpen && (
<GuestNoticeModal
setIsGuestNoticeModalOpen={setIsGuestNoticeModalOpen}
/>
)}
</AddCategoryStyle.Wrap>
);
};
Expand Down
Loading

0 comments on commit f3cd503

Please sign in to comment.