Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions src/components/common/Tab/FolderTab.tsx
Original file line number Diff line number Diff line change
@@ -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 ? (
<BaseBadge size="lg">{TabAlarm > 99 ? '99+' : TabAlarm}</BaseBadge>
) : null;

return (
<div
className={`inline-flex h-[7rem] min-w-[10.6rem] flex-shrink-0 flex-wrap content-center items-center justify-center gap-[0.6rem] px-[3.2rem] py-0 text-body-16_M500 hover:text-black-130 ${
isActive
? 'rounded-tl-[12px] bg-black-10 rounded-tr-[12px] border border-solid border-l-black-50 border-r-black-50 border-t-black-50 border-b-black-10 text-black-130'
: ' text-black-70'
? 'rounded-tl-[12px] rounded-tr-[12px] border border-solid border-b-black-10 border-l-black-50 border-r-black-50 border-t-black-50 bg-black-10 text-black-130'
: 'text-black-70'
} ${className}`}
onClick={onClick}
>
{children}
<div>{TabTitle}</div>
{badge}
</div>
);
};
Expand Down
7 changes: 4 additions & 3 deletions src/components/common/Tab/FolderTabGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import FolderTab from "./FolderTab";
interface TabItem {
id: string | number;
label: React.ReactNode;
alarm?: number;
className?: string;
}

Expand Down Expand Up @@ -32,9 +33,9 @@ export const FolderTabGroup: React.FC<FolderTabGroupProps> = ({
isActive={idx === activeIndex}
onClick={() => handleClick(idx)}
className={`flex-1 basis-0 cursor-pointer ${tab.className ?? ""}`}
>
{tab.label}
</FolderTab>
TabTitle={tab.label}
TabAlarm={tab.alarm}
/>
))}
</div>
);
Expand Down
1 change: 0 additions & 1 deletion src/constants/ProfilePostColumns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
77 changes: 57 additions & 20 deletions src/hooks/useApplicants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
approveApplicant,
rejectApplicant,
UserProfile,
getMyApply,

Check failure on line 7 in src/hooks/useApplicants.ts

View workflow job for this annotation

GitHub Actions / build

Module '"../api/applicants"' has no exported member 'getMyApply'.
postApply,

Check failure on line 8 in src/hooks/useApplicants.ts

View workflow job for this annotation

GitHub Actions / build

Module '"../api/applicants"' has no exported member 'postApply'.
} from '../api/applicants';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';

export function useApplicants(projectId?: number) {
const [list, setList] = useState<UserProfile[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);

const fetch = useCallback(async () => {
if (projectId == null) return;
Expand All @@ -25,29 +28,63 @@
}
}, [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, // 윈도우 포커스 시 재요청하지 않음
});
};
37 changes: 21 additions & 16 deletions src/pages/profile/ProfilePosts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ export default function ProfilePosts() {
[data, sortKey, searchText],
);

const [baselines, setBaselines] = useState<Record<TabKey, number>>(loadBaselines());
const [baselines, setBaselines] =
useState<Record<TabKey, number>>(loadBaselines());
const [totals, setTotals] = useState<Record<TabKey, number>>({
postManagement: 0,
applicantManagement: 0,
Expand All @@ -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 }));
Expand Down Expand Up @@ -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(() => {
Expand All @@ -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 ? <BaseBadge size="lg">{n > 99 ? '99+' : n}</BaseBadge> : null;

return {
...t,
label: (
<span className="flex items-center gap-[0.6rem]">
{t.label}
{badge}
</span>
),
label: t.label,
alarm: badgeCounts[key],
};
});
}, [badgeCounts]);
Expand Down Expand Up @@ -237,7 +241,8 @@ export default function ProfilePosts() {
<FolderTabGroup
tabs={tabsWithBadges.map((t) => ({
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)}
Expand Down
Loading