import React, { useState, useEffect, useMemo, useRef } from 'react'; import { initializeApp } from 'firebase/app'; import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from 'firebase/auth'; import { getFirestore, collection, doc, setDoc, updateDoc, onSnapshot, deleteDoc, serverTimestamp, getDoc } from 'firebase/firestore'; import { LayoutDashboard, Layers, AlertTriangle, GitCompare, UserCheck, Settings, Plus, ChevronRight, ChevronDown, CheckCircle, Lightbulb, Loader2, ArrowRight, Save, Trash2, ArrowUp, ArrowDown, Sparkles, Building2, CheckSquare, UploadCloud, FileText, Check, Settings2, Calculator, TrendingUp, DollarSign, Target, ClipboardCheck, Users, Download, Printer, X } from 'lucide-react'; // ========================================== // Firebase Setup & Config // ========================================== const firebaseConfig = typeof __firebase_config !== 'undefined' ? JSON.parse(__firebase_config) : {}; const app = initializeApp(firebaseConfig); const auth = getAuth(app); const db = getFirestore(app); const appId = typeof __app_id !== 'undefined' ? __app_id : 'jobjoin-mvp-app'; // ========================================== // JOBJOIN 辞書データ // ========================================== const COMPETENCY_DICT = [ "0-1突破力", "改善・工夫力", "戦略的構想力", "創造的発想力", "アンラーニング力", "意味づけ・納得形成力", "違和感感受力", "構造化・整理力", "リスク予知力", "計数・論理力", "探究・リサーチ力", "形式知化力", "IT・ツール適応力", "リソース管理力", "本質的翻訳力", "ラポール形成力", "利害調整力", "プレゼン力", "交渉・締結力", "バッドニュース力", "深層傾聴力", "エンパワメント力", "非言語察知力", "ティーチング力", "ホスピタリティ", "奉仕型リーダー", "フォロワーシップ", "多様性受容力", "瞬発的決断力", "精密継続力", "即レス・即行力", "マルチタスク力", "完遂・執着力", "フィジカル力", "美的・空間力", "自己駆動", "バウンダリー力", "規律遵守力", "自律的学習力", "レジリエンス", "セルフ管理力", "グリット", "当事者意識", "成長解釈力", "価値観一貫性" ]; const TALENT_DICT = [ "小さなおかしさに気づける", "くり返しのパターンに気づける", "危ない流れを先に感じられる", "場の空気がわかる", "相手の気持ちの変化に気づける", "見た目や空間のズレに気づける", "今が動くタイミングだとわかる", "大事なところを直感でつかめる", "頭の中を整理できる", "大事なところをつかめる", "原因と結果を考えられる", "似ているものを分けてまとめられる", "やり方をわかりやすく形にできる", "先の流れを考えて動ける", "どこまでやるかをはっきりさせられる", "ゼロから動き出せる", "新しい見方を思いつける", "先の理想を描ける", "少ない情報でも仮の答えを考えられる", "知らないことを広げて探せる", "出来事に意味を持たせられる", "変化を受け入れやすい", "人に安心してもらえる", "相手の気持ちに寄りそえる", "相手目線で考えられる", "人の背中を押せる", "ぶつかりすぎずにまとめられる", "言葉以外のサインも読める", "自分の熱をまわりに広げられる", "違うタイプの人も受け入れられる", "すぐ動ける", "早く進めたくなる", "コツコツ続けられる", "切り替えが早い", "最後までやり切れる", "自分ごととして引き受けられる", "大変な時でも崩れにくい", "ルールを守れる", "自分を整えられる", "言うこととやることがぶれにくい", "自分をふり返れる", "学ぶことが好き", "言われたことを自分ごとにできる", "失敗から学べる", "自分と相手の線引きを守れる" ]; const MICROSKILL_DICT = [ "論理的思考", "仮説構築力", "問題発見力", "因数分解力", "MECE思考", "なぜなぜ分析", "抽象化・具体化", "クリティカル思考", "構造化能力", "意思決定力", "リスク評価力", "パラドックス対処", "ビジョン構想力", "バックキャスティング", "ラテラル思考", "アイデア発想力", "コンセプト設計力", "デザイン思考", "顧客価値設計", "ビジネスモデル解剖", "シナリオプランニング", "新結合スキル", "転用力 / アナロジー", "ゲーム理論思考", "計数感覚", "データ収集力", "情報ソース評価", "パターン認識力", "相関・因果特定", "KPI設計力", "モニタリング力", "予測シミュレーション", "表計算リテラシー", "データ可視化", "ファクトベース判断", "ROI思考", "ストーリー構成力", "スライド作成力", "デリバリースキル", "要約・言語化力", "平易化スキル", "メタファー活用", "レトリック", "質疑応答力", "エレベーターピッチ", "ビジネスライティング", "コピーライティング", "図解スキル", "傾聴力", "質問力", "共感力", "ラポール形成", "非言語読解力", "フィードバック力", "アサーション", "コンフリクト解消", "交渉・折衝力", "クロージング力", "ネットワーキング", "影響力 / 説得力", "チームビルディング", "タスクアサイン", "動機付けスキル", "コーチング", "メンタリング", "権限委譲", "心理的安全性醸成", "ダイバーシティ受容", "変革リーダーシップ", "危機管理リーダー", "合意形成 / 根回し", "フォロワーシップ", "優先順位付け", "目標管理", "納期遵守力", "WBS作成力", "マルチタスク処理", "報連相", "会議ファシリテーション", "タイムボクシング", "リソース配分力", "バッファ管理", "ToDo管理力", "GTD", "プロセス設計力", "標準化 / マニュアル化", "ボトルネック特定", "ポカヨケ設計", "5S", "PDCA", "正確性・緻密さ", "スピード・俊敏性", "ルーチン維持力", "改善提案", "ドキュメント管理", "引き継ぎスキル", "デジタルリテラシー", "情報セキュリティ", "ツール適応力", "PC操作効率", "検索スキル", "AIプロンプト力", "データリテラシー", "ノーコード活用", "DXマインド", "自動化マインド", "SNS活用力", "リモートワーク力", "自律的学習", "アンラーニング", "メタ認知", "知的探究心", "情報キュレーション", "内省", "速読・多読", "メモ書き・記録", "インプット習慣", "アウトプット習慣", "資格・専門性獲得", "教養・リベラルアーツ", "感情コントロール", "ストレス管理", "レジリエンス", "アンガーマネジメント", "規律性・時間厳守", "誠実性", "マインドフルネス", "健康管理", "エネルギー管理", "集中力", "グリット", "習慣形成力", "成長マインドセット", "当事者意識", "顧客志向", "成果志向", "ポジティブ思考", "謙虚さ", "変化適応力", "曖昧さ耐性", "プロフェッショナリズム", "チャレンジ精神", "利他・貢献心", "価値観一貫性" ]; const apiKey = ""; // Provided by execution environment const fetchWithRetry = async (response, retries = 3) => { if (response.ok) return response.json(); if (retries > 0) { await new Promise(r => setTimeout(r, 1000)); return fetchWithRetry(response, retries - 1); } throw new Error('API request failed'); }; // ========================================== // AI Functions (JSON Schemaにより堅牢化) // ========================================== const generateTemplateJSON = async (profile, existingDataText) => { const prompt = `あなたはBtoB SaaS「JOBJOIN」の組織コンサルタントAIです。企業サービス: ${profile.service}、ターゲット役割: ${profile.targetRole} ${existingDataText ? `\n【既存の業務データ】\n${existingDataText}\n\n※上記を最優先し、大・中・小項目に整理してください。\n` : ''}`; try { const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-09-2025:generateContent?key=${apiKey}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }], generationConfig: { responseMimeType: "application/json", responseSchema: { type: "OBJECT", properties: { categories: { type: "ARRAY", items: { type: "OBJECT", properties: { name: { type: "STRING" }, processes: { type: "ARRAY", items: { type: "OBJECT", properties: { name: { type: "STRING" }, tasks: { type: "ARRAY", items: { type: "STRING" } } } } } } } } } } } }) }); const result = await fetchWithRetry(response); return JSON.parse(result.candidates?.[0]?.content?.parts?.[0]?.text || "{}"); } catch (error) { return null; } }; const generateTaskSuggestions = async (processName, existingTasks) => { const existingText = existingTasks.map(t => t.name).join('\n'); const prompt = `中項目「${processName}」において、現在以下のタスクがあります。\n${existingText}\n次に続くべきタスク候補を3〜5個、箇条書き記号なしで改行区切りで出力してください。`; try { const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-09-2025:generateContent?key=${apiKey}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] }) }); const result = await fetchWithRetry(response); const text = result.candidates?.[0]?.content?.parts?.[0]?.text || ""; return text.split('\n').map(s => s.trim().replace(/^[-・*]\s*/, '')).filter(s => s.length > 0).slice(0, 5); } catch (error) { return []; } }; const generateAIHint = async (taskName, bottleneckReason, stepId) => { const stepPrompts = { 'sort': 'このタスクを「無くす・自動化する」アイデアや問いかけを2〜3行で。', 'reassign': 'このタスクを「別担当へ再配置する」基準や問いかけを2〜3行で。', 'outsource': 'このタスクを「外部委託する」メリットや検討ポイントを2〜3行で。', 'train': 'このタスクを「既存社員を育成して任せる」ための教育ヒントを2〜3行で。', 'recruit': '「採用する」場合、絶対に外せないスキル要件への問いかけを2〜3行で。' }; const prompt = `タスク名: ${taskName}\n課題(理由): ${bottleneckReason}\n指示: ${stepPrompts[stepId]}`; try { const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-09-2025:generateContent?key=${apiKey}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] }) }); const result = await fetchWithRetry(response); return result.candidates?.[0]?.content?.parts?.[0]?.text || "ヒントを生成できませんでした。"; } catch (error) { return "エラーが発生しました。"; } }; const generateCapabilityJSON = async (taskName, reason, decisionLabel) => { if (!apiKey) return null; const prompt = `タスク「${taskName}」(課題: ${reason})の${decisionLabel}に必要な能力を、指定されたリストから抽出してください。`; try { const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-09-2025:generateContent?key=${apiKey}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }], generationConfig: { responseMimeType: "application/json", responseSchema: { type: "OBJECT", properties: { competencies: { type: "ARRAY", items: { type: "STRING", enum: COMPETENCY_DICT }, description: "選んだ職能3つ" }, talents: { type: "ARRAY", items: { type: "STRING", enum: TALENT_DICT }, description: "選んだ才能3つ" }, microSkills: { type: "ARRAY", items: { type: "STRING", enum: MICROSKILL_DICT }, description: "選んだスキル5つ" } } } } }) }); const result = await fetchWithRetry(response); return JSON.parse(result.candidates?.[0]?.content?.parts?.[0]?.text || "{}"); } catch (error) { throw error; } }; const generatePersonaSummary = async (competencies, talents, microSkills) => { if (!apiKey) return ""; const prompt = `以下の能力要件(職能・才能・マイクロスキル)を持つ人材像(ペルソナ)を、採用ターゲットまたは育成のゴールとして、魅力的でわかりやすい3〜4行のサマリー文章にまとめてください。 職能: ${competencies.join(", ")}\n才能: ${talents.join(", ")}\nマイクロスキル: ${microSkills.join(", ")}`; try { const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-09-2025:generateContent?key=${apiKey}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] }) }); const result = await fetchWithRetry(response); return result.candidates?.[0]?.content?.parts?.[0]?.text || "サマリーを生成できませんでした。"; } catch (error) { return "エラーが発生しました。"; } }; // ========================================== // Main Application Component // ========================================== export default function App() { const [user, setUser] = useState(null); const [authLoading, setAuthLoading] = useState(true); // Firestore Data State const [profile, setProfile] = useState(null); const [categories, setCategories] = useState([]); const [processes, setProcesses] = useState([]); const [tasks, setTasks] = useState([]); const [bottlenecks, setBottlenecks] = useState([]); const [decisions, setDecisions] = useState([]); const [personas, setPersonas] = useState([]); const [kpis, setKpis] = useState([]); const [reviews, setReviews] = useState([]); // UI State const [currentPath, setCurrentPath] = useState('loading'); const [isInitializing, setIsInitializing] = useState(true); // Initialize Auth & Global Listeners useEffect(() => { const initAuth = async () => { try { if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) { await signInWithCustomToken(auth, __initial_auth_token); } else { await signInAnonymously(auth); } } catch (error) { console.error("Auth error:", error); } }; initAuth(); const unsubscribe = onAuthStateChanged(auth, (u) => { setUser(u); setAuthLoading(false); }); return () => unsubscribe(); }, []); useEffect(() => { if (!user) return; const fetchProfile = async () => { const docSnap = await getDoc(doc(db, 'artifacts', appId, 'users', user.uid, 'settings', 'profile')); if (docSnap.exists()) { setProfile(docSnap.data()); setCurrentPath('dashboard'); } else { setCurrentPath('setup'); } setIsInitializing(false); }; fetchProfile(); const uPath = (col) => collection(db, 'artifacts', appId, 'users', user.uid, col); const unsubs = [ onSnapshot(uPath('categories'), (s) => setCategories(s.docs.map(d => ({ id: d.id, ...d.data() }))), console.error), onSnapshot(uPath('processes'), (s) => setProcesses(s.docs.map(d => ({ id: d.id, ...d.data() }))), console.error), onSnapshot(uPath('tasks'), (s) => setTasks(s.docs.map(d => ({ id: d.id, ...d.data() }))), console.error), onSnapshot(uPath('bottlenecks'), (s) => setBottlenecks(s.docs.map(d => ({ id: d.id, ...d.data() }))), console.error), onSnapshot(uPath('decisions'), (s) => setDecisions(s.docs.map(d => ({ id: d.id, ...d.data() }))), console.error), onSnapshot(uPath('personas'), (s) => setPersonas(s.docs.map(d => ({ id: d.id, ...d.data() }))), console.error), onSnapshot(uPath('kpis'), (s) => setKpis(s.docs.map(d => ({ id: d.id, ...d.data() }))), console.error), onSnapshot(uPath('reviews'), (s) => setReviews(s.docs.map(d => ({ id: d.id, ...d.data() }))), console.error), ]; return () => unsubs.forEach(unsub => unsub()); }, [user]); if (authLoading || isInitializing) return