From b157efae6776be935d921158edd0b8194e6efe8a Mon Sep 17 00:00:00 2001 From: tamtya Date: Sun, 22 Feb 2026 03:44:32 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=E4=BD=9C=E6=A5=AD=E4=B8=AD=EF=BC=9A?= =?UTF-8?q?=E6=9C=80=E6=96=B0=E3=81=AEdevelop=E3=82=92=E5=8F=96=E3=82=8A?= =?UTF-8?q?=E8=BE=BC=E3=82=80=E5=89=8D=E3=81=AE=E3=83=90=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=82=A2=E3=83=83=E3=83=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package-lock.json | 10 ++ frontend/package.json | 1 + .../result/components/FeatureScoreBar.tsx | 31 +++++- .../result/components/GameDetailTab.tsx | 75 ++++++++++--- .../result/components/MetricsBarChart.tsx | 86 ++++++++------ .../result/components/OverviewTab.tsx | 76 +++++++++---- .../features/result/components/RadarChart.tsx | 2 +- .../result/components/ResultReport.tsx | 105 ++++++++++++------ .../features/result/components/SharePanel.tsx | 8 +- 9 files changed, 274 insertions(+), 120 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f67d067..6a61e1d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "framer-motion": "^12.34.2", "jotai": "^2.18.0", + "lucide-react": "^0.575.0", "next": "16.1.6", "react": "19.2.3", "react-dom": "19.2.3", @@ -5217,6 +5218,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.575.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.575.0.tgz", + "integrity": "sha512-VuXgKZrk0uiDlWjGGXmKV6MSk9Yy4l10qgVvzGn2AWBx1Ylt0iBexKOAoA6I7JO3m+M9oeovJd3yYENfkUbOeg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", diff --git a/frontend/package.json b/frontend/package.json index 3c743e7..5f657d6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,6 +13,7 @@ "dependencies": { "framer-motion": "^12.34.2", "jotai": "^2.18.0", + "lucide-react": "^0.575.0", "next": "16.1.6", "react": "19.2.3", "react-dom": "19.2.3", diff --git a/frontend/src/features/result/components/FeatureScoreBar.tsx b/frontend/src/features/result/components/FeatureScoreBar.tsx index eb180d0..d0ebef1 100644 --- a/frontend/src/features/result/components/FeatureScoreBar.tsx +++ b/frontend/src/features/result/components/FeatureScoreBar.tsx @@ -7,6 +7,20 @@ type FeatureScoreBarProps = { className?: string; }; +const COLOR_MAP: Record = { + 積極性: 'bg-orange-500', + 冷静さ: 'bg-yellow-400', + 論理性: 'bg-blue-500', + 慎重さ: 'bg-red-500', + 協調性: 'bg-green-500', + + positivity: 'bg-orange-500', + calmness: 'bg-yellow-400', + logic: 'bg-blue-500', + caution: 'bg-red-500', + cooperativeness: 'bg-green-500', +}; + export default function FeatureScoreBar({ name, score, @@ -15,17 +29,22 @@ export default function FeatureScoreBar({ }: FeatureScoreBarProps) { const percent = Math.min(100, Math.max(0, (score / max) * 100)); + const barColor = COLOR_MAP[name] || 'bg-gray-400'; + return ( -
-
- {name} - {score} +
+
+ {name} + {score}
-
+ +
+ +
); diff --git a/frontend/src/features/result/components/GameDetailTab.tsx b/frontend/src/features/result/components/GameDetailTab.tsx index b7865dc..8465c99 100644 --- a/frontend/src/features/result/components/GameDetailTab.tsx +++ b/frontend/src/features/result/components/GameDetailTab.tsx @@ -1,5 +1,6 @@ 'use client'; +import { Users } from 'lucide-react'; import type { GameDetail } from '../types'; import FeatureScoreBar from './FeatureScoreBar'; import MetricsBarChart from './MetricsBarChart'; @@ -16,43 +17,81 @@ export default function GameDetailTab({ tabColor = '#3b82f6', }: GameDetailTabProps) { return ( -
-
-
-

+
+
+
+

+ 計測された特徴

-
+ {/* space-y-3 -> space-y-1.5 にしてバーの間隔を詰めました */} +
{detail.feature_scores.map((fs) => ( ))}
-
-

解析コメント

-

- {comment} -

+ +
+
+

+ 解析コメント +

+
+

+ {comment + .split(/(\d+\.?\d*秒|\d+回ホバー|\d+回反論)/) + .map((part, i) => + /(\d+\.?\d*秒|\d+回ホバー|\d+回反論)/.test(part) ? ( + // ハイライトの色をタブの色に合わせて動的に変更 + + {part} + + ) : ( + part + ) + )} +

+
+
-
-

+ +
+

行動ログ詳細データ

-
+ +
-
- ■ あなた - ■ 平均 +
+ + {/* 凡例 */} +
+
+
+ あなた +
+
+
+ 平均
diff --git a/frontend/src/features/result/components/MetricsBarChart.tsx b/frontend/src/features/result/components/MetricsBarChart.tsx index bc26cad..b0374ed 100644 --- a/frontend/src/features/result/components/MetricsBarChart.tsx +++ b/frontend/src/features/result/components/MetricsBarChart.tsx @@ -8,6 +8,7 @@ import { Tooltip, XAxis, YAxis, + CartesianGrid, } from 'recharts'; import type { Metric } from '../types'; @@ -20,58 +21,71 @@ type MetricsBarChartProps = { export default function MetricsBarChart({ metrics, - userBarColor = '#ef4444', - averageBarColor = '#9ca3af', + userBarColor = '#eab308', // スクショの黄金色 + averageBarColor = '#d1d5db', className = '', }: MetricsBarChartProps) { - const allValues = metrics.flatMap((m) => [m.user, m.average]); - const minVal = Math.min(...allValues, 0); - const maxVal = Math.max(...allValues, 1); - - const data = metrics.map((m) => ({ - label: m.label, - user: m.user, - average: m.average, - })); - return ( -
- +
+ - + {/* 縦の点線:グリッド */} + + + + { - if (!active || !payload?.length) return null; - const d = payload[0].payload; - return ( -
-

{d.label}

-

あなた: {d.user}

-

平均: {d.average}

-
- ); + cursor={{ fill: '#f1f5f9', opacity: 0.5 }} + contentStyle={{ + borderRadius: '12px', + border: '4px solid #000', + fontWeight: '900', }} /> - - {data.map((_, i) => ( + + {/* あなた:太くて黒枠があるメインバー */} + + {metrics.map((_, i) => ( ))} - - {data.map((_, i) => ( + + + {metrics.map((_, i) => ( ))} diff --git a/frontend/src/features/result/components/OverviewTab.tsx b/frontend/src/features/result/components/OverviewTab.tsx index 2516fe9..2e58981 100644 --- a/frontend/src/features/result/components/OverviewTab.tsx +++ b/frontend/src/features/result/components/OverviewTab.tsx @@ -7,6 +7,7 @@ type OverviewTabProps = { data: ResultResponse; }; +// ギャップ率の計算(元コードのロジックを継承) function getGapRateDisplay(data: ResultResponse): string { const { gaps } = data; const maxAbsGap = Math.max( @@ -16,36 +17,71 @@ function getGapRateDisplay(data: ResultResponse): string { Math.abs(gaps.cooperativeness), Math.abs(gaps.positivity) ); - const percent = Math.min(100, Math.round((maxAbsGap / 100) * 100)); - return `${percent}%`; + return `${Math.min(100, Math.round(maxAbsGap))}%`; } +// キーワード強調用の小コンポーネント +const Highlight = ({ + children, + color, +}: { + children: React.ReactNode; + color: string; +}) => ( + + {children} + +); + export default function OverviewTab({ data }: OverviewTabProps) { const gapRate = getGapRateDisplay(data); const { feedback } = data; return ( -
-
- +
+ {/* 左側:レーダーチャートのカード */} + {/* 修正ポイント:aspect-square と overflow-hidden で「飛び出し」を防止 */} +
+
+ +
-
-
+ + {/* 右側:フィードバックのカード */} +
+ {/* CHECK THIS OUT! ステッカー */} +
CHECK THIS OUT!
-

- 理性と本能のギャップ:{' '} - {gapRate} -

-

- {feedback.description} -

-

- 最も乖離が大きかった項目: {feedback.gap_point} -

+ + {/* 黄色のカード本体 */} +
+

+ + 理性と本能のギャップ: + + {gapRate} +

+ +
+

+ あなたは + + 『{feedback.gap_point}』 + + を自認していますが、実際の行動は + 直感 + にドーンと振れています! +

+ +

{feedback.description}

+
+
); diff --git a/frontend/src/features/result/components/RadarChart.tsx b/frontend/src/features/result/components/RadarChart.tsx index 6c0d6f0..b1a7272 100644 --- a/frontend/src/features/result/components/RadarChart.tsx +++ b/frontend/src/features/result/components/RadarChart.tsx @@ -46,7 +46,7 @@ export default function RadarChart({ return (
- + = { @@ -38,32 +47,59 @@ export default function ResultReport({ data }: ResultReportProps) { }, [router]); return ( -
-
- - 行動解析REPORT - +
+
+
+

+ + 行動解析REPORT + +

+
-
-
); } diff --git a/frontend/src/features/result/components/SharePanel.tsx b/frontend/src/features/result/components/SharePanel.tsx index 9f1e78c..df13c76 100644 --- a/frontend/src/features/result/components/SharePanel.tsx +++ b/frontend/src/features/result/components/SharePanel.tsx @@ -1,5 +1,6 @@ 'use client'; +import { Share2 } from 'lucide-react'; import { useCallback, useEffect, useRef, useState } from 'react'; type SharePanelProps = { @@ -68,9 +69,12 @@ export default function SharePanel({ title }: SharePanelProps) { {open && ( From a8a21e72009829c8b7bd61dc8c026069ac421ca2 Mon Sep 17 00:00:00 2001 From: tamtya Date: Sun, 22 Feb 2026 03:51:08 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=E5=B9=B3=E5=9D=87=E5=80=A4=E3=81=A8?= =?UTF-8?q?=E3=81=AE=E6=AF=94=E8=BC=83=E3=83=9B=E3=83=90=E3=83=BC=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E3=81=AE=E8=AA=A4=E6=A4=8D=E3=81=AB=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/result/components/MetricsBarChart.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/features/result/components/MetricsBarChart.tsx b/frontend/src/features/result/components/MetricsBarChart.tsx index b0374ed..50321ff 100644 --- a/frontend/src/features/result/components/MetricsBarChart.tsx +++ b/frontend/src/features/result/components/MetricsBarChart.tsx @@ -68,7 +68,7 @@ export default function MetricsBarChart({ {/* あなた:太くて黒枠があるメインバー */} From 7a3d8467a036915618540eef58bae8c0c3b73c1b Mon Sep 17 00:00:00 2001 From: tamtya Date: Sun, 22 Feb 2026 04:16:05 +0900 Subject: [PATCH 3/4] =?UTF-8?q?ui(frontend):=20=E7=B8=A6=E6=A3=92=E3=82=B0?= =?UTF-8?q?=E3=83=A9=E3=83=95=E3=81=AE=E5=B9=B3=E5=9D=87=E3=83=90=E3=83=BC?= =?UTF-8?q?=E3=81=AE=E5=AE=9A=E7=BE=A9=E3=82=92=E7=B0=A1=E7=95=A5=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/result/components/MetricsBarChart.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/frontend/src/features/result/components/MetricsBarChart.tsx b/frontend/src/features/result/components/MetricsBarChart.tsx index 50321ff..b286800 100644 --- a/frontend/src/features/result/components/MetricsBarChart.tsx +++ b/frontend/src/features/result/components/MetricsBarChart.tsx @@ -79,12 +79,7 @@ export default function MetricsBarChart({ ))} - + {metrics.map((_, i) => ( ))} From df835c50185445d2ea186def815efec6dc737ab6 Mon Sep 17 00:00:00 2001 From: ru-se Date: Sun, 22 Feb 2026 08:53:03 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=E3=83=AA=E3=82=B6=E3=83=AB=E3=83=88?= =?UTF-8?q?=E3=81=AE=E3=83=87=E3=83=BC=E3=82=BF=E8=A8=88=E7=AE=97=E6=89=8B?= =?UTF-8?q?=E6=B3=95=E5=A4=9A=E5=B0=91=E8=AA=BF=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/services/resultService.ts | 31 ++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/backend/src/services/resultService.ts b/backend/src/services/resultService.ts index 9320531..a0fc314 100644 --- a/backend/src/services/resultService.ts +++ b/backend/src/services/resultService.ts @@ -14,8 +14,8 @@ export const resultService = { throw { status: 404, code: 'user_not_found', message: 'User not found' }; } - // ベースラインスコア組み立て(キャッシュ有無に関わらず必要) - const baseline_scores: BaselineScores = { + // 1. ベースラインスコア取得 (アンケート結果) + let baseline_scores: BaselineScores = { caution: user.baseline_caution, calmness: user.baseline_calmness, logic: user.baseline_logic, @@ -23,6 +23,14 @@ export const resultService = { positivity: user.baseline_positive, }; + // 2. MBTI理論値スコア取得 + const mbti_scores = user.self_mbti ? getMbtiScores(user.self_mbti) : null; + + // 3. MBTIが回答されている場合、ベースラインを理論値で補正する(極端な値を抑える) + if (mbti_scores) { + baseline_scores = this.blendScores(baseline_scores, mbti_scores); + } + // キャッシュ確認:analysis_results にあればそこから返す const cached = await analysisResultRepository.findByUserId(userId); if (cached) { @@ -49,9 +57,6 @@ export const resultService = { baseline_scores ); - // MBTI理論値スコア - const mbti_scores = user.self_mbti ? getMbtiScores(user.self_mbti) : null; - // analysis_results にキャッシュとして保存 const cacheRow: AnalysisResultRow = { user_id: userId, @@ -133,5 +138,21 @@ export const resultService = { details: cached.details, }; }, + + /** + * アンケート結果とMBTI理論値をブレンドして、ベースラインの極端な値を調整する + */ + blendScores(survey: BaselineScores, mbti: BaselineScores): BaselineScores { + // 比重: アンケート 70%, 理論値 30% + const blend = (s: number, m: number) => Math.round(s * 0.7 + m * 0.3); + + return { + caution: blend(survey.caution, mbti.caution), + calmness: blend(survey.calmness, mbti.calmness), + logic: blend(survey.logic, mbti.logic), + cooperativeness: blend(survey.cooperativeness, mbti.cooperativeness), + positivity: blend(survey.positivity, mbti.positivity), + }; + }, };