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 && (
-
-
+
{/* 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선택하세요`}
+ />
+
+ )}
+
setPage((p) => Math.max(0, p - 1))}
disabled={page === 0}
@@ -146,32 +166,13 @@ const LogNote = ({
>
- {data?.totalElements === 0 ? null : isEditMode ? (
- <>
-
-
- {selectedSlot !== null
- ? `변경할 키워드를 선택하세요`
- : '변경할 대표키워드를\n먼저 선택하세요'}
-
- >
- ) : !selectedKeywordId ? (
- <>
-
-
- 키워드를 선택해 자세한 내용을 확인하세요
-
- >
+ {!selectedKeywordId ? (
+
) : (
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 '마감';