diff --git a/public/icons/teamficial-keyword.svg b/public/icons/teamficial-keyword.svg new file mode 100644 index 00000000..bc45f2ba --- /dev/null +++ b/public/icons/teamficial-keyword.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/app/(main)/project/[id]/_components/InfoItem.tsx b/src/app/(main)/project/[id]/_components/InfoItem.tsx index 38d43e32..f3d06f70 100644 --- a/src/app/(main)/project/[id]/_components/InfoItem.tsx +++ b/src/app/(main)/project/[id]/_components/InfoItem.tsx @@ -18,7 +18,7 @@ const InfoItem = ({ label, value, className = '' }: InfoItemProps) => { href={value} target="_blank" rel="noopener noreferrer" - className="body-4 text-primary-900 line-clamp-1 break-all underline" + className="desktop:body-4 body-8 text-primary-900 line-clamp-1 break-all underline" title={value} > {value} diff --git a/src/app/(main)/teampsylog/_components/KeywordBar.tsx b/src/app/(main)/teampsylog/_components/KeywordBar.tsx index b42ceda9..b89bf95f 100644 --- a/src/app/(main)/teampsylog/_components/KeywordBar.tsx +++ b/src/app/(main)/teampsylog/_components/KeywordBar.tsx @@ -1,10 +1,11 @@ import { useGetKeyword } from '@/hooks/queries/useKeyword'; -import React from 'react'; +import React, { useEffect, useState } from 'react'; import KeywordItem from './KeywordItem'; import Image from 'next/image'; import ProfileDropdown from './ProfileDropdown'; import { ResponseProfile } from '@/types/profile'; import { useToast } from '@/contexts/ToastContext'; +import KeywordGuideBalloon from './KeywordGuideBalloon'; const KeywordBar = ({ profileId, @@ -29,6 +30,7 @@ const KeywordBar = ({ }) => { const { data } = useGetKeyword({ profileId }); const { addToast } = useToast(); + const [showGuide, setShowGuide] = useState(false); const headKeywords = data?.headKeywords || []; const desktopDisplayKeywords = [ @@ -61,45 +63,63 @@ const KeywordBar = ({ addToast({ message: '링크가 복사되었어요' }); }; + useEffect(() => { + // 편집 모드이고 아직 슬롯이 선택되지 않았을 때만 가이드 표시 + if (isEditMode && selectedSlot === null) { + setShowGuide(true); + } else { + setShowGuide(false); + } + }, [isEditMode, selectedSlot]); + if (!isShareMode) return null; + return ( <> {/* desktop */} -
- {/* 대표키워드 및 프로필 드롭다운 */} -
- - {desktopDisplayKeywords.map((keyword, index) => ( - = headKeywords.length} - onClick={() => isEditMode && onSelectSlot(index)} - /> - ))} -
- {isShareMode && ( -
- - + {desktopDisplayKeywords.map((keyword, index) => ( + = headKeywords.length} + onClick={() => isEditMode && onSelectSlot(index)} + /> + ))} +
+ {isShareMode && ( +
+ + +
+ )}
- )} + + {/* mobile */}
{/* 수정 버튼 및 프로필 드롭다운 */} @@ -123,18 +143,24 @@ const KeywordBar = ({
-
- {mobileDisplayKeywords.map((keyword, index) => ( - = headKeywords.length} - onClick={() => isEditMode && onSelectSlot(index)} - isMobileDevice={true} - /> - ))} + +
+ {isEditMode && showGuide && ( + setShowGuide(false)} /> + )} +
+ {mobileDisplayKeywords.map((keyword, index) => ( + = headKeywords.length} + onClick={() => isEditMode && onSelectSlot(index)} + isMobileDevice={true} + /> + ))} +
diff --git a/src/app/(main)/teampsylog/_components/KeywordGuideBalloon.tsx b/src/app/(main)/teampsylog/_components/KeywordGuideBalloon.tsx new file mode 100644 index 00000000..5d97cf5f --- /dev/null +++ b/src/app/(main)/teampsylog/_components/KeywordGuideBalloon.tsx @@ -0,0 +1,41 @@ +import React from 'react'; + +interface KeywordGuideBalloonProps { + position: 'top' | 'bottom'; + onClose: () => void; + text?: string; +} + +const KeywordGuideBalloon: React.FC = ({ + position, + onClose, + text = '변경할 대표 키워드를\n먼저 선택하세요', +}) => { + return ( +
+
+ {text} + +
+ {/* 꼬리 */} +
+
+ ); +}; + +export default KeywordGuideBalloon; diff --git a/src/app/(main)/teampsylog/_components/KeywordGuideOverlay.tsx b/src/app/(main)/teampsylog/_components/KeywordGuideOverlay.tsx index 32d89003..9f64f375 100644 --- a/src/app/(main)/teampsylog/_components/KeywordGuideOverlay.tsx +++ b/src/app/(main)/teampsylog/_components/KeywordGuideOverlay.tsx @@ -78,7 +78,7 @@ const KeywordGuideOverlay = ({ onClose }: Props) => {
{/* mobile */} -
+
{ const [page, setPage] = useState(0); const [isMobileSheetOpen, setIsMobileSheetOpen] = useState(false); + const [showGuide, setShowGuide] = useState(false); const { data, isLoading, isError } = useGetKeywordList({ userId: userId ?? 0, @@ -42,6 +44,14 @@ const LogNote = ({ setSelectedKeywordId(null); }, [page]); + useEffect(() => { + if (isEditMode && selectedSlot !== null) { + setShowGuide(true); + } else { + setShowGuide(false); + } + }, [isEditMode, selectedSlot]); + const handleKeywordClick = (keywordId: number) => { if (isEditMode && selectedSlot !== null) { // 편집 모드: 선택된 슬롯에 키워드 할당 @@ -67,6 +77,16 @@ const LogNote = ({
{/* 왼쪽 페이지 */}
+ {showGuide && ( +
+ setShowGuide(false)} + text={`변경할 키워드를\n선택하세요`} + /> +
+ )} + - {data?.totalElements === 0 ? null : isEditMode ? ( - <> - teamficial_symbol -

- {selectedSlot !== null - ? `변경할 키워드를 선택하세요` - : '변경할 대표키워드를\n먼저 선택하세요'} -

- - ) : !selectedKeywordId ? ( - <> - teamficial_symbol -

- 키워드를 선택해 자세한 내용을 확인하세요 -

- + {!selectedKeywordId ? ( + teamficial_symbol ) : ( id === selectedKeywordId)] ?? ''} diff --git a/src/utils/project/formatDate.ts b/src/utils/project/formatDate.ts index 99620710..260cf1ed 100644 --- a/src/utils/project/formatDate.ts +++ b/src/utils/project/formatDate.ts @@ -1,5 +1,22 @@ +export const parseDate = (dateString: string): Date | null => { + if (!dateString) return null; + + const isoString = dateString.replace(/\./g, '-').replace(/ /g, 'T'); + + const date = new Date(isoString); + + if (isNaN(date.getTime())) { + console.error('Failed to parse date:', dateString); + return null; + } + + return date; +}; + export const formatDate = (dateString: string) => { - const date = new Date(dateString); + const date = parseDate(dateString); + if (!date) return ''; + const year = date.getFullYear(); const month = date.getMonth() + 1; const day = date.getDate(); @@ -8,7 +25,9 @@ export const formatDate = (dateString: string) => { }; export const formatDateDot = (dateString: string): string => { - const date = new Date(dateString); + const date = parseDate(dateString); + if (!date) return ''; + const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); @@ -17,13 +36,17 @@ export const formatDateDot = (dateString: string): string => { }; export const formatDday = (dday: number | null | undefined): string => { - // NaN 체크 - if (dday == null || isNaN(Number(dday))) { + if (dday == null) { return '마감'; } const numDday = Number(dday); + if (isNaN(numDday)) { + console.error('Invalid dday value:', dday); + return '마감'; + } + if (numDday > 0) return `D-${numDday}`; if (numDday === 0) return 'D-DAY'; return '마감';