ご感想アンケート
import React, { useState } from ‘react’;
import { Sparkles, Copy, RefreshCw, Check, ChevronRight, MapPin, User, Stethoscope, Smile, ThumbsUp, ClipboardList } from ‘lucide-react’;
// ==========================================
// ★設定エリア:ここにAPIキーを入力してください
// ==========================================
// 患者様のスマホで動作させるため、ここに直接キーを記述します。
// ※Web公開する際は、Google Cloud Consoleでこのキーに対して「リファラー制限(公開URLのみ許可)」を設定することを強く推奨します。
const GEMINI_API_KEY = “AIzaSyCn6feb9Uw2E2Gkhnt37nB9uEdBv4wpjiw”; // 例: “AIzaSy…”
const CLINIC_INFO = {
name: “カイロプラクティック健寿 岡山大元院”,
reviewUrl: “https://g.page/r/CZEKJ4dn49BcEBM/review”, // 口コミ投稿用リンク
keywords: [“岡山”, “整体”, “自律神経”, “首こり”, “肩こり”, “改善”, “おすすめ”, “ストレートネック”],
};
// ==========================================
// 質問データの定義
const QUESTIONS = [
{
id: ‘symptoms’,
title: ‘Q1. 気になる症状・お悩みは?’,
subtitle: ‘当てはまるものを全て選んでください’,
icon:
,
type: ‘multi’,
options: [
“首こり”, “肩こり”, “頭痛・頭重感”, “眠れない・不眠”,
“疲れが取れない”, “めまい・ふらつき”, “やる気が出ない”,
“背中・腰の痛み”, “自律神経の乱れ”, “病院で異常なし”, “姿勢が悪い”
]
},
{
id: ‘changes’,
title: ‘Q2. 施術後の変化・感想は?’,
subtitle: ‘施術を受けてどう感じましたか?’,
icon:
,
type: ‘multi’,
options: [
“体が軽くなった”, “痛みが和らいだ”, “頭がスッキリした”,
“視界が明るくなった”, “リラックスできた”, “ポカポカ温まった”,
“動きやすくなった”, “姿勢が良くなった”, “呼吸が深くなった”
]
},
{
id: ‘goodPoints’,
title: ‘Q3. 当院の良かった点は?’,
subtitle: ‘印象に残ったことがあれば教えてください’,
icon:
,
type: ‘multi’,
options: [
“説明が分かりやすい”, “話をよく聞いてくれる”, “負担が少なかった”,
“先生が話しやすい”, “院内がきれい”, “対応が丁寧”,
“原因が分かった”, “予約が取りやすい”
]
},
{
id: ‘demographics’,
title: ‘Q4. 年代・性別を教えてください’,
subtitle: ‘集計のために使用します’,
icon:
,
type: ‘demographics’,
ages: [“10代”, “20代”, “30代”, “40代”, “50代”, “60代以上”],
genders: [“女性”, “男性”, “無回答”]
}
];
export default function ReviewGeneratorAppMobile() {
const [step, setStep] = useState(0); // 0:Welcome, 1-4:Questions, 5:Loading, 6:Result
// 回答データを管理するState
const [answers, setAnswers] = useState({
symptoms: [],
changes: [],
goodPoints: [],
age: ”,
gender: ”,
freeText: ”
});
const [generatedReviews, setGeneratedReviews] = useState([]);
const [error, setError] = useState(”);
const [copiedIndex, setCopiedIndex] = useState(null);
// 複数選択の切り替えロジック
const toggleOption = (qId, option) => {
setAnswers(prev => {
const currentList = prev[qId] || [];
if (currentList.includes(option)) {
return { …prev, [qId]: currentList.filter(item => item !== option) };
} else {
return { …prev, [qId]: […currentList, option] };
}
});
};
// 年代・性別の選択ロジック
const setDemographic = (type, value) => {
setAnswers(prev => ({ …prev, [type]: value }));
};
// コピー処理
const handleCopy = (text, index) => {
const textArea = document.createElement(“textarea”);
textArea.value = text;
textArea.style.position = “fixed”;
textArea.style.left = “-9999px”;
textArea.style.top = “0”;
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand(‘copy’);
document.body.removeChild(textArea);
if (successful) {
setCopiedIndex(index);
setTimeout(() => setCopiedIndex(null), 2000);
return;
}
} catch (err) {
document.body.removeChild(textArea);
}
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text)
.then(() => {
setCopiedIndex(index);
setTimeout(() => setCopiedIndex(null), 2000);
})
.catch(err => {
console.error(‘Copy failed’, err);
alert(‘コピーに失敗しました。手動でコピーしてください。’);
});
} else {
alert(‘お使いの環境では自動コピーがサポートされていません。’);
}
};
// プロンプト作成関数
const createPrompt = () => {
const demo = `${answers.age} ${answers.gender}`.trim() || “未回答”;
const symptoms = answers.symptoms.join(‘、’);
const changes = answers.changes.join(‘、’);
const goodPoints = answers.goodPoints.join(‘、’);
return `
# 役割
あなたは「${CLINIC_INFO.name}」に通う患者です。
これからGoogleマップに投稿するための口コミを作成します。
# 患者データ
– 年代・性別: ${demo}
– 悩み: ${symptoms}
– 変化: ${changes}
– 良かった点: ${goodPoints}
# 必須条件
1. 文字数は300〜450文字程度。
2. MEOキーワード[${CLINIC_INFO.keywords.join(‘, ‘)}]を文脈の中で自然に使用すること。
3. 文体は自然な日本語とし、${demo}という属性はあくまで参考程度にとどめること(過剰な役割演技や、不自然な若者言葉・絵文字の多用は避ける)。
4. 箇条書きではなく、自然な体験談として書くこと。
5. **重要: 適度に改行を入れて、スマホで読みやすい。ということを意識すること。**
6. 「心からおすすめできる」など、サクラに見えるような過剰な表現はしないこと
# 成果物
以下の3パターンの口コミ案を作成してください。冒頭の挨拶は不要です。本文のみを出力してください。
フォーマットはJSON形式で、keysは “patternA”, “patternB”, “patternC” としてください。
{“patternA”: “…”, “patternB”: “…”, “patternC”: “…”}
`;
};
// Gemini API呼び出し
const generateReview = async () => {
setStep(5); // Loading画面へ
// APIキーがない場合はデモモード
if (!GEMINI_API_KEY) {
setTimeout(() => {
setGeneratedReviews([
{ title: “パターンA”, content: “(デモモード:APIキー未設定)\n岡山で自律神経専門の整体を探して、健寿さんに伺いました。\n\nずっと首こりと頭痛に悩んでいましたが、施術を受けて本当に体が軽くなりました!ボキボキしない優しい施術で、先生の説明もとても分かりやすかったです。\n\n院内も静かでリラックスできました。同じように悩んでいる方におすすめです。” },
{ title: “パターンB”, content: “(デモモード)\n仕事の疲れが取れず、眠れない日が続いていたのが嘘のようです。\n\n施術が終わった瞬間、視界がパッと明るくなり、重かった背中がスッキリしました。先生がじっくり話を聞いてくれたので安心感も抜群です。\n\n岡山の整体ならここをおすすめします!” },
{ title: “パターンC”, content: “(デモモード)\n丁寧な対応ありがとうございました。\n\n首の痛みが和らぎ、久しぶりにぐっすり眠れそうです。自律神経のケアの大切さがよく分かりました。\n\nまた通わせていただきます。” }
]);
setStep(6);
}, 1500);
return;
}
try {
const prompt = createPrompt();
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${GEMINI_API_KEY}`, {
method: ‘POST’,
headers: { ‘Content-Type’: ‘application/json’ },
body: JSON.stringify({
contents: [{ parts: [{ text: prompt }] }],
generationConfig: { responseMimeType: “application/json” }
})
});
const data = await response.json();
if (data.error) throw new Error(data.error.message);
const text = data.candidates[0].content.parts[0].text;
const json = JSON.parse(text);
setGeneratedReviews([
{ title: “パターンA”, content: json.patternA || text },
{ title: “パターンB”, content: json.patternB || text },
{ title: “パターンC”, content: json.patternC || text }
]);
setStep(6);
} catch (e) {
setError(“エラーが発生しました: ” + e.message);
setStep(6);
}
};
// — UIコンポーネント —
// 1. ウェルカム画面
if (step === 0) {
return (
{CLINIC_INFO.name}
施術後のアンケート
本日はご来院ありがとうございます。
より良い施術を提供するため、
簡単なアンケートにご協力をお願いいたします。
(所要時間:約1分)
);
}
// 2. 質問画面 (Step 1-4)
if (step >= 1 && step <= 4) {
const qIndex = step - 1;
const question = QUESTIONS[qIndex];
let canProceed = false;
if (question.type === 'demographics') {
canProceed = answers.age !== '' && answers.gender !== '';
} else {
canProceed = (answers[question.id] || []).length > 0;
}
return (
{question.icon}
STEP {step}/4
{question.title}
{question.subtitle}
{question.type === ‘demographics’ ? (
年代
{question.ages.map(age => (
))}
性別
{question.genders.map(gender => (
))}
) : (
{question.options.map((option) => {
const isSelected = (answers[question.id] || []).includes(option);
return (
);
})}
)}
{step > 1 && (
)}
);
}
// 3. ローディング画面
if (step === 5) {
return (
回答を送信しています…
ご協力ありがとうございます。
結果をまとめています。
);
}
// 4. 結果画面
if (step === 6) {
return (
ご協力ありがとうございました
もしよろしければ、以下の文章をコピーして
投稿いただけると大変励みになります。
(横にスワイプして選べます)
{error && (
{error}
)}
{generatedReviews.map((review, index) => (
{review.title}
{review.content}
))}
Googleマップで投稿して応援する
);
}
}