FE_Test / components /improvement-result /analysis-table.tsx
GitHub Actions
Deploy from GitHub Actions [test] - 2025-10-31 10:18:25
5f2aab6
'use client';
import { LoadingPulse } from '@/components/parts/loading-pulse';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import type { SwotData } from '@/schema/pox';
import type { ProposalTabData } from '@/schema/proposal';
import { useGlobalStore } from '@/store/global';
import { useResultStore } from '@/store/result';
import { useEffect, useState } from 'react';
interface AnalysisTableProps {
onLoadingChange?: (isLoading: boolean) => void;
proposalTabsData?: Record<string, ProposalTabData>; // 各タブのデータ(prediction値順)
}
export default function AnalysisTable({ onLoadingChange, proposalTabsData }: AnalysisTableProps) {
// ストアからSWOTデータを取得(既にrefresh-momentsページで取得済み)
const { swotData75, swotData78 } = useResultStore();
const { dummyMode } = useGlobalStore();
// ダミーモード用のデータ取得
const [dummySwotData, setDummySwotData] = useState<SwotData | null>(null);
useEffect(() => {
if (dummyMode) {
// ダミーモードの場合はPOX JSONファイルから取得
fetch('/dummy/get_pox.json')
.then((res) => res.json())
.then((data) => {
if (Array.isArray(data) && data[0]) {
setDummySwotData(data[0]);
}
})
.catch((err) => console.error('Failed to load dummy POX data:', err));
}
}, [dummyMode]);
const swotData = dummyMode ? dummySwotData : swotData75 || swotData78;
// 戦略別にタブをグループ化する関数
const getProposalsByStrategy = (strategy: string): string[] => {
if (!proposalTabsData) {
return [];
}
const proposals: string[] = [];
Object.entries(proposalTabsData).forEach(([key, data]) => {
// 戦略名から「(強み×機会)」などの括弧部分を削除して比較
const normalizedStrategy = data?.intent?.strategy?.split('(')[0];
if (normalizedStrategy === strategy) {
proposals.push(key);
}
});
// 最大3つまで返す
return proposals.slice(0, 3);
};
// 初期ローディング状態を管理
const [isInitialLoading, setIsInitialLoading] = useState(!swotData);
// SWOTデータが取得されたらローディングを終了
useEffect(() => {
if (swotData) {
setIsInitialLoading(false);
}
}, [swotData]);
const isLoading = isInitialLoading;
const error = null; // ストアから取得するためエラーはない
// ローディング状態を親コンポーネントに通知
useEffect(() => {
if (onLoadingChange) {
onLoadingChange(isLoading);
}
}, [isLoading, onLoadingChange]);
// ローディング中の表示
if (isLoading) {
return (
<div className="flex flex-col space-y-6 rounded-xl bg-[#f3fafd] px-12 py-8">
<h1 className="mb-3 text-[20px] leading-[135%] font-bold text-[#212121]">分析に基づく改善アプローチ</h1>
<div className="flex items-center justify-center py-8">
<LoadingPulse />
</div>
</div>
);
}
// エラー時またはデータがない場合は簡単なメッセージを表示
if (error || !swotData) {
return (
<div className="flex flex-col space-y-6 rounded-xl bg-[#f3fafd] px-12 py-8">
<h1 className="mb-3 text-[20px] leading-[135%] font-bold text-[#212121]">分析に基づく改善アプローチ</h1>
<div className="flex items-center justify-center py-8">
<p className="text-gray-600">{error ? 'データの読み込み中にエラーが発生しました' : 'データを準備中です...'}</p>
</div>
</div>
);
}
return (
<div className="flex flex-col space-y-6 rounded-xl bg-[#f3fafd] px-12 py-8">
<>
<h1 className="mb-3 text-[20px] leading-[135%] font-bold text-[#212121]">分析に基づく改善アプローチ</h1>
<p className="text-[14px] leading-[135%] font-bold text-[#212121]">
競合分析で抽出したLP情報を活用してクロスSWOT分析を行い、戦略的な改善アプローチを導き出しました。
</p>
</>
<Table className="table-fixed border border-[#ccc]" data-testid="analysis-table">
<TableHeader>
<TableRow>
<TableHead
className="w-[22%] border-r border-b border-[#ccc] bg-[#D6EBF7] text-start text-base leading-[135%] font-bold text-[#212121]"
rowSpan={2}
colSpan={3}
data-testid="swot-analysis-section"
>
クロスSWOT分析
</TableHead>
<TableHead className="text-foreground border-b border-[#ccc] bg-[#CDE7F5] text-center text-[14px] font-bold" colSpan={6}>
内部要因
</TableHead>
</TableRow>
<TableRow>
<TableHead
colSpan={3}
rowSpan={1}
className="text-foreground h-auto border-r border-b border-[#ccc] bg-[#E2F0F9] px-4! py-3! text-center font-bold"
>
<h1 className="text-lg leading-[135%] font-bold text-[#212121]">Strength 強み</h1>
<span className="text-[11px] font-medium">{swotData.強み?.一番の強み?.言い換え}</span>
</TableHead>
<TableHead colSpan={3} className="text-foreground h-auto border-r border-b border-[#ccc] bg-[#E2F0F9] px-4! py-3! text-center font-bold">
<h1 className="text-lg leading-[135%] font-bold text-[#212121]">Weakness 弱み</h1>
<span className="text-[11px] font-medium">{swotData.弱み?.一番の弱み?.言い換え}</span>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow className="w-full">
<TableCell
className="text-foreground border-r border-[#ccc] bg-[#CDE7F5] px-[16px] py-[8px] align-middle text-[14px] font-bold"
rowSpan={2}
colSpan={1}
>
外部要因
</TableCell>
<TableCell
className="space-y-2 border-r border-b border-[#ccc] bg-[#E2F0F9] px-[16px] py-[8px] text-center align-middle font-bold"
colSpan={2}
>
<p className="text-lg leading-[135%] font-bold text-[#212121]">Opportunity</p>
<p className="text-lg leading-[135%] font-bold text-[#212121]">機会</p>
<ul className="flex flex-col items-center space-y-1 text-[11px] font-medium">
{(swotData.機会 || []).slice(0, 2).map((item: string, index: number) => (
<li key={index} className="text-[11px] leading-[20px] font-medium">
・{item}
</li>
))}
</ul>
</TableCell>
<TableCell className="space-y-2 border-r border-b border-[#ccc] bg-[#F8FBFE] px-[16px] py-[8px] text-center font-bold" colSpan={3}>
<p className="text-lg font-semibold text-[#0186C9]">積極化戦略</p>
<p className="text-[13px] font-semibold">{swotData.積極化戦略?.テーマ || ''}</p>
<p className="text-start text-[11px] font-medium">{swotData.積極化戦略?.戦略の内容 || ''}</p>
<div className="flex w-full justify-center space-x-4">
{getProposalsByStrategy('積極化戦略').map((proposalKey) => (
<div
key={proposalKey}
className="flex h-[24px] w-[65px] items-center justify-center rounded-xs border border-[#ccc] bg-[#FFFFFF] px-[8px] py-[8px] text-sm"
>
{proposalKey}
</div>
))}
</div>
</TableCell>
<TableCell className="space-y-2 border-r border-b border-[#ccc] bg-[#F8FBFE] px-[16px] py-[8px] text-center font-bold" colSpan={3}>
<p className="text-lg font-semibold text-[#0186C9]">改善戦略</p>
<p className="text-[13px] font-semibold">{swotData.改善戦略?.テーマ || ''}</p>
<p className="text-start text-[11px] font-medium">{swotData.改善戦略?.戦略の内容 || ''}</p>
<div className="flex w-full justify-center space-x-4">
{getProposalsByStrategy('改善戦略').map((proposalKey) => (
<div
key={proposalKey}
className="flex h-[24px] w-[65px] items-center justify-center rounded-xs border border-[#ccc] bg-[#FFFFFF] px-[8px] py-[8px] text-sm text-[12px]"
>
{proposalKey}
</div>
))}
</div>
</TableCell>
</TableRow>
<TableRow className="h-full min-h-[200px] w-full">
<TableCell
className="space-y-2 border-r border-b border-[#ccc] bg-[#E2F0F9] px-[16px] py-[8px] text-center align-middle font-bold"
colSpan={2}
>
<p className="text-lg leading-[135%] font-bold text-[#212121]">Threat</p>
<p className="text-lg leading-[135%] font-bold text-[#212121]">脅威</p>
<ul className="flex flex-col items-center space-y-1 text-[11px] font-medium">
{(swotData.脅威 || []).slice(0, 2).map((item: string, index: number) => (
<li key={index} className="text-[11px] leading-[20px] font-medium">
・{item}
</li>
))}
</ul>
</TableCell>
<TableCell className="space-y-2 border-r border-b border-[#ccc] bg-[#F8FBFE] px-[16px] py-[8px] text-center font-bold" colSpan={3}>
<p className="text-lg font-semibold text-[#0186C9]">差別化戦略</p>
<p className="text-[13px] font-semibold">{swotData.差別化戦略?.テーマ || ''}</p>
<p className="text-start text-[11px] font-medium">{swotData.差別化戦略?.戦略の内容 || ''}</p>
<div className="flex w-full justify-center space-x-4">
{getProposalsByStrategy('差別化戦略').map((proposalKey) => (
<div
key={proposalKey}
className="flex h-[24px] w-[65px] items-center justify-center rounded-xs border border-[#ccc] bg-[#FFFFFF] px-[8px] py-[8px] text-sm"
>
{proposalKey}
</div>
))}
</div>
</TableCell>
<TableCell className="border-r border-b border-[#ccc] bg-[#EFEFEF]/50 px-[16px] py-[8px] text-center align-middle font-bold" colSpan={3}>
<p className="text-lg font-semibold text-[#212121]">防衛戦略</p>
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
);
}