Skip to content

Commit

Permalink
refactor: 캘린더 컴포넌트 개선
Browse files Browse the repository at this point in the history
  • Loading branch information
paulcjy committed Sep 19, 2024
1 parent af71bd8 commit 07cb557
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 243 deletions.
16 changes: 15 additions & 1 deletion app/lovely/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
import { allBibles } from 'contentlayer/generated'
import { LovelyCalendar } from '@/components/LovelyCalendar'
import { BibleData, DayData } from '@/components/calendar/types'

export default function Home() {
const generateBibleData = () =>
allBibles.reduce((acc, bible) => {
const { date, year, month, day, title } = bible
const dailyBible: DayData = { day, title, url: `/${date}` }

acc[year] ??= {}
acc[year][month] ??= {}
acc[year][month][day] = dailyBible
return acc
}, {} as BibleData)

const bibleData = generateBibleData()
return (
<div className="h-screen">
<LovelyCalendar />
<LovelyCalendar bibleData={bibleData} />
</div>
)
}
17 changes: 16 additions & 1 deletion app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
import { allBibles } from 'contentlayer/generated'
import { Calendar } from '@/components/calendar/Calendar'
import { BibleData, DayData } from '@/components/calendar/types'

export default function Home() {
const generateBibleData = () =>
allBibles.reduce((acc, bible) => {
const { date, year, month, day, title } = bible
const dailyBible: DayData = { day, title, url: `/${date}` }

acc[year] ??= {}
acc[year][month] ??= {}
acc[year][month][day] = dailyBible
return acc
}, {} as BibleData)

const bibleData = generateBibleData()

return (
<div className="h-screen">
<Calendar />
<Calendar bibleData={bibleData} />
</div>
)
}
260 changes: 127 additions & 133 deletions components/LovelyCalendar.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
'use client'

import { useMemo, useState, useCallback } from 'react'
import Link from 'next/link'
import { useState, useEffect, useMemo } from 'react'
import { ChevronLeft, ChevronRight, Heart } from 'lucide-react'
import { allBibles } from 'contentlayer/generated'
import { Day, Month } from './calendar/types'

