Skip to content

Commit b7b04c2

Browse files
committed
feat: 계획 관리 페이지 상단 카드에 보여지는 전체 플랜 수, 진행 중인 플랜 수, 완료된 플랜 수를 보여지도록 메서드 구현 및 UI 적용
1 parent dccc071 commit b7b04c2

3 files changed

Lines changed: 94 additions & 4 deletions

File tree

app/(with-sidebar)/plans/page.tsx

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import {
77
addPlan,
88
deletePlan,
99
deletePlanItem,
10+
fetchAllPlanItems,
1011
fetchPlans,
1112
Plan,
13+
PlanItem,
1214
updatePlan,
1315
} from '@/services/plans/planManageService.service';
1416

@@ -30,17 +32,72 @@ const Page = () => {
3032
const [currentPage, setCurrentPage] = useState(1);
3133
const itemsPerPage = 4;
3234

35+
const [stats, setStats] = useState({
36+
total: 0,
37+
working: 0, // 진행중
38+
completed: 0, // 완료됨
39+
});
40+
41+
// 카드 업데이트를 위한 stats 가져오기 메서드
42+
const fetchAndCalculate = async (uid: string) => {
43+
try {
44+
// 1. 데이터 병렬 로드
45+
const [fetchedPlans, fetchedItems] = await Promise.all([
46+
fetchPlans(uid),
47+
fetchAllPlanItems(uid),
48+
]);
49+
50+
setPlans(fetchedPlans); // 플랜 목록 업데이트
51+
52+
// 2. 통계 계산 로직
53+
let completedCount = 0;
54+
let workingCount = 0;
55+
56+
fetchedPlans.forEach((plan) => {
57+
const myItems = fetchedItems.filter(
58+
(item: PlanItem) => item.planId === plan.id
59+
);
60+
// ⚠️ 하위 항목이 하나라도 있는 경우에만 상태를 판별합니다.
61+
if (myItems.length > 0) {
62+
const isAllChecked = myItems.every(
63+
(item: PlanItem) => item.isChecked
64+
);
65+
66+
if (isAllChecked) {
67+
completedCount++; // 모두 완료됨
68+
} else {
69+
workingCount++; // 항목은 있는데 아직 다 완료 안 됨 -> 진행중
70+
}
71+
}
72+
});
73+
74+
// 3. 통계 State 업데이트
75+
setStats({
76+
total: fetchedPlans.length,
77+
completed: completedCount,
78+
working: workingCount,
79+
});
80+
} catch (err) {
81+
console.error(err);
82+
}
83+
};
84+
3385
// 사용자 인증 상태 리스너 및 초기 플랜 목록 로드
3486
useEffect(() => {
3587
const unsubscribe = onAuthStateChanged(auth, async (currentUser) => {
3688
setUser(currentUser);
3789

3890
if (currentUser) {
3991
try {
40-
const fetchedPlans = await fetchPlans(currentUser.uid);
41-
setPlans(fetchedPlans);
92+
const [fetchedPlans, fetchedItems] = await Promise.all([
93+
fetchPlans(currentUser.uid),
94+
fetchAllPlanItems(currentUser.uid),
95+
]);
4296

97+
setPlans(fetchedPlans);
4398
setCurrentPage(1);
99+
100+
await fetchAndCalculate(currentUser.uid);
44101
} catch (err) {
45102
console.error('플랜 목록 로딩 실패:', err);
46103
setPlans([]);
@@ -69,6 +126,8 @@ const Page = () => {
69126
setPlans(fetchedPlans);
70127
setCurrentPage(1);
71128

129+
await fetchAndCalculate(user.uid);
130+
72131
setIsAdding(false); // 폼 닫기
73132
} catch (err) {
74133
console.error('플랜 추가 실패:', err);
@@ -99,6 +158,8 @@ const Page = () => {
99158
const fetchedPlans = await fetchPlans(user.uid);
100159
setPlans(fetchedPlans);
101160

161+
await fetchAndCalculate(user.uid);
162+
102163
// 페이지 조절
103164
if (
104165
currentPage > Math.ceil((plans.length - 1) / itemsPerPage) &&
@@ -136,6 +197,7 @@ const Page = () => {
136197
}
137198
};
138199

200+
// 페이지네이션 파트
139201
const totalPages = Math.ceil(plans.length / itemsPerPage);
140202
const startIndex = (currentPage - 1) * itemsPerPage;
141203
const endIndex = startIndex + itemsPerPage;
@@ -181,7 +243,9 @@ const Page = () => {
181243
<span className="text-text-sub text-sm font-medium">
182244
진행중인 플랜 수
183245
</span>
184-
<span className="text-4xl font-bold text-[#7B44C4]">1</span>
246+
<span className="text-4xl font-bold text-[#7B44C4]">
247+
{stats.working}
248+
</span>
185249
</div>
186250

187251
{/* 오른쪽: 아이콘 영역 */}
@@ -195,7 +259,9 @@ const Page = () => {
195259
<span className="text-text-sub text-sm font-medium">
196260
완료된 플랜 수
197261
</span>
198-
<span className="text-4xl font-bold text-[#00841F]">1</span>
262+
<span className="text-4xl font-bold text-[#00841F]">
263+
{stats.completed}
264+
</span>
199265
</div>
200266

201267
{/* 오른쪽: 아이콘 영역 */}
@@ -237,6 +303,7 @@ const Page = () => {
237303
description={plan.description}
238304
onDeletePlan={handleDeletePlan}
239305
onUpdatePlan={handleUpdatePlan}
306+
onChangeStats={() => fetchAndCalculate(user!.uid)}
240307
/>
241308
))
242309
) : (

components/plans/PlanSection.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ interface PlanSectionProps {
3333
newTitle: string,
3434
newDescription: string
3535
) => void;
36+
onChangeStats: () => void;
3637
}
3738

3839
export default function PlanSection({
@@ -42,6 +43,7 @@ export default function PlanSection({
4243
description,
4344
onDeletePlan,
4445
onUpdatePlan,
46+
onChangeStats,
4547
}: PlanSectionProps) {
4648
const [isOpen, setIsOpen] = useState(false);
4749
const [tasks, setTasks] = useState<PlanItem[]>([]);
@@ -111,6 +113,8 @@ export default function PlanSection({
111113
// 데이터 최신화: DB 업데이트 후 목록을 다시 불러옵니다.
112114
const updatedTasks = await fetchPlanItems(userId, planId);
113115
setTasks(updatedTasks);
116+
117+
onChangeStats();
114118
} catch (error) {
115119
console.error('상태 변경 실패: ', error);
116120
// 에러 시 원래대로 돌리거나 다시 불러오기
@@ -142,6 +146,8 @@ export default function PlanSection({
142146
onDeletePlan(planId, title);
143147

144148
setShowPlanMenu(false);
149+
150+
onChangeStats();
145151
};
146152

147153
// 5. 하위 항목 삭제 핸들러
@@ -157,6 +163,8 @@ export default function PlanSection({
157163
console.error(err);
158164
}
159165
}
166+
167+
onChangeStats();
160168
};
161169

162170
// 6. 플랜 수정 핸들러
@@ -170,6 +178,7 @@ export default function PlanSection({
170178
setShowPlanMenu(false); // 메뉴 닫기
171179
};
172180

181+
// 플랜 수정 핸들러
173182
const handleSaveEdit = (e: React.MouseEvent) => {
174183
e.stopPropagation();
175184

services/plans/planManageService.service.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,3 +211,17 @@ export const updatePlanItem = async (
211211
await updateDoc(itemRef, updatePayload);
212212
}
213213
};
214+
215+
// 10. 하위 항목 모두 가져오기
216+
export const fetchAllPlanItems = async (uid: string): Promise<PlanItem[]> => {
217+
const allPlanItemsRef = collection(db, 'users', uid, 'planItems');
218+
const q = query(allPlanItemsRef);
219+
const snapshot = await getDocs(q);
220+
221+
return snapshot.docs.map((doc) => ({
222+
id: doc.id,
223+
planId: doc.data().planId,
224+
isChecked: doc.data().isChecked,
225+
...doc.data(),
226+
})) as PlanItem[];
227+
};

0 commit comments

Comments
 (0)