From 523c3621383a9a26e07e62ae0bcc089df4cf6976 Mon Sep 17 00:00:00 2001 From: Choi Yong Won Date: Sun, 7 Sep 2025 13:40:52 +0900 Subject: [PATCH 1/7] =?UTF-8?q?fix:[#330]=20zod=EB=A1=9C=20=EC=9D=B8?= =?UTF-8?q?=ED=95=98=EC=97=AC,=20=ED=99=88=EC=9D=B4=20=EB=82=98=EC=98=A4?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8D=98=20=EB=B6=80=EB=B6=84=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/validation/home/popularSchema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/validation/home/popularSchema.ts b/src/validation/home/popularSchema.ts index be7b291..2d43a81 100644 --- a/src/validation/home/popularSchema.ts +++ b/src/validation/home/popularSchema.ts @@ -3,7 +3,7 @@ import { z } from 'zod'; export const PopularTodoSchema = z.object({ todoId: z.number(), title: z.string(), - profileImage: z.string().url(), + profileImage: z.string().url().nullable(), memberNickname: z.string(), memberLevel: z.string().nullable(), jobName: z.string(), From e289db6f90663c35fea25e9d792bb3db1535847d Mon Sep 17 00:00:00 2001 From: Choi Yong Won Date: Sun, 7 Sep 2025 13:59:04 +0900 Subject: [PATCH 2/7] =?UTF-8?q?fix:[#330]=20a=EB=A7=81=ED=81=AC=EB=A5=BC?= =?UTF-8?q?=20=EC=9D=B4=EC=9A=A9=ED=95=98=EC=97=AC=20url=EC=9D=84=20?= =?UTF-8?q?=ED=86=B5=ED=95=B4=20=EC=9D=B4=EB=8F=99=EC=9D=B4=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hook/useHomeQuery.ts | 1 + src/pages/home/components/HomeRecruit.tsx | 59 ++++++++++++++--------- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/hook/useHomeQuery.ts b/src/hook/useHomeQuery.ts index b59bc5e..a491237 100644 --- a/src/hook/useHomeQuery.ts +++ b/src/hook/useHomeQuery.ts @@ -55,6 +55,7 @@ interface RecruitProps { active: number; jobName: string; postDate: string; + url: string; } const NewRecruit = async (pageNum: number, postDate: string) => { const token = localStorage.getItem('accessToken'); diff --git a/src/pages/home/components/HomeRecruit.tsx b/src/pages/home/components/HomeRecruit.tsx index 1c37534..ca941a9 100644 --- a/src/pages/home/components/HomeRecruit.tsx +++ b/src/pages/home/components/HomeRecruit.tsx @@ -60,36 +60,49 @@ const HomeRecruit = () => {
{recruitData && recruitData.map((data) => ( -
toggleLike(data.id)} + key={data.id} + className="flex min-h-[312px] w-[384px] cursor-pointer flex-col items-start rounded-[30px] border-[1.2px] border-gray-300 p-[30px] hover:shadow-shadow2" > - {likedItems[data.id] ? : } -
-
- {data.companyName} -
-
- {data.jobName} -
-
- {data.title} -
+
{ + e.preventDefault(); + e.stopPropagation(); + toggleLike(data.id); + }} + > + {likedItems[data.id] ? : } +
+
+ {data.companyName} +
+
+ {data.jobName} +
+
+ {data.title} +
-
-
{data.postDate}
-
- - - {data.count}명이 관심을 보였어요 - +
+
+ {data.postDate} +
+
+ + + {data.count}명이 관심을 보였어요 + +
-
+ ))}
From 06bca4ecd56d0108397c0f457b53b8f0b1af6e4c Mon Sep 17 00:00:00 2001 From: Choi Yong Won Date: Sun, 7 Sep 2025 14:34:55 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat:=20[#330]=ED=99=88=20=EB=A6=AC?= =?UTF-8?q?=ED=81=AC=EB=A3=A8=ED=8A=B8=20=EC=B9=B4=EB=93=9C=20=ED=81=B4?= =?UTF-8?q?=EB=A6=AD=20=EC=8B=9C=20=EC=83=81=EC=84=B8=20=EB=AA=A8=EB=8B=AC?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=20=EB=B3=80=ED=99=98=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/home/components/HomeRecruit.tsx | 135 ++++++++++++++++------ 1 file changed, 99 insertions(+), 36 deletions(-) diff --git a/src/pages/home/components/HomeRecruit.tsx b/src/pages/home/components/HomeRecruit.tsx index ca941a9..3774cf9 100644 --- a/src/pages/home/components/HomeRecruit.tsx +++ b/src/pages/home/components/HomeRecruit.tsx @@ -8,17 +8,77 @@ import { useRecruitQuery } from '@hook/useHomeQuery'; import LoadingSpinner from '@common/LoadingSpinner'; import { useFilterStore } from '@store/filterStore'; import { useNavigate } from 'react-router-dom'; +import CardDetail from '@pages/jobSearch/components/CardDetail'; +import { RecruitItem } from '@validation/recruit/recruitSchema'; + +interface RecruitData { + id: number; + title: string; + companyName: string; + jobName: string; + postDate: string; + count: number | string; + url: string; + active: number; + locationName?: string | null; + jobTypeName?: string; + experienceLevel?: string; + requiredEducationLevel?: string; + closeType?: string; + salary?: string; + postTimestamp?: string; + 'expiration-timestamp'?: string; + 'expiration-date'?: string; + deadline?: string; +} const HomeRecruit = () => { const regionName = useUserStore((state) => state.regionName); const setSelection = useFilterStore((state) => state.setSelection); const [likedItems, setLikedItems] = useState<{ [key: number]: boolean }>({}); + const [selectedCard, setSelectedCard] = useState(null); + const [isModalOpen, setIsModalOpen] = useState(false); + const toggleLike = (id: number) => { setLikedItems((prev) => ({ ...prev, [id]: !prev[id], })); }; + + const convertToRecruitItem = (data: RecruitData): RecruitItem => { + return { + url: data.url || '', + active: data.active || 1, + title: data.title || '', + jobName: data.jobName || '', + companyName: data.companyName || '', + locationName: data.locationName || null, + jobTypeName: data.jobTypeName || '', + experienceLevel: data.experienceLevel || '', + requiredEducationLevel: data.requiredEducationLevel || '', + closeType: data.closeType || '', + salary: data.salary || '', + id: String(data.id), + postTimestamp: data.postTimestamp || '', + postDate: data.postDate || '', + 'expiration-timestamp': data['expiration-timestamp'] || '', + 'expiration-date': data['expiration-date'] || '', + deadline: data.deadline || '', + count: + typeof data.count === 'string' ? parseInt(data.count) : data.count || 0, + }; + }; + + const handleCardClick = (data: RecruitData) => { + console.log('Card clicked:', data); + const recruitItem = convertToRecruitItem(data); + console.log('Converted item:', recruitItem); + setSelectedCard(recruitItem); + setIsModalOpen(true); + console.log('Modal should open'); + }; + const isLoggedIn = localStorage.getItem('accessToken'); const navigate = useNavigate(); @@ -60,51 +120,54 @@ const HomeRecruit = () => {
{recruitData && recruitData.map((data) => ( - handleCardClick(data)} >
{ + e.stopPropagation(); + toggleLike(data.id); + }} > -
{ - e.preventDefault(); - e.stopPropagation(); - toggleLike(data.id); - }} - > - {likedItems[data.id] ? : } -
-
- {data.companyName} -
-
- {data.jobName} -
-
- {data.title} -
+ {likedItems[data.id] ? : } +
+
+ {data.companyName} +
+
+ {data.jobName} +
+
+ {data.title} +
-
))}
+ + {isModalOpen && selectedCard && ( +
+ { + console.log('Closing modal'); + setIsModalOpen(false); + }} + /> +
+ )} ); }; From 6a0aea24be6c298c75d80fc6faa68dc2a23a7ede Mon Sep 17 00:00:00 2001 From: Choi Yong Won Date: Sun, 7 Sep 2025 15:16:24 +0900 Subject: [PATCH 4/7] =?UTF-8?q?fix:=20[#330]=20=EC=B2=B4=ED=81=AC=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=97=90=EC=84=9C=20AddIcon=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20=EC=BB=A4=EB=AE=A4=EB=8B=88=ED=8B=B0=20?= =?UTF-8?q?=EC=99=BC=EC=AA=BD=20=EC=82=AC=EC=9D=B4=EB=93=9C=EB=B0=94?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EA=B8=B0=EB=B3=B8=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=B6=94=EA=B0=80,?= =?UTF-8?q?=20=ED=99=88=20=EB=93=9C=EB=A6=AC=EB=A8=B8=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=84=A4=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/CheckList.tsx | 7 +------ src/pages/community/components/CommunityLeftSide.tsx | 11 ++++++----- src/pages/home/components/HomeDreamer.tsx | 2 +- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/common/CheckList.tsx b/src/common/CheckList.tsx index 1ab3ac2..85bfa4c 100644 --- a/src/common/CheckList.tsx +++ b/src/common/CheckList.tsx @@ -10,7 +10,6 @@ import { useDeleteTodoMutation } from '@hook/todo/useDeleteTodoMutation'; import { ReactTagManager } from 'react-gtm-ts'; import { useAddTodoMutation } from '@hook/todo/useAddTodoMutation.ts'; import Plus from '@assets/icons/plus.svg?react'; -import AddIcon from '@assets/icons/AddIcon.svg?react'; import BookMarkIcon from '@assets/icons/bookmark.svg?react'; import { useUpdateTodoMutation } from '@hook/todo/useUpdateTodoMutation.ts'; @@ -84,7 +83,7 @@ const CheckList = ({ }); console.log('Amplitude event sent: todo_create_attempt (inpage)'); } - + mutate( { todoTitle: trimmedText }, { @@ -263,10 +262,6 @@ const CheckList = ({ ) : ( <> -
- - 3 -
999 diff --git a/src/pages/community/components/CommunityLeftSide.tsx b/src/pages/community/components/CommunityLeftSide.tsx index b3c479e..c41e11c 100644 --- a/src/pages/community/components/CommunityLeftSide.tsx +++ b/src/pages/community/components/CommunityLeftSide.tsx @@ -5,6 +5,7 @@ import { useGetHotPopularQuery } from '@hook/community/query/useGetHotPopularQue import Bookmark from '@assets/icons/bookmark.svg?react'; import Arrow from '@assets/icons/arrow.svg?react'; import jobNames, { findJobIdByName } from '@utils/data/community/jobs'; +import BaseImage from '@assets/images/profile.png'; const CommunityLeftSide = () => { const navigate = useNavigate(); @@ -27,7 +28,6 @@ const CommunityLeftSide = () => {
{ - // 하드코딩 매핑된 jobId 사용 const id = findJobIdByName(selectedJobName); navigate(`/others/${id ?? 1}`); }} @@ -46,23 +46,24 @@ const CommunityLeftSide = () => { {popularTodos.map((item, idx) => (
navigate(`/otherslist/${item.id}`)} >
{idx + 1}
프로필이미지
-
+
{item.description}
-
+
{item.dDay}
diff --git a/src/pages/home/components/HomeDreamer.tsx b/src/pages/home/components/HomeDreamer.tsx index 512d27d..17c92e7 100644 --- a/src/pages/home/components/HomeDreamer.tsx +++ b/src/pages/home/components/HomeDreamer.tsx @@ -40,7 +40,7 @@ const HomeDreamer = () => {
{title}
navigate('/jobfound')} + onClick={() => navigate('/community')} > 더 보러가기 From 7453db5586edf90d3c166c627ca72f39112b33ee Mon Sep 17 00:00:00 2001 From: Choi Yong Won Date: Sun, 7 Sep 2025 15:16:33 +0900 Subject: [PATCH 5/7] =?UTF-8?q?feat:=20[#330]=20=ED=95=A0=EC=9D=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=B7=A8=EC=86=8C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84,=20=ED=86=A0=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/OtherTodoCard.tsx | 134 +++++++++++++++--- 1 file changed, 112 insertions(+), 22 deletions(-) diff --git a/src/pages/otherTodoList/components/OtherTodoCard.tsx b/src/pages/otherTodoList/components/OtherTodoCard.tsx index b1e1a38..872e51a 100644 --- a/src/pages/otherTodoList/components/OtherTodoCard.tsx +++ b/src/pages/otherTodoList/components/OtherTodoCard.tsx @@ -3,13 +3,19 @@ import { useNavigate } from 'react-router-dom'; import { useState, useEffect } from 'react'; import LoadingSpinner from '@common/LoadingSpinner'; import { useMemoQuery } from '@hook/mydream/useGetMemo'; +import BookMarkIcon from '@assets/icons/bookmark.svg?react'; +import { useCommunityAddTodoMutation } from '@hook/community/useCommunityAddTodoMutation'; +import { useDeleteCommunityTodosMutation } from '@hook/community/useDeleteCommunityTodos'; +import ToastModal from '@common/modal/ToastModal'; +import Info from '@assets/icons/info.svg?react'; interface TodoItem { todoId: number; title: string; completed: boolean; - isPublic: boolean; + saveCount?: number; + isSaved?: boolean; } interface TodoCardProps { @@ -21,6 +27,47 @@ const OtherTodoCard = ({ todos }: TodoCardProps) => { const navigate = useNavigate(); const [isLoading, setIsLoading] = useState(false); const [selectedTodoId, setSelectedTodoId] = useState(null); + const [added, setAdded] = useState>({}); + const addTodoMutation = useCommunityAddTodoMutation(); + const deleteTodoMutation = useDeleteCommunityTodosMutation(); + const [showToast, setShowToast] = useState(false); + const [toastMessage, setToastMessage] = useState(''); + + const toggleAdd = (id: number, isAdded: boolean) => { + if (isAdded) { + deleteTodoMutation.mutate( + { id }, + { + onSuccess: () => { + setAdded((prev) => ({ ...prev, [id]: false })); + setToastMessage('할일이 취소되었습니다.'); + setShowToast(true); + setTimeout(() => setShowToast(false), 2500); + }, + onError: () => { + alert('추가 취소에 실패했어요.'); + }, + } + ); + } else { + addTodoMutation.mutate( + { id }, + { + onSuccess: () => { + setShowToast(true); + setAdded((prev) => ({ ...prev, [id]: true })); + setToastMessage('할일이 추가되었습니다.'); + setTimeout(() => { + setShowToast(false); + }, 2500); + }, + onError: () => { + alert('내 할일 추가에 실패했어요.'); + }, + } + ); + } + }; const { data, @@ -54,30 +101,73 @@ const OtherTodoCard = ({ todos }: TodoCardProps) => { )}
    - {todos.map((item, index) => ( -
  • -
    { + const isAdded = + typeof added[item.todoId] === 'boolean' + ? added[item.todoId] + : item.isSaved || false; + + return ( +
  • - {item.completed && } -
+
+
+ {item.completed && ( + + )} +
+ + + {item.title} + +
-
- - {item.title} - -
- - ))} +
+
+ + + {item.saveCount || 0} + +
+ + +
+ + ); + })} + + {showToast && ( +
+ } + text={toastMessage} + width="w-[274px]" + /> +
+ )}
); }; From 516a051a317eeb110d1e1955a6294a29430dae69 Mon Sep 17 00:00:00 2001 From: Choi Yong Won Date: Sun, 7 Sep 2025 16:32:27 +0900 Subject: [PATCH 6/7] =?UTF-8?q?feat:=20[#330]=20HotPopularItem=20=EC=9D=B8?= =?UTF-8?q?=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=EC=97=90=20todoGroupId=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20console.log=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hook/community/query/useGetHotPopularQuery.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hook/community/query/useGetHotPopularQuery.ts b/src/hook/community/query/useGetHotPopularQuery.ts index 3bd2f4b..f7fae86 100644 --- a/src/hook/community/query/useGetHotPopularQuery.ts +++ b/src/hook/community/query/useGetHotPopularQuery.ts @@ -11,6 +11,7 @@ export interface HotPopularItem { description: string; saveCount: number; isSaved: boolean; + todoGroupId: number; } interface HotPopularApiResponse { @@ -29,7 +30,6 @@ export const useGetHotPopularQuery = () => { const res = await api.get('/v1/community/todos/popular', { params: { jobName: selectedJobName }, }); - console.log(res.data); const body = res.data as HotPopularApiResponse; return Array.isArray(body?.data) ? body.data : []; }, From a5186aa1f98f894470d074fdfa98dbe55b1d2894 Mon Sep 17 00:00:00 2001 From: Choi Yong Won Date: Sun, 7 Sep 2025 16:48:29 +0900 Subject: [PATCH 7/7] =?UTF-8?q?feat:=20[#330]=20=ED=95=A0=EC=9D=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EC=8B=9C=20Amplitude=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=ED=8A=B8=EB=9E=98=ED=82=B9=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EA=B4=80=EB=A0=A8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/CommunityContents.tsx | 6 ++-- .../components/TabContent/SproutContent.tsx | 6 ++-- .../components/TabContent/TreeContents.tsx | 6 ++-- .../components/OtherTodoCard.tsx | 6 ++-- src/utils/amplitude.ts | 31 +++++++++++++++++++ 5 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 src/utils/amplitude.ts diff --git a/src/pages/community/components/CommunityContents.tsx b/src/pages/community/components/CommunityContents.tsx index 37bf882..70fce4a 100644 --- a/src/pages/community/components/CommunityContents.tsx +++ b/src/pages/community/components/CommunityContents.tsx @@ -5,6 +5,7 @@ import { useCommunityAddTodoMutation } from '@hook/community/useCommunityAddTodo import { useDeleteCommunityTodosMutation } from '@hook/community/useDeleteCommunityTodos'; import ToastModal from '@common/modal/ToastModal'; import Info from '@assets/icons/info.svg?react'; +import { trackTodoImport } from '@utils/amplitude'; type CommunityItem = { id: number; @@ -48,7 +49,7 @@ const CommunityContents = ({ return base; }, [filtered, sort]); - const toggleAdd = (id: number, isAdded: boolean) => { + const toggleAdd = (id: number, isAdded: boolean, todoTitle: string) => { if (isAdded) { deleteTodoMutation.mutate( { id }, @@ -65,6 +66,7 @@ const CommunityContents = ({ } ); } else { + trackTodoImport(todoTitle); // Amplitude 이벤트 트래킹 addTodoMutation.mutate( { id }, { @@ -131,7 +133,7 @@ const CommunityContents = ({