Skip to content

Commit 3080ecd

Browse files
committed
feat: 다가오는 일정 표시
1 parent 5bddd53 commit 3080ecd

6 files changed

Lines changed: 154 additions & 9 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ const Page = () => {
7676

7777
{/* 2-3. BottomSection */}
7878
<BottomSection
79+
uid={currentUser.uid}
7980
className="grid grid-cols-1 gap-4 md:grid-cols-2"
8081
items={items}
8182
loading={planLoading}

components/home/BottomSection.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
'use client';
22

33
import TodayPlanContainer from './TodayPlanContainer';
4+
import UpcomingPlanContainer from '@/components/home/UpcomingPlanContainer';
45
import type { PlanItem } from '@/services/plans/planManageService.service';
6+
import { useTodayPlanItems } from '@/hooks/useTodayPlanItems';
7+
import { useUpcomingPlanItems } from '@/hooks/useUpcomingPlanItems';
58

69
interface BottomSectionProps {
10+
uid: string;
711
className?: string;
812
items: PlanItem[];
913
loading: boolean;
@@ -12,30 +16,33 @@ interface BottomSectionProps {
1216
}
1317

1418
export default function BottomSection({
19+
uid,
1520
className,
1621
items,
1722
loading,
1823
error,
1924
onToggle,
2025
}: BottomSectionProps) {
21-
26+
const today = useTodayPlanItems(uid);
27+
const upcoming = useUpcomingPlanItems(uid);
2228

2329
return (
2430
<div className={className}>
31+
{/* 오늘의 목표 */}
2532
<TodayPlanContainer
2633
items={items}
2734
loading={loading}
2835
error={error}
2936
onToggle={onToggle}
3037
></TodayPlanContainer>
3138

32-
{/* <Card title="다가오는 일정">
33-
<CheckList
34-
items={upcoming}
35-
onToggle={toggleUpcoming}
36-
emptyText="다가오는 일정이 없습니다"
37-
/>
38-
</Card> */}
39+
{/* 다가오는 일정 */}
40+
<UpcomingPlanContainer
41+
items={upcoming.items}
42+
loading={upcoming.loading}
43+
error={upcoming.error}
44+
limit={today.total}
45+
/>
3946
</div>
4047
);
4148
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
'use client';
2+
3+
import Card from '@/components/home/Card';
4+
import CheckList from '@/components/home/CheckList';
5+
import { PlanItem } from '@/services/plans/planManageService.service';
6+
7+
type Props = {
8+
items: PlanItem[];
9+
loading: boolean;
10+
error: string | null;
11+
limit: number;
12+
};
13+
14+
export default function UpcomingPlanContainer({
15+
items,
16+
loading,
17+
error,
18+
limit,
19+
}: Props) {
20+
if (loading) {
21+
return <div className="text-sm text-gray-400">불러오는 중...</div>;
22+
}
23+
24+
if (error) {
25+
return <div className="text-sm text-red-500">{error}</div>;
26+
}
27+
28+
const visible = items.slice(0, limit);
29+
const hiddenCount = Math.max(items.length - limit, 0);
30+
31+
return (
32+
<Card title="다가오는 일정">
33+
<CheckList
34+
items={visible}
35+
onToggleTodo={() => {}}
36+
emptyText="다가오는 일정이 없습니다"
37+
/>
38+
39+
{hiddenCount > 0 && (
40+
<p className="mt-2 text-xs text-gray-400">
41+
+{hiddenCount}개의 일정이 더 있습니다
42+
</p>
43+
)}
44+
</Card>
45+
);
46+
}

hooks/useTodayPlanItems.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ export const useTodayPlanItems = (uid?: string) => {
3636
[completed, total]
3737
);
3838

39-
// ✅ 토글만
4039
const toggle = useCallback(
4140
async (id: string, current: boolean) => {
4241
if (!uid) return;
@@ -71,5 +70,6 @@ export const useTodayPlanItems = (uid?: string) => {
7170
error,
7271
toggle,
7372
progressText,
73+
total,
7474
};
7575
};

hooks/useUpcomingPlanItems.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use client';
2+
3+
import { useCallback, useEffect, useMemo, useState } from 'react';
4+
import type { PlanItem } from '@/services/plans/planManageService.service';
5+
import { fetchUpcomingPlanItems } from '@/services/plans/planManageService.service';
6+
7+
const startOfTomorrowKST = () => {
8+
// KST 기준 "내일 00:00"
9+
const now = new Date();
10+
const kst = new Date(now.getTime() + 9 * 60 * 60 * 1000);
11+
12+
const y = kst.getUTCFullYear();
13+
const m = kst.getUTCMonth();
14+
const d = kst.getUTCDate();
15+
16+
// 내일 00:00 KST => UTC로 다시 변환(= -9h)
17+
const tomorrowKSTMidnightUTC = new Date(Date.UTC(y, m, d + 2, 0, 0, 0));
18+
const backToLocal = new Date(
19+
tomorrowKSTMidnightUTC.getTime() - 9 * 60 * 60 * 1000
20+
);
21+
22+
return backToLocal;
23+
};
24+
25+
export const useUpcomingPlanItems = (uid?: string) => {
26+
const [items, setItems] = useState<PlanItem[]>([]);
27+
const [loading, setLoading] = useState(false);
28+
const [error, setError] = useState<string | null>(null);
29+
30+
const fromDate = useMemo(() => startOfTomorrowKST(), []);
31+
32+
const loadUpcoming = useCallback(async () => {
33+
if (!uid) return;
34+
try {
35+
setLoading(true);
36+
setError(null);
37+
const data = await fetchUpcomingPlanItems(uid, fromDate);
38+
setItems(data);
39+
} catch (e) {
40+
console.error(e);
41+
setError('다가오는 일정을 불러오는 데 실패했습니다.');
42+
} finally {
43+
setLoading(false);
44+
}
45+
}, [uid, fromDate]);
46+
47+
useEffect(() => {
48+
if (!uid) return;
49+
loadUpcoming();
50+
}, [uid, loadUpcoming]);
51+
52+
return {
53+
items,
54+
loading,
55+
error,
56+
loadUpcoming,
57+
};
58+
};

services/plans/planManageService.service.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ export const updatePlanItem = async (
187187
}
188188
};
189189

190+
// 오늘의 할일 가져오기
190191
export const fetchTodayPlanItems = async (uid: string): Promise<PlanItem[]> => {
191192
const itemsRef = collection(db, 'users', uid, 'planItems');
192193

@@ -217,3 +218,35 @@ export const fetchTodayPlanItems = async (uid: string): Promise<PlanItem[]> => {
217218
};
218219
}) as PlanItem[];
219220
};
221+
222+
// 다가오는 일정 가져오기
223+
export const fetchUpcomingPlanItems = async (
224+
uid: string,
225+
fromDate: Date
226+
): Promise<PlanItem[]> => {
227+
const itemsRef = collection(db, 'users', uid, 'planItems');
228+
229+
const q = query(
230+
itemsRef,
231+
where('deadline', '>=', Timestamp.fromDate(fromDate))
232+
);
233+
234+
const snapshot = await getDocs(q);
235+
236+
const items = snapshot.docs.map((d) => {
237+
const data = d.data();
238+
return {
239+
id: d.id,
240+
...data,
241+
createdAt: data.createdAt?.toDate?.() ?? new Date(),
242+
deadline: data.deadline?.toDate?.() ?? new Date(),
243+
} as PlanItem;
244+
});
245+
246+
//정렬
247+
items.sort(
248+
(a, b) => +a.deadline - +b.deadline || +a.createdAt - +b.createdAt
249+
);
250+
251+
return items;
252+
};

0 commit comments

Comments
 (0)