diff --git a/src/components/common/Tab/FolderTab.tsx b/src/components/common/Tab/FolderTab.tsx index 9c2754b..779ba5a 100644 --- a/src/components/common/Tab/FolderTab.tsx +++ b/src/components/common/Tab/FolderTab.tsx @@ -1,23 +1,37 @@ import React from 'react'; +import BaseBadge from '../Tag/BaseBadge'; type FolderTabProps = { - children: React.ReactNode; + TabTitle: React.ReactNode; + TabAlarm?: number; isActive?: boolean; className?: string; onClick?: () => void; }; -const FolderTab = ({ children, isActive = false, className="", onClick }: FolderTabProps) => { +const FolderTab = ({ + TabTitle, + TabAlarm, + isActive = false, + className = '', + onClick, +}: FolderTabProps) => { + const badge = + TabAlarm && TabAlarm > 0 ? ( + {TabAlarm > 99 ? '99+' : TabAlarm} + ) : null; + return (
- {children} +
{TabTitle}
+ {badge}
); }; diff --git a/src/components/common/Tab/FolderTabGroup.tsx b/src/components/common/Tab/FolderTabGroup.tsx index d11ca42..da60e9f 100644 --- a/src/components/common/Tab/FolderTabGroup.tsx +++ b/src/components/common/Tab/FolderTabGroup.tsx @@ -4,6 +4,7 @@ import FolderTab from "./FolderTab"; interface TabItem { id: string | number; label: React.ReactNode; + alarm?: number; className?: string; } @@ -32,9 +33,9 @@ export const FolderTabGroup: React.FC = ({ isActive={idx === activeIndex} onClick={() => handleClick(idx)} className={`flex-1 basis-0 cursor-pointer ${tab.className ?? ""}`} - > - {tab.label} - + TabTitle={tab.label} + TabAlarm={tab.alarm} + /> ))} ); diff --git a/src/constants/ProfilePostColumns.tsx b/src/constants/ProfilePostColumns.tsx index 041b909..828806d 100644 --- a/src/constants/ProfilePostColumns.tsx +++ b/src/constants/ProfilePostColumns.tsx @@ -14,7 +14,6 @@ import EditIc from '../assets/icons/profile/ic_edit.svg?react'; import TrashIc from '../assets/icons/profile/ic_trashcan.svg?react'; import BaseTag from '../components/common/Tag/BaseTag'; import ArrowIc from '../assets/icons/ic_arrow_down_large.svg?react'; -import BaseBadge from '../components/common/Tag/BaseBadge.tsx'; export type PostRow = { id: string; diff --git a/src/hooks/useApplicants.ts b/src/hooks/useApplicants.ts index 5a3c8a3..c9cff5e 100644 --- a/src/hooks/useApplicants.ts +++ b/src/hooks/useApplicants.ts @@ -4,12 +4,15 @@ import { approveApplicant, rejectApplicant, UserProfile, + getMyApply, + postApply, } from '../api/applicants'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; export function useApplicants(projectId?: number) { const [list, setList] = useState([]); const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); + const [error, setError] = useState(null); const fetch = useCallback(async () => { if (projectId == null) return; @@ -25,29 +28,63 @@ export function useApplicants(projectId?: number) { } }, [projectId]); - useEffect(() => { fetch(); }, [fetch]); + useEffect(() => { + fetch(); + }, [fetch]); - const approve = useCallback(async (userId: string) => { - if (projectId == null) return; - setList(prev => prev.filter(u => u.id !== userId)); - try { - await approveApplicant(projectId, userId); - } finally { - fetch(); - } - }, [projectId, fetch]); + const approve = useCallback( + async (userId: string) => { + if (projectId == null) return; + setList((prev) => prev.filter((u) => u.id !== userId)); + try { + await approveApplicant(projectId, userId); + } finally { + fetch(); + } + }, + [projectId, fetch], + ); - const reject = useCallback(async (userId: string) => { - if (projectId == null) return; - setList(prev => prev.filter(u => u.id !== userId)); - try { - await rejectApplicant(projectId, userId); - } finally { - fetch(); - } - }, [projectId, fetch]); + const reject = useCallback( + async (userId: string) => { + if (projectId == null) return; + setList((prev) => prev.filter((u) => u.id !== userId)); + try { + await rejectApplicant(projectId, userId); + } finally { + fetch(); + } + }, + [projectId, fetch], + ); return { applicants: list, loading, error, refetch: fetch, approve, reject }; } export default useApplicants; + +// 프로젝트 지원 + +export const usePostApply = (projectId: number) => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (position: string) => postApply(projectId, position), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['postApply', projectId] }); + }, + onError: (error) => { + console.error('Error Apply:', error); + }, + }); +}; + +// 내가 지원한 프로젝트 조회 + +export const useGetMyApply = (status?: string) => { + return useQuery({ + queryKey: ['getMyApply', status], + queryFn: () => getMyApply(status), + staleTime: 1000 * 60 * 5, // 5분 동안 캐시 유지 + refetchOnWindowFocus: false, // 윈도우 포커스 시 재요청하지 않음 + }); +}; diff --git a/src/pages/profile/ProfilePosts.tsx b/src/pages/profile/ProfilePosts.tsx index 9839829..b476b25 100644 --- a/src/pages/profile/ProfilePosts.tsx +++ b/src/pages/profile/ProfilePosts.tsx @@ -127,7 +127,8 @@ export default function ProfilePosts() { [data, sortKey, searchText], ); - const [baselines, setBaselines] = useState>(loadBaselines()); + const [baselines, setBaselines] = + useState>(loadBaselines()); const [totals, setTotals] = useState>({ postManagement: 0, applicantManagement: 0, @@ -143,7 +144,9 @@ export default function ProfilePosts() { useEffect(() => { let alive = true; (async () => { - const ids = rows.map((r) => Number(r.id)).filter((n) => Number.isFinite(n)); + const ids = rows + .map((r) => Number(r.id)) + .filter((n) => Number.isFinite(n)); if (ids.length === 0) { if (!alive) return; setTotals((prev) => ({ ...prev, memberManagement: 0 })); @@ -177,9 +180,18 @@ export default function ProfilePosts() { }, [postsTotal, applicantsTotal]); const badgeCounts = { - postManagement: Math.max(0, totals.postManagement - baselines.postManagement), - applicantManagement: Math.max(0, totals.applicantManagement - baselines.applicantManagement), - memberManagement: Math.max(0, totals.memberManagement - baselines.memberManagement), + postManagement: Math.max( + 0, + totals.postManagement - baselines.postManagement, + ), + applicantManagement: Math.max( + 0, + totals.applicantManagement - baselines.applicantManagement, + ), + memberManagement: Math.max( + 0, + totals.memberManagement - baselines.memberManagement, + ), }; useEffect(() => { @@ -193,18 +205,10 @@ export default function ProfilePosts() { const tabsWithBadges = useMemo(() => { return profilePostTabs.map((t) => { const key = t.key as TabKey; - const n = badgeCounts[key]; - const badge = - n > 0 ? {n > 99 ? '99+' : n} : null; - return { ...t, - label: ( - - {t.label} - {badge} - - ), + label: t.label, + alarm: badgeCounts[key], }; }); }, [badgeCounts]); @@ -237,7 +241,8 @@ export default function ProfilePosts() { ({ id: t.key, - label: t.label, + label: <>{t.label}, + alarm: badgeCounts[t.key], }))} activeIndex={tabsWithBadges.findIndex((t) => t.key === activeKey)} onTabChange={(idx) => setActiveKey(tabsWithBadges[idx].key as TabKey)}