{/* Video Info */}
-
+ {/*
{t('social.postNumber')}
+
-
+
*/}
{video.store_name} {new Date(video.created_at).toLocaleString('ko-KR')}
- {videoSpecs && (
-
{videoSpecs.resolution} · {videoSpecs.duration}
- )}
+
+ {videoMeta
+ ? `${videoMeta.width}×${videoMeta.height} · ${Math.floor(videoMeta.duration / 60)}:${String(Math.floor(videoMeta.duration % 60)).padStart(2, '0')}`
+ : t('social.videoSpecs')}
+
{/* Channel Selector - Custom Dropdown */}
diff --git a/src/locales/en.json b/src/locales/en.json
index 8f88625..d561a5d 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -16,6 +16,7 @@
"logout": "Log Out"
},
"footer": {
+ "company":"O2O Inc.",
"businessNumber": "Business Registration No. : 620-87-00810 | CEO : Ahn Sungmin",
"headquarters": "HQ : 41593 Unicorn Lab Daegu A05, 5F, 111 Oksan-ro, Buk-gu, Daegu, Korea",
"researchCenter": "R&D : 13453 Rooms 504-505 (East), KT Pangyo Bldg, 32 Geumto-ro, Sujeong-gu, Seongnam-si, Gyeonggi-do, Korea",
@@ -25,7 +26,7 @@
"social": {
"title": "Social Media Posting",
"postNumber": "Post 1",
- "videoSpecs": "1080x1920 · 10 sec",
+ "videoSpecs": "720x1280 · 1:01",
"channelLabel": "Post Channel",
"loadingAccounts": "Loading accounts...",
"noAccounts": "No connected social accounts.",
diff --git a/src/locales/ko.json b/src/locales/ko.json
index 45e8b59..07716a4 100644
--- a/src/locales/ko.json
+++ b/src/locales/ko.json
@@ -16,6 +16,7 @@
"logout": "로그아웃"
},
"footer": {
+ "company":"㈜에이아이오투오",
"businessNumber": "사업자 등록번호 : 620-87-00810 | 대표 : 안성민",
"headquarters": "본사 : 41593 대구광역시 북구 옥산로 111, 5층 유니콘랩 대구 A05호",
"researchCenter": "연구소 : 13453 경기 성남시 수정구 금토로 32 (금토동) (주)KT 판교빌딩 504호~505호 (East)",
@@ -25,7 +26,7 @@
"social": {
"title": "소셜 미디어 포스팅",
"postNumber": "게시물 1",
- "videoSpecs": "1080x1920 · 10초",
+ "videoSpecs": "720x1280 · 1:01",
"channelLabel": "게시 채널",
"loadingAccounts": "계정 로딩 중...",
"noAccounts": "연결된 소셜 계정이 없습니다.",
@@ -345,11 +346,11 @@
"conceptScalability": "컨셉 확장성",
"noInfo": "정보 없음",
"marketPositioning": "시장 포지셔닝",
- "coreValue": "핵심 가치 (Core Value)",
+ "coreValue": "핵심 가치",
"categoryDefinition": "카테고리 정의",
- "targetPersona": "타겟 페르소나",
+ "targetPersona": "주요 고객 유형",
"ageSuffix": "세",
- "sellingPoints": "주요 셀링 포인트 (USP)",
+ "sellingPoints": "주요 셀링 포인트",
"recommendedKeywords": "추천 타겟 키워드",
"generateContent": "콘텐츠 생성",
"pageDescBefore": "을 통해 도출된 ",
diff --git a/src/pages/Analysis/AnalysisResultSection.tsx b/src/pages/Analysis/AnalysisResultSection.tsx
index 5ce9ea0..88a49f7 100755
--- a/src/pages/Analysis/AnalysisResultSection.tsx
+++ b/src/pages/Analysis/AnalysisResultSection.tsx
@@ -1,7 +1,8 @@
-import React, { useState, useEffect, useRef } from 'react';
+import React from 'react';
import { useTranslation } from 'react-i18next';
-import { CrawlingResponse, TargetPersona, SellingPoint } from '../../types/api';
+import { CrawlingResponse, TargetPersona } from '../../types/api';
+import { GeometricChart } from './GeometricChart';
interface AnalysisResultSectionProps {
onBack: () => void;
@@ -9,200 +10,9 @@ interface AnalysisResultSectionProps {
data: CrawlingResponse;
}
-// 레이더 차트 컴포넌트
-interface RadarChartProps {
- data: SellingPoint[];
- size?: number;
-}
-
-const RadarChart: React.FC
= ({ data, size = 360 }) => {
- const [animatedScores, setAnimatedScores] = useState(data.map(() => 0));
- const [isAnimating, setIsAnimating] = useState(true);
-
- useEffect(() => {
- const targetScores = data.map(item => item.score);
- const duration = 1500;
- const startTime = Date.now();
-
- const animate = () => {
- const elapsed = Date.now() - startTime;
- const progress = Math.min(elapsed / duration, 1);
- const easeProgress = 1 - Math.pow(1 - progress, 3);
-
- const newScores = targetScores.map(target => target * easeProgress);
- setAnimatedScores(newScores);
-
- if (progress < 1) {
- requestAnimationFrame(animate);
- } else {
- setIsAnimating(false);
- }
- };
-
- requestAnimationFrame(animate);
- }, [data]);
-
- const padding = 80;
- const center = size / 2 + padding;
- const maxRadius = size / 2 - 20;
- const levels = 5;
- const angleStep = (2 * Math.PI) / data.length;
-
- const getPoint = (index: number, score: number) => {
- const angle = angleStep * index - Math.PI / 2;
- const radius = (score / 100) * maxRadius;
- return {
- x: center + radius * Math.cos(angle),
- y: center + radius * Math.sin(angle),
- };
- };
-
- const dataPoints = animatedScores.map((score, i) => getPoint(i, score));
- const dataPath = dataPoints.map((p, i) => (i === 0 ? `M ${p.x} ${p.y}` : `L ${p.x} ${p.y}`)).join(' ') + ' Z';
-
- const levelPaths = Array.from({ length: levels }, (_, levelIndex) => {
- const levelRadius = ((levelIndex + 1) / levels) * maxRadius;
- const points = data.map((_, i) => {
- const angle = angleStep * i - Math.PI / 2;
- return {
- x: center + levelRadius * Math.cos(angle),
- y: center + levelRadius * Math.sin(angle),
- };
- });
- return points.map((p, i) => (i === 0 ? `M ${p.x} ${p.y}` : `L ${p.x} ${p.y}`)).join(' ') + ' Z';
- });
-
- const axisLines = data.map((_, i) => {
- const angle = angleStep * i - Math.PI / 2;
- const endX = center + maxRadius * Math.cos(angle);
- const endY = center + maxRadius * Math.sin(angle);
- return `M ${center} ${center} L ${endX} ${endY}`;
- });
-
- const svgSize = size + padding * 2;
-
- // 순위별로 정렬하여 라벨 위치에 순위 번호 배치
- const sortedIndices = [...data]
- .map((item, i) => ({ index: i, score: item.score }))
- .sort((a, b) => b.score - a.score)
- .map((item, rank) => ({ ...item, rank: rank + 1 }));
-
- const rankMap = new Map();
- sortedIndices.forEach(item => rankMap.set(item.index, item.rank));
-
- return (
-
-
-
- );
-};
-
const AnalysisResultSection: React.FC = ({ onBack, onGenerate, data }) => {
const { t } = useTranslation();
const { processed_info, marketing_analysis } = data;
- const containerRef = useRef(null);
- const [buttonPosition, setButtonPosition] = useState({ left: 0, width: 0 });
const brandIdentity = marketing_analysis?.brand_identity;
const marketPositioning = marketing_analysis?.market_positioning;
@@ -213,26 +23,6 @@ const AnalysisResultSection: React.FC = ({ onBack, o
// 셀링 포인트를 score 내림차순으로 정렬
const sortedSellingPoints = [...sellingPoints].sort((a, b) => b.score - a.score);
- useEffect(() => {
- const updateButtonPosition = () => {
- if (containerRef.current) {
- const rect = containerRef.current.getBoundingClientRect();
- setButtonPosition({ left: rect.left, width: rect.width });
- }
- };
-
- updateButtonPosition();
- window.addEventListener('resize', updateButtonPosition);
-
- const observer = new MutationObserver(updateButtonPosition);
- observer.observe(document.body, { attributes: true, subtree: true, attributeFilter: ['class'] });
-
- return () => {
- window.removeEventListener('resize', updateButtonPosition);
- observer.disconnect();
- };
- }, []);
-
return (
{/* Header */}
@@ -253,13 +43,15 @@ const AnalysisResultSection: React.FC
= ({ onBack, o
{t('analysis.pageTitle')}
- {t('analysis.pageDescHighlight')}{t('analysis.pageDescBefore')}{processed_info?.customer_name || t('analysis.defaultBrandName')}{t('analysis.pageDescAfter')}
+ {t('analysis.pageDescHighlight')}{t('analysis.pageDescBefore')}{processed_info?.customer_name || t('analysis.defaultBrandName')}{t('analysis.pageDescAfter')}
+
+
{/* Main Content Container */}
-
+
{/* 매장명 & 주소 */}
{processed_info?.customer_name || t('analysis.brandNameFallback')}
@@ -298,7 +90,7 @@ const AnalysisResultSection: React.FC
= ({ onBack, o
{/* 레이더 차트 */}
{sellingPoints.length > 0 && (
-
+
)}
{/* 셀링 포인트 리스트 */}
@@ -340,9 +132,11 @@ const AnalysisResultSection: React.FC = ({ onBack, o
{t('analysis.favorKeywords', { defaultValue: '선호 키워드' })}
-
- {persona.favor_target.join('\n')}
-
+
+ {persona.favor_target.map((item, i) => (
+
{item}
+ ))}
+
@@ -369,13 +163,10 @@ const AnalysisResultSection: React.FC
= ({ onBack, o
{/* 콘텐츠 생성 버튼 */}
-
+