diff --git a/frontend-temp/src/constants/index.js b/frontend-temp/src/constants/index.js index ac714f0..525006f 100644 --- a/frontend-temp/src/constants/index.js +++ b/frontend-temp/src/constants/index.js @@ -22,15 +22,9 @@ export const TIMEZONE = 'Asia/Seoul'; /** 요일 이름 */ export const WEEKDAYS = ['일', '월', '화', '수', '목', '금', '토']; -/** 기본 페이지 크기 */ -export const DEFAULT_PAGE_SIZE = 20; - /** 검색 결과 페이지 크기 */ export const SEARCH_LIMIT = 20; -/** API 기본 URL */ -export const API_BASE_URL = '/api'; - /** 캘린더 최소 년도 */ export const MIN_YEAR = 2017; diff --git a/frontend-temp/src/hooks/common/index.js b/frontend-temp/src/hooks/common/index.js index c18567f..0b12f91 100644 --- a/frontend-temp/src/hooks/common/index.js +++ b/frontend-temp/src/hooks/common/index.js @@ -1,31 +1,2 @@ -// 미디어 쿼리 -export { useMediaQuery, useIsMobile, useIsDesktop, useIsTablet } from './useMediaQuery'; - -// 멤버 데이터 -export { useMembers, useMemberDetail } from './useMemberData'; - -// 앨범 데이터 -export { useAlbums, useAlbumDetail, useAlbumGallery } from './useAlbumData'; - -// 스케줄 데이터 -export { - useScheduleData, - useScheduleDetail, - useUpcomingSchedules, - useCategories, -} from './useScheduleData'; - -// 스케줄 검색 -export { useScheduleSearch } from './useScheduleSearch'; - -// 스케줄 필터링 -export { useScheduleFiltering, useCategoryCounts } from './useScheduleFiltering'; - -// 캘린더 -export { useCalendar } from './useCalendar'; - -// 라이트박스 -export { useLightbox } from './useLightbox'; - // 토스트 export { default as useToast } from './useToast'; diff --git a/frontend-temp/src/hooks/common/useAlbumData.js b/frontend-temp/src/hooks/common/useAlbumData.js deleted file mode 100644 index a51571a..0000000 --- a/frontend-temp/src/hooks/common/useAlbumData.js +++ /dev/null @@ -1,36 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { albumApi } from '@/api'; - -/** - * 앨범 목록 조회 훅 - */ -export function useAlbums() { - return useQuery({ - queryKey: ['albums'], - queryFn: albumApi.getAlbums, - }); -} - -/** - * 앨범 상세 조회 훅 - * @param {string} title - 앨범 타이틀 또는 폴더명 - */ -export function useAlbumDetail(title) { - return useQuery({ - queryKey: ['album', title], - queryFn: () => albumApi.getAlbumByTitle(title), - enabled: !!title, - }); -} - -/** - * 앨범 갤러리 조회 훅 - * @param {string} title - 앨범 타이틀 또는 폴더명 - */ -export function useAlbumGallery(title) { - return useQuery({ - queryKey: ['album-gallery', title], - queryFn: () => albumApi.getAlbumGallery(title), - enabled: !!title, - }); -} diff --git a/frontend-temp/src/hooks/common/useCalendar.js b/frontend-temp/src/hooks/common/useCalendar.js deleted file mode 100644 index d645304..0000000 --- a/frontend-temp/src/hooks/common/useCalendar.js +++ /dev/null @@ -1,118 +0,0 @@ -import { useState, useMemo, useCallback } from 'react'; -import { MIN_YEAR, WEEKDAYS, MONTH_NAMES } from '@/constants'; -import { getTodayKST } from '@/utils'; - -/** - * 캘린더 훅 - * 날짜 선택, 월 이동 등 캘린더 로직 제공 - * @param {Date} initialDate - 초기 날짜 - */ -export function useCalendar(initialDate = new Date()) { - const [currentDate, setCurrentDate] = useState(initialDate); - const [selectedDate, setSelectedDate] = useState(getTodayKST()); - - const year = currentDate.getFullYear(); - const month = currentDate.getMonth(); - - // 캘린더 데이터 계산 - const calendarData = useMemo(() => { - const firstDay = new Date(year, month, 1).getDay(); - const daysInMonth = new Date(year, month + 1, 0).getDate(); - const prevMonthDays = new Date(year, month, 0).getDate(); - - return { - year, - month, - monthName: MONTH_NAMES[month], - firstDay, - daysInMonth, - prevMonthDays, - weekdays: WEEKDAYS, - }; - }, [year, month]); - - // 이전 월로 이동 가능 여부 - const canGoPrevMonth = !(year === MIN_YEAR && month === 0); - - // 선택 날짜 업데이트 헬퍼 - const updateSelectedDate = useCallback((newDate) => { - const today = new Date(); - if ( - newDate.getFullYear() === today.getFullYear() && - newDate.getMonth() === today.getMonth() - ) { - setSelectedDate(getTodayKST()); - } else { - const firstDay = `${newDate.getFullYear()}-${String(newDate.getMonth() + 1).padStart(2, '0')}-01`; - setSelectedDate(firstDay); - } - }, []); - - // 이전 월로 이동 - const goToPrevMonth = useCallback(() => { - if (!canGoPrevMonth) return; - const newDate = new Date(year, month - 1, 1); - setCurrentDate(newDate); - updateSelectedDate(newDate); - }, [year, month, canGoPrevMonth, updateSelectedDate]); - - // 다음 월로 이동 - const goToNextMonth = useCallback(() => { - const newDate = new Date(year, month + 1, 1); - setCurrentDate(newDate); - updateSelectedDate(newDate); - }, [year, month, updateSelectedDate]); - - // 특정 월로 이동 - const goToMonth = useCallback( - (newYear, newMonth) => { - const newDate = new Date(newYear, newMonth, 1); - setCurrentDate(newDate); - updateSelectedDate(newDate); - }, - [updateSelectedDate] - ); - - // 오늘로 이동 - const goToToday = useCallback(() => { - const today = new Date(); - setCurrentDate(today); - setSelectedDate(getTodayKST()); - }, []); - - // 날짜 선택 - const selectDate = useCallback( - (day) => { - const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; - setSelectedDate(dateStr); - }, - [year, month] - ); - - // 반환 객체 메모이제이션 - return useMemo( - () => ({ - ...calendarData, - currentDate, - selectedDate, - canGoPrevMonth, - goToPrevMonth, - goToNextMonth, - goToMonth, - goToToday, - selectDate, - setSelectedDate, - }), - [ - calendarData, - currentDate, - selectedDate, - canGoPrevMonth, - goToPrevMonth, - goToNextMonth, - goToMonth, - goToToday, - selectDate, - ] - ); -} diff --git a/frontend-temp/src/hooks/common/useLightbox.js b/frontend-temp/src/hooks/common/useLightbox.js deleted file mode 100644 index 63c18c9..0000000 --- a/frontend-temp/src/hooks/common/useLightbox.js +++ /dev/null @@ -1,125 +0,0 @@ -import { useState, useEffect, useCallback } from 'react'; - -/** - * 라이트박스 상태 및 동작 관리 훅 - * @param {Object} options - * @param {Array<{url: string, thumb_url?: string}|string>} options.images - 이미지 배열 - * @param {Function} options.onClose - 닫기 콜백 (optional) - * @returns {Object} 라이트박스 상태 및 메서드 - */ -export function useLightbox({ images = [], onClose } = {}) { - const [isOpen, setIsOpen] = useState(false); - const [currentIndex, setCurrentIndex] = useState(0); - - // 라이트박스 열기 - const open = useCallback((index = 0) => { - setCurrentIndex(index); - setIsOpen(true); - window.history.pushState({ lightbox: true }, ''); - }, []); - - // 라이트박스 닫기 - const close = useCallback(() => { - setIsOpen(false); - onClose?.(); - }, [onClose]); - - // 이전 이미지 - const goToPrev = useCallback(() => { - if (images.length <= 1) return; - setCurrentIndex((prev) => (prev > 0 ? prev - 1 : images.length - 1)); - }, [images.length]); - - // 다음 이미지 - const goToNext = useCallback(() => { - if (images.length <= 1) return; - setCurrentIndex((prev) => (prev < images.length - 1 ? prev + 1 : 0)); - }, [images.length]); - - // 특정 인덱스로 이동 - const goToIndex = useCallback( - (index) => { - if (index >= 0 && index < images.length) { - setCurrentIndex(index); - } - }, - [images.length] - ); - - // 뒤로가기 처리 - useEffect(() => { - if (!isOpen) return; - - const handlePopState = () => { - close(); - }; - - window.addEventListener('popstate', handlePopState); - return () => window.removeEventListener('popstate', handlePopState); - }, [isOpen, close]); - - // body 스크롤 방지 (Lightbox 컴포넌트에서 처리하므로 여기선 생략) - - // 이미지 프리로딩 - useEffect(() => { - if (!isOpen || images.length === 0) return; - - // 현재, 이전, 다음 이미지 프리로드 - const indicesToPreload = [ - currentIndex, - (currentIndex + 1) % images.length, - (currentIndex - 1 + images.length) % images.length, - ]; - - indicesToPreload.forEach((index) => { - const img = new Image(); - const imageItem = images[index]; - img.src = typeof imageItem === 'string' ? imageItem : imageItem?.url || imageItem; - }); - }, [isOpen, currentIndex, images]); - - // 이미지 다운로드 - const downloadImage = useCallback(async () => { - const imageItem = images[currentIndex]; - const imageUrl = typeof imageItem === 'string' ? imageItem : imageItem?.url || imageItem; - if (!imageUrl) return; - - try { - const response = await fetch(imageUrl); - const blob = await response.blob(); - const url = URL.createObjectURL(blob); - const link = document.createElement('a'); - link.href = url; - link.download = `image_${currentIndex + 1}.jpg`; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - URL.revokeObjectURL(url); - } catch (error) { - console.error('이미지 다운로드 실패:', error); - } - }, [images, currentIndex]); - - // 현재 이미지 URL 가져오기 - const getCurrentImageUrl = useCallback(() => { - const imageItem = images[currentIndex]; - return typeof imageItem === 'string' ? imageItem : imageItem?.url || imageItem; - }, [images, currentIndex]); - - return { - isOpen, - currentIndex, - currentImage: images[currentIndex], - currentImageUrl: getCurrentImageUrl(), - totalCount: images.length, - open, - close, - goToPrev, - goToNext, - goToIndex, - downloadImage, - setCurrentIndex, - }; -} - -export default useLightbox; diff --git a/frontend-temp/src/hooks/common/useMediaQuery.js b/frontend-temp/src/hooks/common/useMediaQuery.js deleted file mode 100644 index 5e6f390..0000000 --- a/frontend-temp/src/hooks/common/useMediaQuery.js +++ /dev/null @@ -1,46 +0,0 @@ -import { useState, useEffect, useCallback } from 'react'; - -/** - * 미디어 쿼리 훅 - * @param {string} query - CSS 미디어 쿼리 문자열 - * @returns {boolean} 쿼리 매칭 여부 - */ -export function useMediaQuery(query) { - const [matches, setMatches] = useState(() => { - if (typeof window !== 'undefined') { - return window.matchMedia(query).matches; - } - return false; - }); - - // 핸들러 메모이제이션 - const handler = useCallback((e) => { - setMatches(e.matches); - }, []); - - // DOM 이벤트 리스너 - useEffect(() => { - const mediaQuery = window.matchMedia(query); - setMatches(mediaQuery.matches); - - mediaQuery.addEventListener('change', handler); - return () => mediaQuery.removeEventListener('change', handler); - }, [query, handler]); - - return matches; -} - -/** - * 모바일 여부 확인 훅 - */ -export const useIsMobile = () => useMediaQuery('(max-width: 768px)'); - -/** - * 데스크탑 여부 확인 훅 - */ -export const useIsDesktop = () => useMediaQuery('(min-width: 769px)'); - -/** - * 태블릿 여부 확인 훅 - */ -export const useIsTablet = () => useMediaQuery('(min-width: 769px) and (max-width: 1024px)'); diff --git a/frontend-temp/src/hooks/common/useMemberData.js b/frontend-temp/src/hooks/common/useMemberData.js deleted file mode 100644 index 072e251..0000000 --- a/frontend-temp/src/hooks/common/useMemberData.js +++ /dev/null @@ -1,24 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { memberApi } from '@/api'; - -/** - * 멤버 목록 조회 훅 - */ -export function useMembers() { - return useQuery({ - queryKey: ['members'], - queryFn: memberApi.getMembers, - }); -} - -/** - * 멤버 상세 조회 훅 - * @param {number} id - 멤버 ID - */ -export function useMemberDetail(id) { - return useQuery({ - queryKey: ['member', id], - queryFn: () => memberApi.getMemberById(id), - enabled: !!id, - }); -} diff --git a/frontend-temp/src/hooks/common/useScheduleData.js b/frontend-temp/src/hooks/common/useScheduleData.js deleted file mode 100644 index 56bc9e6..0000000 --- a/frontend-temp/src/hooks/common/useScheduleData.js +++ /dev/null @@ -1,49 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { scheduleApi } from '@/api'; - -/** - * 스케줄 목록 조회 훅 (월별) - * @param {number} year - 년도 - * @param {number} month - 월 (1-12) - */ -export function useScheduleData(year, month) { - return useQuery({ - queryKey: ['schedules', year, month], - queryFn: () => scheduleApi.getSchedules(year, month), - enabled: !!year && !!month, - }); -} - -/** - * 스케줄 상세 조회 훅 - * @param {number} id - 스케줄 ID - */ -export function useScheduleDetail(id) { - return useQuery({ - queryKey: ['schedule', id], - queryFn: () => scheduleApi.getSchedule(id), - enabled: !!id, - }); -} - -/** - * 다가오는 스케줄 조회 훅 - * @param {number} limit - 조회 개수 - */ -export function useUpcomingSchedules(limit = 3) { - return useQuery({ - queryKey: ['schedules', 'upcoming', limit], - queryFn: () => scheduleApi.getUpcomingSchedules(limit), - }); -} - -/** - * 카테고리 목록 조회 훅 - */ -export function useCategories() { - return useQuery({ - queryKey: ['categories'], - queryFn: scheduleApi.getCategories, - staleTime: 1000 * 60 * 10, // 10분 캐시 - }); -} diff --git a/frontend-temp/src/hooks/common/useScheduleFiltering.js b/frontend-temp/src/hooks/common/useScheduleFiltering.js deleted file mode 100644 index f12ddc4..0000000 --- a/frontend-temp/src/hooks/common/useScheduleFiltering.js +++ /dev/null @@ -1,71 +0,0 @@ -import { useMemo } from 'react'; -import { formatDate } from '@/utils'; -import { getCategoryId, getScheduleDate, isBirthdaySchedule } from '@/utils/schedule'; - -/** - * 스케줄 필터링 훅 - * @param {Array} schedules - 스케줄 배열 - * @param {object} options - 필터 옵션 - * @param {number[]} options.categoryIds - 선택된 카테고리 ID 배열 - * @param {string} options.selectedDate - 선택된 날짜 (YYYY-MM-DD) - * @param {boolean} options.birthdayFirst - 생일 우선 정렬 여부 - * @returns {Array} 필터링된 스케줄 배열 - */ -export function useScheduleFiltering(schedules, options = {}) { - const { - categoryIds = [], - selectedDate = null, - birthdayFirst = true, - } = options; - - return useMemo(() => { - if (!schedules || schedules.length === 0) return []; - - let result = schedules.filter((schedule) => { - // 카테고리 필터 - if (categoryIds.length > 0) { - const catId = getCategoryId(schedule); - if (!categoryIds.includes(catId)) return false; - } - - // 날짜 필터 - if (selectedDate) { - const scheduleDate = getScheduleDate(schedule); - if (scheduleDate !== selectedDate) return false; - } - - return true; - }); - - // 생일 우선 정렬 - if (birthdayFirst) { - result = [...result].sort((a, b) => { - const aIsBirthday = isBirthdaySchedule(a); - const bIsBirthday = isBirthdaySchedule(b); - if (aIsBirthday && !bIsBirthday) return -1; - if (!aIsBirthday && bIsBirthday) return 1; - return 0; - }); - } - - return result; - }, [schedules, categoryIds, selectedDate, birthdayFirst]); -} - -/** - * 카테고리별 개수 계산 훅 - * @param {Array} schedules - 스케줄 배열 - * @returns {object} 카테고리 ID별 개수 객체 - */ -export function useCategoryCounts(schedules) { - return useMemo(() => { - if (!schedules || schedules.length === 0) return {}; - - const counts = {}; - for (const schedule of schedules) { - const categoryId = getCategoryId(schedule); - counts[categoryId] = (counts[categoryId] || 0) + 1; - } - return counts; - }, [schedules]); -} diff --git a/frontend-temp/src/hooks/common/useScheduleSearch.js b/frontend-temp/src/hooks/common/useScheduleSearch.js deleted file mode 100644 index 88c563e..0000000 --- a/frontend-temp/src/hooks/common/useScheduleSearch.js +++ /dev/null @@ -1,75 +0,0 @@ -import { useState, useMemo } from 'react'; -import { useInfiniteQuery } from '@tanstack/react-query'; -import { fetchApi } from '@/api'; -import { SEARCH_LIMIT } from '@/constants'; - -/** - * 스케줄 검색 훅 - * 무한 스크롤, 검색어 추천 지원 - */ -export function useScheduleSearch() { - const [searchInput, setSearchInput] = useState(''); - const [searchTerm, setSearchTerm] = useState(''); - const [isSearchMode, setIsSearchMode] = useState(false); - - // 검색 결과 (무한 스크롤) - const searchQuery = useInfiniteQuery({ - queryKey: ['scheduleSearch', searchTerm], - queryFn: async ({ pageParam = 0, signal }) => { - const response = await fetch( - `/api/schedules?search=${encodeURIComponent(searchTerm)}&offset=${pageParam}&limit=${SEARCH_LIMIT}`, - { signal } - ); - if (!response.ok) throw new Error('Search failed'); - return response.json(); - }, - getNextPageParam: (lastPage) => { - return lastPage.hasMore - ? lastPage.offset + lastPage.schedules.length - : undefined; - }, - enabled: !!searchTerm && isSearchMode, - }); - - // 검색 결과 평탄화 - const searchResults = useMemo(() => { - return searchQuery.data?.pages?.flatMap((page) => page.schedules) || []; - }, [searchQuery.data]); - - const searchTotal = searchQuery.data?.pages?.[0]?.total || 0; - - // 검색 실행 - const executeSearch = () => { - if (searchInput.trim()) { - setSearchTerm(searchInput.trim()); - setIsSearchMode(true); - } - }; - - // 검색 초기화 - const clearSearch = () => { - setSearchInput(''); - setSearchTerm(''); - setIsSearchMode(false); - }; - - return { - // 상태 - searchInput, - searchTerm, - isSearchMode, - searchResults, - searchTotal, - isLoading: searchQuery.isLoading, - isFetching: searchQuery.isFetching, - hasNextPage: searchQuery.hasNextPage, - isFetchingNextPage: searchQuery.isFetchingNextPage, - - // 액션 - setSearchInput, - setIsSearchMode, - executeSearch, - clearSearch, - fetchNextPage: searchQuery.fetchNextPage, - }; -} diff --git a/frontend-temp/src/stores/index.js b/frontend-temp/src/stores/index.js index 26fc97a..b657c6c 100644 --- a/frontend-temp/src/stores/index.js +++ b/frontend-temp/src/stores/index.js @@ -3,4 +3,3 @@ */ export { default as useAuthStore } from './useAuthStore'; export { default as useScheduleStore } from './useScheduleStore'; -export { default as useUIStore } from './useUIStore'; diff --git a/frontend-temp/src/stores/useUIStore.js b/frontend-temp/src/stores/useUIStore.js deleted file mode 100644 index 4c68426..0000000 --- a/frontend-temp/src/stores/useUIStore.js +++ /dev/null @@ -1,98 +0,0 @@ -import { create } from 'zustand'; - -/** - * UI 상태 스토어 - * 모달, 토스트, 사이드바 등 전역 UI 상태 관리 - */ -const useUIStore = create((set, get) => ({ - // ===== 모달 ===== - modalOpen: false, - modalContent: null, - modalProps: {}, - - openModal: (content, props = {}) => { - set({ - modalOpen: true, - modalContent: content, - modalProps: props, - }); - }, - - closeModal: () => { - set({ - modalOpen: false, - modalContent: null, - modalProps: {}, - }); - }, - - // ===== 토스트 ===== - toasts: [], - - addToast: (message, type = 'info', duration = 3000) => { - const id = Date.now(); - const toast = { id, message, type, duration }; - - set((state) => ({ - toasts: [...state.toasts, toast], - })); - - // 자동 제거 - if (duration > 0) { - setTimeout(() => { - get().removeToast(id); - }, duration); - } - - return id; - }, - - removeToast: (id) => { - set((state) => ({ - toasts: state.toasts.filter((t) => t.id !== id), - })); - }, - - clearToasts: () => set({ toasts: [] }), - - // 편의 메서드 - showSuccess: (message, duration) => get().addToast(message, 'success', duration), - showError: (message, duration) => get().addToast(message, 'error', duration), - showWarning: (message, duration) => get().addToast(message, 'warning', duration), - showInfo: (message, duration) => get().addToast(message, 'info', duration), - - // ===== 사이드바 (모바일) ===== - sidebarOpen: false, - toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })), - openSidebar: () => set({ sidebarOpen: true }), - closeSidebar: () => set({ sidebarOpen: false }), - - // ===== 라이트박스 ===== - lightboxOpen: false, - lightboxImages: [], - lightboxIndex: 0, - - openLightbox: (images, index = 0) => { - set({ - lightboxOpen: true, - lightboxImages: Array.isArray(images) ? images : [images], - lightboxIndex: index, - }); - }, - - closeLightbox: () => { - set({ - lightboxOpen: false, - lightboxImages: [], - lightboxIndex: 0, - }); - }, - - setLightboxIndex: (index) => set({ lightboxIndex: index }), - - // ===== 로딩 ===== - globalLoading: false, - setGlobalLoading: (value) => set({ globalLoading: value }), -})); - -export default useUIStore; diff --git a/frontend-temp/src/utils/date.js b/frontend-temp/src/utils/date.js index c4d7ce5..d8dfa56 100644 --- a/frontend-temp/src/utils/date.js +++ b/frontend-temp/src/utils/date.js @@ -19,14 +19,6 @@ export const getTodayKST = () => { return dayjs().tz(TIMEZONE).format('YYYY-MM-DD'); }; -/** - * KST 기준 현재 시각 - * @returns {dayjs.Dayjs} dayjs 객체 - */ -export const nowKST = () => { - return dayjs().tz(TIMEZONE); -}; - /** * 날짜 문자열 포맷팅 * @param {string|Date} date - 날짜 @@ -38,21 +30,6 @@ export const formatDate = (date, format = 'YYYY-MM-DD') => { return dayjs(date).tz(TIMEZONE).format(format); }; -/** - * 날짜에서 년, 월, 일, 요일 추출 - * @param {string|Date} date - 날짜 - * @returns {{ year: number, month: number, day: number, weekday: string }} - */ -export const parseDateKST = (date) => { - const d = dayjs(date).tz(TIMEZONE); - return { - year: d.year(), - month: d.month() + 1, - day: d.date(), - weekday: WEEKDAYS[d.day()], - }; -}; - /** * 두 날짜 비교 (같은 날인지) * @param {string|Date} date1 @@ -75,24 +52,6 @@ export const isToday = (date) => { return isSameDay(date, dayjs()); }; -/** - * 날짜가 과거인지 확인 - * @param {string|Date} date - * @returns {boolean} - */ -export const isPast = (date) => { - return dayjs(date).tz(TIMEZONE).isBefore(dayjs().tz(TIMEZONE), 'day'); -}; - -/** - * 날짜가 미래인지 확인 - * @param {string|Date} date - * @returns {boolean} - */ -export const isFuture = (date) => { - return dayjs(date).tz(TIMEZONE).isAfter(dayjs().tz(TIMEZONE), 'day'); -}; - /** * 전체 날짜 포맷 (YYYY. M. D. (요일)) * @param {string|Date} date - 날짜 diff --git a/frontend-temp/src/utils/format.js b/frontend-temp/src/utils/format.js index 48f46ff..d8929a5 100644 --- a/frontend-temp/src/utils/format.js +++ b/frontend-temp/src/utils/format.js @@ -39,75 +39,6 @@ export const formatTime = (time) => { return time.slice(0, 5); }; -/** - * 숫자에 천 단위 콤마 추가 - * @param {number} num - 숫자 - * @returns {string} 콤마가 추가된 문자열 - */ -export const formatNumber = (num) => { - if (num === null || num === undefined) return '0'; - return num.toLocaleString('ko-KR'); -}; - -/** - * 조회수 포맷팅 (1만 이상이면 '만' 단위로) - * @param {number} count - 조회수 - * @returns {string} 포맷된 조회수 - */ -export const formatViewCount = (count) => { - if (!count) return '0'; - if (count >= 10000) { - return `${(count / 10000).toFixed(1)}만`; - } - return formatNumber(count); -}; - -/** - * 파일 크기 포맷팅 - * @param {number} bytes - 바이트 단위 크기 - * @returns {string} 포맷된 크기 (예: "1.5 MB") - */ -export const formatFileSize = (bytes) => { - if (!bytes) return '0 B'; - const units = ['B', 'KB', 'MB', 'GB']; - let size = bytes; - let unitIndex = 0; - while (size >= 1024 && unitIndex < units.length - 1) { - size /= 1024; - unitIndex++; - } - return `${size.toFixed(1)} ${units[unitIndex]}`; -}; - -/** - * 재생 시간 포맷팅 (초 -> MM:SS 또는 HH:MM:SS) - * @param {number} seconds - 초 단위 시간 - * @returns {string} 포맷된 시간 - */ -export const formatDuration = (seconds) => { - if (!seconds) return '0:00'; - const hours = Math.floor(seconds / 3600); - const minutes = Math.floor((seconds % 3600) / 60); - const secs = Math.floor(seconds % 60); - - if (hours > 0) { - return `${hours}:${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`; - } - return `${minutes}:${String(secs).padStart(2, '0')}`; -}; - -/** - * 텍스트 말줄임 처리 - * @param {string} text - 원본 텍스트 - * @param {number} maxLength - 최대 길이 - * @returns {string} 말줄임 처리된 텍스트 - */ -export const truncateText = (text, maxLength) => { - if (!text) return ''; - if (text.length <= maxLength) return text; - return `${text.slice(0, maxLength)}...`; -}; - /** * 크레딧 텍스트를 배열로 분리 * @param {string} text - 쉼표로 구분된 크레딧 텍스트 diff --git a/frontend-temp/src/utils/index.js b/frontend-temp/src/utils/index.js index 843080c..2cf48e3 100644 --- a/frontend-temp/src/utils/index.js +++ b/frontend-temp/src/utils/index.js @@ -8,13 +8,9 @@ export { cn } from './cn'; // 날짜 관련 export { getTodayKST, - nowKST, formatDate, - parseDateKST, isSameDay, isToday, - isPast, - isFuture, formatFullDate, formatXDateTime, extractDate, @@ -26,11 +22,6 @@ export { export { decodeHtmlEntities, formatTime, - formatNumber, - formatViewCount, - formatFileSize, - formatDuration, - truncateText, parseCredits, calculateTotalDuration, } from './format';