diff --git a/src/contexts/budget/hooks/useBudgetDataEvents.ts b/src/contexts/budget/hooks/useBudgetDataEvents.ts index c5d9b17..d2b7657 100644 --- a/src/contexts/budget/hooks/useBudgetDataEvents.ts +++ b/src/contexts/budget/hooks/useBudgetDataEvents.ts @@ -1,72 +1,121 @@ import { useEffect } from 'react'; -import { BudgetData, Transaction } from '../types'; -import { calculateSpentAmounts, safelyLoadBudgetData } from '../budgetUtils'; -import { saveBudgetDataToStorage } from '../storage'; +import { Transaction, BudgetData } from '../types'; +import { calculateSpentAmounts } from '../utils/spendingCalculation'; -/** - * 예산 데이터 관련 이벤트를 처리하는 훅 - */ export const useBudgetDataEvents = ( isInitialized: boolean, transactions: Transaction[], - setBudgetData: (data: BudgetData) => void, - setLastUpdateTime: (time: number) => void + setBudgetData: React.Dispatch>, + setLastUpdateTime: React.Dispatch> ) => { - // budgetDataUpdated 이벤트 리스너 설정 + // 트랜잭션 데이터가 변경될 때마다 예산 데이터 업데이트 useEffect(() => { - const handleBudgetUpdate = () => { - console.log('예산 데이터 업데이트 이벤트 감지'); + if (!isInitialized || transactions.length === 0) return; + + console.log('트랜잭션 변경 감지, 예산 데이터 업데이트 중...', transactions.length); + + setBudgetData(prevBudgetData => { + // 현재 지출 금액 계산 + const updatedBudgetData = calculateSpentAmounts(transactions, prevBudgetData); - // 현재 예산 데이터 로드하여 상태 업데이트 - const loadedData = safelyLoadBudgetData(); - setBudgetData(loadedData); + // 마지막 업데이트 시간 기록 setLastUpdateTime(Date.now()); - }; - - window.addEventListener('budgetDataUpdated', handleBudgetUpdate); - - // 스토리지 이벤트도 함께 처리 (다른 탭/창에서의 업데이트 감지) - const storageUpdateHandler = (e: StorageEvent) => { - if (e.key === 'budgetData' || e.key === null) { - console.log('스토리지 이벤트 감지 - 예산 데이터 업데이트'); - handleBudgetUpdate(); - } - }; - window.addEventListener('storage', storageUpdateHandler); - - // 이벤트 리스너 정리 - return () => { - window.removeEventListener('budgetDataUpdated', handleBudgetUpdate); - window.removeEventListener('storage', storageUpdateHandler); - }; - }, [isInitialized]); + + console.log('예산 데이터 업데이트 완료:', updatedBudgetData); + return updatedBudgetData; + }); + }, [isInitialized, transactions, setBudgetData, setLastUpdateTime]); - // 트랜잭션 변경 시 지출 금액 업데이트 + // 트랜잭션 변경 감지 이벤트 useEffect(() => { - if (transactions.length > 0 && isInitialized) { - console.log('트랜잭션 변경으로 인한 예산 데이터 업데이트. 트랜잭션 수:', transactions.length); - try { - // 현재 예산 데이터 다시 로드 (최신 상태 확보) - const currentBudgetData = safelyLoadBudgetData(); + if (!isInitialized) return; + + const handleTransactionChanged = (event: CustomEvent) => { + console.log('트랜잭션 변경 이벤트 감지', event.detail); + + setBudgetData(prevBudgetData => { + // 현재 지출 금액 계산 + const updatedBudgetData = calculateSpentAmounts(transactions, prevBudgetData); - // 지출 금액 업데이트 - const updatedBudgetData = calculateSpentAmounts(transactions, currentBudgetData); + // 마지막 업데이트 시간 기록 + setLastUpdateTime(Date.now()); - // 변경이 있을 때만 저장 - if (JSON.stringify(updatedBudgetData) !== JSON.stringify(currentBudgetData)) { - console.log('예산 지출액 업데이트 감지 - 데이터 저장'); - // 상태 및 스토리지 모두 업데이트 - setBudgetData(updatedBudgetData); - saveBudgetDataToStorage(updatedBudgetData); - setLastUpdateTime(Date.now()); - - // 기타 컴포넌트에 이벤트 알림 - window.dispatchEvent(new Event('budgetDataUpdated')); + return updatedBudgetData; + }); + }; + + // 이벤트 리스너 등록 + window.addEventListener('transactionChanged', handleTransactionChanged as EventListener); + + return () => { + // 이벤트 리스너 제거 + window.removeEventListener('transactionChanged', handleTransactionChanged as EventListener); + }; + }, [isInitialized, transactions, setBudgetData, setLastUpdateTime]); + + // 스토리지 이벤트 리스너 (다른 탭에서도 데이터 동기화) + useEffect(() => { + if (!isInitialized) return; + + const handleStorageChange = (event: StorageEvent) => { + if (event.key === 'transactions') { + console.log('스토리지 이벤트 감지: 트랜잭션 데이터 변경'); + + try { + // 새 트랜잭션 데이터로 예산 계산 + if (event.newValue) { + const newTransactions = JSON.parse(event.newValue); + + setBudgetData(prevBudgetData => { + // 현재 지출 금액 계산 + const updatedBudgetData = calculateSpentAmounts(newTransactions, prevBudgetData); + + // 마지막 업데이트 시간 기록 + setLastUpdateTime(Date.now()); + + return updatedBudgetData; + }); + } + } catch (error) { + console.error('스토리지 이벤트 처리 중 오류:', error); } - } catch (error) { - console.error('예산 데이터 업데이트 중 오류:', error); } - } - }, [transactions, isInitialized]); + }; + + // 이벤트 리스너 등록 + window.addEventListener('storage', handleStorageChange); + + return () => { + // 이벤트 리스너 제거 + window.removeEventListener('storage', handleStorageChange); + }; + }, [isInitialized, setBudgetData, setLastUpdateTime]); + + // 예산 데이터 수동 새로고침 이벤트 + useEffect(() => { + if (!isInitialized) return; + + const handleBudgetRefresh = () => { + console.log('예산 데이터 수동 새로고침 이벤트 감지'); + + setBudgetData(prevBudgetData => { + // 현재 지출 금액 계산 + const updatedBudgetData = calculateSpentAmounts(transactions, prevBudgetData); + + // 마지막 업데이트 시간 기록 + setLastUpdateTime(Date.now()); + + return updatedBudgetData; + }); + }; + + // 수동 새로고침 이벤트 등록 + window.addEventListener('budgetDataRefresh', handleBudgetRefresh); + + return () => { + // 이벤트 리스너 제거 + window.removeEventListener('budgetDataRefresh', handleBudgetRefresh); + }; + }, [isInitialized, transactions, setBudgetData, setLastUpdateTime]); }; diff --git a/src/contexts/budget/utils/spendingCalculation.ts b/src/contexts/budget/utils/spendingCalculation.ts index 7f9e446..794cb66 100644 --- a/src/contexts/budget/utils/spendingCalculation.ts +++ b/src/contexts/budget/utils/spendingCalculation.ts @@ -1,5 +1,6 @@ import { BudgetData, Transaction } from '../types'; +import { format, isWithinInterval, subDays, startOfMonth, endOfMonth, parseISO } from 'date-fns'; // 지출액 계산 (일일, 주간, 월간) export const calculateSpentAmounts = ( @@ -10,62 +11,145 @@ export const calculateSpentAmounts = ( // 지출 거래 필터링 const expenseTransactions = transactions.filter(t => t.type === 'expense'); + console.log("필터링된 지출 트랜잭션:", expenseTransactions.length); - // 오늘 지출 계산 - const todayExpenses = expenseTransactions.filter(t => { - if (t.date && (t.date.includes('오늘') || t.date.includes('today'))) return true; - return false; + // 현재 날짜 정보 + const now = new Date(); + const today = format(now, 'yyyy-MM-dd'); + const currentMonth = format(now, 'yyyy-MM'); + const startOfCurrentMonth = startOfMonth(now); + const endOfCurrentMonth = endOfMonth(now); + + // 각 지출 트랜잭션의 날짜 정보 로깅 + expenseTransactions.forEach((t, index) => { + if (index < 10) { // 처음 10개만 로그 출력 (너무 많은 로그 방지) + console.log(`트랜잭션 ${index}: 날짜=${t.date}, 금액=${t.amount}, 카테고리=${t.category}`); + } }); - const dailySpent = todayExpenses.reduce((sum, t) => sum + t.amount, 0); - - // 이번 주 지출 계산 (단순화된 버전) - const weeklyExpenses = expenseTransactions.filter(t => { - if (t.date && ( - t.date.includes('오늘') || - t.date.includes('today') || - t.date.includes('어제') || - t.date.includes('yesterday') || - t.date.includes('이번주') || - t.date.includes('this week') - )) return true; - return false; - }); - const weeklySpent = weeklyExpenses.reduce((sum, t) => sum + t.amount, 0); - - // 이번 달 총 지출 계산 (모든 지출 트랜잭션) - const monthlySpent = expenseTransactions.reduce((sum, t) => sum + t.amount, 0); - // 기존 예산 목표 유지 (없으면 기본값 0) + // 날짜 문자열을 Date 객체로 변환하는 함수 + const parseTransactionDate = (dateStr: string): Date | null => { + try { + if (dateStr.includes('오늘')) { + return new Date(); + } + + if (dateStr.includes('어제')) { + return subDays(new Date(), 1); + } + + // "yyyy-MM-dd" 형식 확인 + if (/^\d{4}-\d{2}-\d{2}/.test(dateStr)) { + return parseISO(dateStr); + } + + // "MM월 dd일" 형식 확인 (한국어 날짜) + const koreanDateMatch = dateStr.match(/(\d+)월\s*(\d+)일/); + if (koreanDateMatch) { + const month = parseInt(koreanDateMatch[1]) - 1; // 0-based month + const day = parseInt(koreanDateMatch[2]); + const year = now.getFullYear(); + return new Date(year, month, day); + } + + // 기타 날짜 문자열은 그대로 Date 생성자에 전달 + return new Date(dateStr); + } catch (e) { + console.error('날짜 파싱 실패:', dateStr, e); + return null; + } + }; + + // 월간 지출 계산 (현재 달의 모든 지출) + const monthlyExpenses = expenseTransactions.filter(t => { + try { + const transactionDate = parseTransactionDate(t.date); + if (!transactionDate) return false; + + return isWithinInterval(transactionDate, { + start: startOfCurrentMonth, + end: endOfCurrentMonth + }); + } catch (e) { + console.error('월간 지출 필터링 오류:', e); + // 날짜 형식이 명확하지 않은 경우, 트랜잭션 날짜 문자열에 현재 월 문자열이 포함되어 있는지 확인 + return t.date.includes(currentMonth) || + t.date.includes(format(now, 'M월')) || + t.date.includes('이번 달'); + } + }); + + // 주간 지출 계산 (최근 7일) + const weeklyExpenses = expenseTransactions.filter(t => { + try { + const transactionDate = parseTransactionDate(t.date); + if (!transactionDate) return false; + + return isWithinInterval(transactionDate, { + start: subDays(now, 7), + end: now + }); + } catch (e) { + // 날짜 형식이 명확하지 않은 경우, 최근에 추가된 항목으로 간주 + return t.date.includes('오늘') || + t.date.includes('어제') || + t.date.includes('이번 주') || + t.date.includes('일 전'); + } + }); + + // 일일 지출 계산 (오늘) + const dailyExpenses = expenseTransactions.filter(t => { + try { + const transactionDate = parseTransactionDate(t.date); + if (!transactionDate) return false; + + return format(transactionDate, 'yyyy-MM-dd') === today; + } catch (e) { + // 날짜 형식이 명확하지 않은 경우, 오늘 추가된 항목인지 확인 + return t.date.includes('오늘') || t.date.includes('지금'); + } + }); + + // 계산된 지출 금액 + const dailyTotal = dailyExpenses.reduce((sum, t) => sum + t.amount, 0); + const weeklyTotal = weeklyExpenses.reduce((sum, t) => sum + t.amount, 0); + const monthlyTotal = monthlyExpenses.reduce((sum, t) => sum + t.amount, 0); + + console.log("계산된 지출 금액:", { + 일일: dailyTotal, + 주간: weeklyTotal, + 월간: monthlyTotal + }); + + // 예산 데이터에 적용 전 로그 + console.log("기존 예산 데이터:", prevBudgetData); + + // 기존 예산 목표 유지 const dailyTarget = prevBudgetData?.daily?.targetAmount || 0; const weeklyTarget = prevBudgetData?.weekly?.targetAmount || 0; const monthlyTarget = prevBudgetData?.monthly?.targetAmount || 0; - console.log("예산 목표 확인:", { - 일일: dailyTarget, - 주간: weeklyTarget, - 월간: monthlyTarget - }); - // 예산 데이터 업데이트 const updatedBudget = { daily: { targetAmount: dailyTarget, - spentAmount: dailySpent, - remainingAmount: Math.max(0, dailyTarget - dailySpent) + spentAmount: dailyTotal, + remainingAmount: Math.max(0, dailyTarget - dailyTotal) }, weekly: { targetAmount: weeklyTarget, - spentAmount: weeklySpent, - remainingAmount: Math.max(0, weeklyTarget - weeklySpent) + spentAmount: weeklyTotal, + remainingAmount: Math.max(0, weeklyTarget - weeklyTotal) }, monthly: { targetAmount: monthlyTarget, - spentAmount: monthlySpent, - remainingAmount: Math.max(0, monthlyTarget - monthlySpent) + spentAmount: monthlyTotal, + remainingAmount: Math.max(0, monthlyTarget - monthlyTotal) } }; - console.log("지출액 계산 결과:", updatedBudget); + console.log("업데이트된 예산 데이터:", updatedBudget); return updatedBudget; }; diff --git a/src/hooks/transactions/filterOperations/index.ts b/src/hooks/transactions/filterOperations/index.ts index 2f09d8c..3e5fdb8 100644 --- a/src/hooks/transactions/filterOperations/index.ts +++ b/src/hooks/transactions/filterOperations/index.ts @@ -1,12 +1,20 @@ -import { useCallback } from 'react'; -import { FilteringProps, FilteringReturn } from './types'; -import { useMonthSelection } from './useMonthSelection'; -import { useFilterApplication } from './useFilterApplication'; -import { useTotalCalculation } from './useTotalCalculation'; +import { useCallback, useEffect } from 'react'; +import { Transaction } from '@/contexts/budget/types'; +import { getCurrentMonth, getPrevMonth, getNextMonth } from '../dateUtils'; +import { filterTransactionsByMonth, filterTransactionsByQuery, calculateTotalExpenses } from '../filterUtils'; + +interface UseTransactionsFilteringProps { + transactions: Transaction[]; + selectedMonth: string; + setSelectedMonth: (month: string) => void; + searchQuery: string; + setFilteredTransactions: React.Dispatch>; +} /** - * 트랜잭션 필터링 관련 기능을 통합한 훅 + * 트랜잭션 필터링 관련 커스텀 훅 + * 월간, 검색어 필터링 및 총 지출 계산 기능을 제공합니다. */ export const useTransactionsFiltering = ({ transactions, @@ -14,34 +22,52 @@ export const useTransactionsFiltering = ({ setSelectedMonth, searchQuery, setFilteredTransactions -}: FilteringProps): FilteringReturn => { - // 월 선택 관련 기능 - const { handlePrevMonth, handleNextMonth } = useMonthSelection({ - selectedMonth, - setSelectedMonth - }); +}: UseTransactionsFilteringProps) => { + // 필터링 적용 + useEffect(() => { + console.log('트랜잭션 필터링 적용:', { 선택된월: selectedMonth, 검색어: searchQuery }); + + try { + // 먼저 월별 필터링 + const monthFiltered = filterTransactionsByMonth(transactions, selectedMonth); + console.log('월별 필터링 결과:', monthFiltered.length); + + // 그 다음 검색어 필터링 + const searchFiltered = searchQuery + ? filterTransactionsByQuery(monthFiltered, searchQuery) + : monthFiltered; + + console.log('최종 필터링 결과:', searchFiltered.length); + setFilteredTransactions(searchFiltered); + + } catch (error) { + console.error('트랜잭션 필터링 중 오류 발생:', error); + // 오류 발생 시 원본 데이터 유지 + setFilteredTransactions(transactions); + } + }, [transactions, selectedMonth, searchQuery, setFilteredTransactions]); - // 필터 적용 관련 기능 - const { filterTransactions } = useFilterApplication({ - transactions, - selectedMonth, - searchQuery, - setFilteredTransactions - }); + // 이전 달로 이동 + const handlePrevMonth = useCallback(() => { + setSelectedMonth(getPrevMonth(selectedMonth)); + }, [selectedMonth, setSelectedMonth]); - // 총 지출 계산 관련 기능 - const { getTotalExpenses } = useTotalCalculation(); + // 다음 달로 이동 + const handleNextMonth = useCallback(() => { + setSelectedMonth(getNextMonth(selectedMonth)); + }, [selectedMonth, setSelectedMonth]); - // 강제 필터링 실행 함수 (외부에서 호출 가능) - const forceRefresh = useCallback(() => { - console.log('필터 강제 새로고침'); - filterTransactions(); - }, [filterTransactions]); + // 총 지출 계산 - date-fns 없이도 사용 가능한 단순 버전 + const getTotalExpenses = useCallback((filteredTransactions: Transaction[]): number => { + console.log('총 지출 계산 중...', filteredTransactions.length); + const total = calculateTotalExpenses(filteredTransactions); + console.log('계산된 총 지출:', total); + return total; + }, []); return { handlePrevMonth, handleNextMonth, - getTotalExpenses, - forceRefresh + getTotalExpenses }; }; diff --git a/src/hooks/transactions/filterUtils.ts b/src/hooks/transactions/filterUtils.ts index 0c45087..925b72f 100644 --- a/src/hooks/transactions/filterUtils.ts +++ b/src/hooks/transactions/filterUtils.ts @@ -1,63 +1,108 @@ -import { Transaction } from '@/components/TransactionCard'; -import { MONTHS_KR } from './dateUtils'; +import { Transaction } from '@/contexts/budget/types'; -// 월별 거래 필터링 -export const filterTransactionsByMonth = (transactions: Transaction[], selectedMonth: string): Transaction[] => { - console.log('월별 필터링:', selectedMonth, '트랜잭션 수:', transactions.length); +/** + * 월별로 트랜잭션 필터링 + */ +export const filterTransactionsByMonth = ( + transactions: Transaction[], + selectedMonth: string +): Transaction[] => { + console.log(`월별 트랜잭션 필터링: ${selectedMonth}, 총 데이터 수: ${transactions.length}`); - // 현재 날짜 정보 - const now = new Date(); - const currentYear = now.getFullYear(); - const currentMonth = now.getMonth() + 1; // JavaScript 월은 0부터 시작 - - // 선택된 월의 인덱스 (0-11) - const selectedMonthIndex = MONTHS_KR.findIndex(month => month === selectedMonth); - - // 특수 케이스 처리: '이번 달', '오늘', '이번 주' 등 - if (transactions.some(t => t.date.includes('오늘') || t.date.includes('어제') || t.date.includes('이번주'))) { - return transactions; + // 필터링 전 샘플 데이터 로그 + if (transactions.length > 0) { + console.log('샘플 트랜잭션 날짜:', transactions.slice(0, 3).map(t => t.date)); } - // 실제 필터링 - return transactions.filter(transaction => { - // 날짜 형식이 '2023-05-15' 또는 '2023/05/15' 등의 형식인 경우 - if (transaction.date.includes('-') || transaction.date.includes('/')) { - const parts = transaction.date.split(/[-\/]/); - if (parts.length >= 2) { - const transactionMonth = parseInt(parts[1]); - const monthIndex = transactionMonth - 1; // 0-11 인덱스로 변환 - return monthIndex === selectedMonthIndex; - } + const filtered = transactions.filter(transaction => { + // 트랜잭션 타입 확인 - 지출 항목만 포함 + if (transaction.type !== 'expense') { + return false; } - // 날짜에 월이 포함된 경우 (예: '5월 15일') - for (let i = 0; i < MONTHS_KR.length; i++) { - if (transaction.date.includes(MONTHS_KR[i])) { - return MONTHS_KR[i] === selectedMonth; + try { + // 날짜가 없는 경우 필터링 제외 + if (!transaction.date) { + console.warn('날짜 없는 트랜잭션:', transaction); + return false; } + + // 현재 월 포함 확인 (다양한 형식 지원) + const dateIncludes = transaction.date.includes(selectedMonth); + + // 월 이름으로 확인 (한글) + const monthNumberMatch = selectedMonth.match(/\d{4}-(\d{2})/); + let monthNameIncluded = false; + + if (monthNumberMatch) { + const monthNumber = parseInt(monthNumberMatch[1]); + const koreanMonths = [ + '1월', '2월', '3월', '4월', '5월', '6월', + '7월', '8월', '9월', '10월', '11월', '12월' + ]; + + // 해당 월의 한글 이름이 트랜잭션 날짜에 포함되어 있는지 확인 + if (monthNumber >= 1 && monthNumber <= 12) { + monthNameIncluded = transaction.date.includes(koreanMonths[monthNumber - 1]); + } + } + + return dateIncludes || monthNameIncluded; + } catch (e) { + console.error('트랜잭션 필터링 중 오류:', e, transaction); + return false; } - - // 기본적으로 모든 트랜잭션 표시 (필터링 실패 시) - return true; }); -}; - -// 검색어로 거래 필터링 -export const filterTransactionsByQuery = (transactions: Transaction[], query: string): Transaction[] => { - if (!query.trim()) return transactions; - const lowercaseQuery = query.toLowerCase(); - return transactions.filter(transaction => - transaction.title.toLowerCase().includes(lowercaseQuery) || - transaction.category.toLowerCase().includes(lowercaseQuery) || - transaction.amount.toString().includes(lowercaseQuery) - ); + console.log(`월별 필터링 결과: ${filtered.length}개 항목 (${selectedMonth})`); + return filtered; }; -// 총 지출 계산 -export const calculateTotalExpenses = (transactions: Transaction[]): number => { - return transactions - .filter(t => t.type === 'expense') - .reduce((total, transaction) => total + transaction.amount, 0); +/** + * 검색어로 트랜잭션 필터링 + */ +export const filterTransactionsByQuery = ( + transactions: Transaction[], + searchQuery: string +): Transaction[] => { + if (!searchQuery.trim()) return transactions; + + const query = searchQuery.toLowerCase().trim(); + console.log(`검색어 필터링: "${query}"`); + + const filtered = transactions.filter(transaction => { + try { + return ( + (transaction.title?.toLowerCase().includes(query)) || + (transaction.category?.toLowerCase().includes(query)) || + (transaction.paymentMethod?.toLowerCase().includes(query)) + ); + } catch (e) { + console.error('검색어 필터링 중 오류:', e, transaction); + return false; + } + }); + + console.log(`검색어 필터링 결과: ${filtered.length}개 항목`); + return filtered; +}; + +/** + * 총 지출 금액 계산 + */ +export const calculateTotalExpenses = (transactions: Transaction[]): number => { + try { + const total = transactions.reduce((sum, t) => { + // 유효한 숫자인지 확인 + const amount = typeof t.amount === 'number' ? t.amount : 0; + return sum + amount; + }, 0); + + console.log(`총 지출 계산: ${total}원 (${transactions.length}개 항목)`); + return total; + } catch (e) { + console.error('총 지출 계산 중 오류:', e); + return 0; + } }; diff --git a/src/utils/dateParser.ts b/src/utils/dateParser.ts new file mode 100644 index 0000000..855cf4a --- /dev/null +++ b/src/utils/dateParser.ts @@ -0,0 +1,96 @@ + +import { format, parse, isValid, parseISO } from 'date-fns'; +import { ko } from 'date-fns/locale'; + +/** + * 다양한 형식의 날짜 문자열을 Date 객체로 변환하는 유틸리티 + */ +export const parseTransactionDate = (dateStr: string): Date | null => { + // 빈 문자열 체크 + if (!dateStr) { + return null; + } + + try { + // 특수 키워드 처리 + if (dateStr.includes('오늘')) { + return new Date(); + } + + if (dateStr.includes('어제')) { + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + return yesterday; + } + + // ISO 형식 (yyyy-MM-dd) 시도 + if (/^\d{4}-\d{2}-\d{2}/.test(dateStr)) { + const date = parseISO(dateStr); + if (isValid(date)) { + return date; + } + } + + // "M월 d일" 형식 (한국어) 시도 + const koreanDatePattern = /(\d+)월\s*(\d+)일/; + const koreanMatch = dateStr.match(koreanDatePattern); + + if (koreanMatch) { + const month = parseInt(koreanMatch[1]) - 1; // 0-based month + const day = parseInt(koreanMatch[2]); + const date = new Date(); + date.setMonth(month); + date.setDate(day); + return date; + } + + // parse를 사용한 다양한 형식 시도 + const formats = [ + 'yyyy-MM-dd', + 'yyyy/MM/dd', + 'MM-dd-yyyy', + 'MM/dd/yyyy', + 'yyyy년 MM월 dd일', + 'MM월 dd일' + ]; + + for (const formatStr of formats) { + try { + const date = parse(dateStr, formatStr, new Date(), { locale: ko }); + if (isValid(date)) { + return date; + } + } catch (e) { + // 이 형식이 실패하면 다음 형식 시도 + continue; + } + } + + // 위 모든 형식이 실패하면 마지막으로 Date 생성자 시도 + const dateFromConstructor = new Date(dateStr); + if (isValid(dateFromConstructor)) { + return dateFromConstructor; + } + + // 모든 방법이 실패하면 null 반환 + console.warn(`날짜 파싱 실패: ${dateStr}`); + return null; + } catch (error) { + console.error(`날짜 파싱 중 오류 발생: ${dateStr}`, error); + return null; + } +}; + +/** + * Date 객체를 yyyy-MM-dd 형식의 문자열로 변환 + */ +export const formatDateToYMD = (date: Date): string => { + return format(date, 'yyyy-MM-dd'); +}; + +/** + * 현재 년월 포맷 (yyyy-MM) + */ +export const getCurrentYearMonth = (): string => { + return format(new Date(), 'yyyy-MM'); +};