export const LovelyCalendar = () => {
const [currentDate, setCurrentDate] = useState(new Date())
const [calendar, setCalendar] = useState<Month>([])
import { BibleData, DayData } from './calendar/types'

export const LovelyCalendar = ({ bibleData }: { bibleData: BibleData }) => {
const [currentDate, setCurrentDate] = useState(() => new Date())
const today = useMemo(() => new Date(), [])

const header = ['일', '월', '화', '수', '목', '금', '토']
const monthNames = [
const CALENDAR_TITLE = '주내힘교회 청소년부'
const CALENDAR_SUBTITLE = '성경읽기표'
const WEEKDAYS = ['일', '월', '화', '수', '목', '금', '토']
const MONTH_NAMES = [
'1월',
'2월',
'3월',
Expand All @@ -28,75 +26,138 @@ export const LovelyCalendar = () => {
'12월',
]

useEffect(() => {
const updateCalendar = () => {
const year = currentDate.getFullYear()
const month = currentDate.getMonth()
const firstDay = new Date(year, month, 1).getDay()
const lastDay = new Date(year, month + 1, 0).getDate()

const monthlyBible = allBibles.filter((bible) => {
return bible.year === year && bible.month === month + 1
})

let calendar = [Array(7).fill(null)]
const createCalendar = useCallback(
(date: Date) => {
const getDayStyle = (date: Date, today: Date) => {
date.setHours(0, 0, 0, 0)
today.setHours(0, 0, 0, 0)

for (let date = 1; date <= lastDay; date++) {
const index = firstDay + date - 1
const day = index % 7
const week = Math.floor(index / 7)
const isPast = date < today
const isToday = date.getTime() === today.getTime()
const isSunday = date.getDay() === 0
const isSaturday = date.getDay() === 6

const currentDate = new Date(year, month, date)
const dailyBible = monthlyBible.find((bible) => bible.day === date)
const styles = [
'hover:shadow-md transition-shadow hover:border-pink-300',
]

const dayData = {
date: currentDate,
content: dailyBible
? {
url: `/${dailyBible.date}`,
title: dailyBible.title,
}
: null,
}
if (isPast) styles.push('opacity-50')
if (isToday) styles.push('bg-pink-100 border-pink-300')
if (isSunday) styles.push('text-red-400')
if (isSaturday) styles.push('text-blue-400')

if (!calendar[week]) calendar.push(Array(7).fill(null))
calendar[week][day] = dayData
return styles.join(' ')
}

setCalendar(calendar)
}
const year = date.getFullYear()
const month = date.getMonth()
const monthlyBible = bibleData[year]?.[month] ?? {}

updateCalendar()
}, [currentDate])
const firstDay = new Date(year, month, 1)
const firstDayOfWeek = firstDay.getDay()
const lastDay = new Date(year, month + 1, 0)
const totalDays = lastDay.getDate()

const getDateStyle = (day: Day) => {
if (!day) return ''
const totalCells = Math.ceil((firstDayOfWeek + totalDays) / 7) * 7

const date = day.date
date.setHours(0, 0, 0, 0)
today.setHours(0, 0, 0, 0)

const isPast = date < today
const isToday = date.getTime() === today.getTime()
const isSunday = date.getDay() === 0
const isSaturday = date.getDay() === 6
return Array.from(Array(totalCells), (_, i) => {
const day = i - firstDayOfWeek + 1
if (day < 1 || totalDays < day) return null
return {
style: getDayStyle(new Date(year, month, day), today),
...monthlyBible[day],
day,
}
})
},
[bibleData, today]
)

const styles = ['hover:shadow-md transition-shadow hover:border-pink-300']
const calendar = useMemo(
() => createCalendar(currentDate),
[currentDate, createCalendar]
)

if (isPast) styles.push('opacity-50')
if (isToday) styles.push('bg-pink-100 border-pink-300')
if (isSunday) styles.push('text-red-400')
if (isSaturday) styles.push('text-blue-400')
const changeMonth = useCallback((d: number) => {
setCurrentDate(
(prevDate) => new Date(prevDate.getFullYear(), prevDate.getMonth() + d, 1)
)
}, [])

const CalendarHeader = () => (
<div className="p-2 bg-pink-300 text-white">
<h1 className="text-xl md:text-3xl font-bold text-center">
{CALENDAR_TITLE}
</h1>
<h2 className="text-lg md:text-2xl text-center flex items-center justify-center">
{CALENDAR_SUBTITLE}
<Heart className="ml-2" size={24} />
</h2>
</div>
)

return styles.join(' ')
}
const CalendarNav = () => (
<div className="flex justify-between items-center p-2 bg-pink-200 text-pink-700">
<button
onClick={() => changeMonth(-1)}
className="p-2 hover:bg-pink-300 hover:text-white rounded-full transition-colors"
>
<ChevronLeft size={24} />
</button>
<h2 className="text-xl font-bold whitespace-pre">
{currentDate.getFullYear()}{' '.repeat(2)}
{MONTH_NAMES[currentDate.getMonth()]}
</h2>
<button
onClick={() => changeMonth(1)}
className="p-2 hover:bg-pink-300 hover:text-white rounded-full transition-colors"
>
<ChevronRight size={24} />
</button>
</div>
)

const changeMonth = (delta: number) => {
setCurrentDate(
new Date(currentDate.getFullYear(), currentDate.getMonth() + delta, 1)
const CalendarDay = ({ dayData }: { dayData: DayData }) => {
const { day, url = '#', title, style = '' } = dayData ?? {}

return (
<div className={`border-2 rounded-md p-1 h-24 md:h-32 ${style}`}>
{dayData && (
<Link href={url} className="h-full flex flex-col">
<span className="text-right font-semibold">{day}</span>
<div className="mt-auto pb-2 pl-0.5 text-xs md:text-sm">
<span className="font-medium whitespace-pre-line text-pink-600">
{title}
</span>
</div>
</Link>
)}
</div>
)
}

const CalendarGrid = () => (
<div className="p-1.5">
<div className="grid grid-cols-7 gap-0 mb-1">
{WEEKDAYS.map((day: string, i: number) => (
<div
key={i}
className={`text-center font-bold p-1 ${
i === 0 ? 'text-red-400' : ''
} ${i === 6 ? 'text-blue-400' : ''}`}
>
{day}
</div>
))}
</div>
<div className="grid grid-cols-7 gap-0">
{calendar.map((dayData, i: number) => (
<CalendarDay key={i} dayData={dayData} />
))}
</div>
</div>
)

return (
<>
<style jsx global>{`
Expand All @@ -108,76 +169,9 @@ export const LovelyCalendar = () => {
<div className="min-h-screen bg-gradient-to-b from-pink-100 to-white p-2 md:p-8">
<div className="max-w-4xl mx-auto bg-white rounded-3xl shadow-lg overflow-hidden border-4 border-pink-200 relative">
<div className="relative z-10">
<div className="p-2 bg-pink-300 text-white">
<h1 className="text-xl md:text-3xl font-bold text-center">
주내힘교회 청소년부♡
</h1>
<h2 className="text-lg md:text-2xl text-center flex items-center justify-center">
성경읽기표
</h2>
</div>

<div className="flex justify-between items-center p-2 bg-pink-200 text-pink-700">
<button
onClick={() => changeMonth(-1)}
className="p-2 hover:bg-pink-300 hover:text-white rounded-full transition-colors"
>
<ChevronLeft size={24} />
</button>
<h2 className="text-xl font-bold whitespace-pre">
{currentDate.getFullYear()}{' '.repeat(2)}
{monthNames[currentDate.getMonth()]}
</h2>
<button
onClick={() => changeMonth(1)}
className="p-2 hover:bg-pink-300 hover:text-white rounded-full transition-colors"
>
<ChevronRight size={24} />
</button>
</div>

<div className="p-1.5">
<div className="grid grid-cols-7 gap-0 mb-1">
{header.map((day, i) => (
<div
key={i}
className={`text-center font-bold p-1 ${
i === 0 ? 'text-red-400' : ''
} ${i === 6 ? 'text-blue-400' : ''}`}
>
{day}
</div>
))}
</div>

<div className="grid grid-cols-7 gap-0">
{calendar.flat().map((day, index) => (
<div
key={index}
className={`
border-2 rounded-md p-1 h-24 md:h-32
${day ? getDateStyle(day) : ''}
`}
>
{day && (
<Link
href={day.content?.url || '#'}
className="h-full flex flex-col"
>
<span className="text-right font-semibold">
{day.date.getDate()}
</span>
<div className="mt-auto pb-2 pl-0.5 text-xs md:text-sm">
<span className="font-medium whitespace-pre-line text-pink-600">
{day.content?.title || ''}
</span>
</div>
</Link>
)}
</div>
))}
</div>
</div>
<CalendarHeader />
<CalendarNav />
<CalendarGrid />
</div>
</div>
</div>
Expand Down
Loading

0 comments on commit 07cb557

Please sign in to comment.