// ===== 店舗設定 =====
function StorePage({ settings, onSetSettings, customers, onSetCustomers }) {
  const [csvImport, setCsvImport] = useState(null); // { rows, file } or null
  const [form, setForm] = useState(() => JSON.parse(JSON.stringify(settings)));
  // settings が後から（cloud bootstrap で）更新されたら form も追従
  // 編集中のフィールドは保持しつつ、未管理フィールド（adChannels/monthlyFixedCosts等）は最新値で上書き
  const lastSettingsRef = React.useRef(JSON.stringify(settings));
  React.useEffect(() => {
    const cur = JSON.stringify(settings);
    if (cur !== lastSettingsRef.current) {
      lastSettingsRef.current = cur;
      setForm(prev => {
        // 編集中のキー（store.jsx で UI 提供しているもの）は prev 優先で保護
        // それ以外（adChannels/adCostByChannel/monthlyFixedCosts/monthlyFixedCost/maxCustomersPerDay 等）は settings から最新を取る
        const PROTECTED_KEYS = ['storeName','storeNameEn','phone','address','hours','staff','notifyNewBooking','notifyCancel','notifySound','adChannels','monthlyFixedCosts','maxCustomersPerDay','monthlyFixedCost','icalSecret','counselingFormUrl','salonEmail'];
        const next = { ...settings };
        // PROTECTED_KEYS に prev 側の編集状態がある場合は優先（保存前の編集を消さない）
        // ただし「初回ロード後の自動同期」では prev は settings の初期値と同じはずなので、ほぼ settings に揃う
        for (const k of PROTECTED_KEYS) {
          if (prev[k] !== undefined && JSON.stringify(prev[k]) !== JSON.stringify(settings[k])) {
            // prev に編集差分がある → 保持
            next[k] = prev[k];
          }
        }
        return next;
      });
    }
  }, [settings]);
  const [savedAt, setSavedAt] = useState(null);
  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));

  const updateHour = (idx, key, val) => {
    setForm(f => ({
      ...f,
      hours: f.hours.map((h, i) => i === idx ? { ...h, [key]: val } : h)
    }));
  };
  const addStaff = () => {
    setForm(f => ({
      ...f,
      staff: [...f.staff, { id: 'st-' + Date.now(), name: '新しいスタッフ', role: 'セラピスト', color: '#5b8259', active: true }]
    }));
  };
  const removeStaff = (id) => {
    if (!confirm('このスタッフを削除しますか？')) return;
    setForm(f => ({ ...f, staff: f.staff.filter(s => s.id !== id) }));
  };
  const updateStaff = (id, key, val) => {
    setForm(f => ({ ...f, staff: f.staff.map(s => s.id === id ? { ...s, [key]: val } : s) }));
  };

  const save = () => {
    onSetSettings(form);
    setSavedAt(new Date());
    setTimeout(() => setSavedAt(null), 2500);
  };

  return (
    <div>
      <div className="two-col">
        <div>
          <div className="card" style={{marginBottom:18}}>
            <div className="card-header"><h3 className="card-title">店舗情報</h3></div>
            <div className="card-body">
              <div className="field">
                <label className="field-label">店舗名</label>
                <input className="input" value={form.storeName} onChange={e => set('storeName', e.target.value)}/>
              </div>
              <div className="field">
                <label className="field-label">店舗名（英字）</label>
                <input className="input" value={form.storeNameEn} onChange={e => set('storeNameEn', e.target.value)}/>
              </div>
              <div className="field-row">
                <div className="field">
                  <label className="field-label">電話番号</label>
                  <input className="input" value={form.phone} onChange={e => set('phone', e.target.value)}/>
                </div>
                <div className="field">
                  <label className="field-label">予約スロット（分）</label>
                  <select className="select" value={form.slotMinutes} onChange={e => set('slotMinutes', parseInt(e.target.value))}>
                    <option value={10}>10分</option>
                    <option value={15}>15分</option>
                    <option value={30}>30分</option>
                  </select>
                </div>
              </div>
              <div className="field">
                <label className="field-label">住所</label>
                <input className="input" value={form.address} onChange={e => set('address', e.target.value)}/>
              </div>
              <div className="field" style={{maxWidth:240}}>
                <label className="field-label">1日の最大施術人数（稼働率計算に使用）</label>
                <input className="input" type="number" min="1" max="30"
                  value={form.maxCustomersPerDay || 8}
                  onChange={e => set('maxCustomersPerDay', parseInt(e.target.value) || 8)}/>
                <div className="text-muted" style={{fontSize:11, marginTop:4}}>
                  経営分析の「稼働率」を「実件数 ÷（営業日数 × ここの値）」で計算します
                </div>
              </div>
              <div className="field">
                <label className="field-label">📧 サロン用メール（送信元）</label>
                <input
                  className="input"
                  type="email"
                  value={form.salonEmail || ''}
                  onChange={e => set('salonEmail', e.target.value)}
                  placeholder="physiosalon.kimura2022@gmail.com"
                />
                <div className="text-muted" style={{fontSize:11, marginTop:4}}>
                  顧客カルテの「📧 メール催促」ボタンで使われます。このアカウントでGmailにログインしている時、自動で下書きが作成されます。
                </div>
              </div>
              <div className="field">
                <label className="field-label">📋 カウンセリングシート（GoogleフォームURL）</label>
                <input
                  className="input"
                  type="url"
                  value={form.counselingFormUrl || ''}
                  onChange={e => set('counselingFormUrl', e.target.value)}
                  placeholder="https://forms.gle/xxxxxxxx または https://docs.google.com/forms/d/e/..."
                />
                <div className="text-muted" style={{fontSize:11, marginTop:4}}>
                  顧客詳細・予約詳細から「カウンセリングシートを送る」ボタンで使用されます。LINEや手動メールに貼り付けて送信できます。
                </div>
              </div>
            </div>
          </div>

          {/* ============ 月固定費の管理 ============ */}
          <div className="card">
            <div className="card-header"><h3 className="card-title">月固定費の管理（営業利益計算に使用）</h3></div>
            <div className="card-body">
              <p className="text-soft" style={{margin:'0 0 12px', fontSize:12.5}}>
                毎月発生する固定費を項目ごとに登録します。各項目の合計が経営分析の「営業利益（概算）」に反映されます。
              </p>
              {(() => {
                // 旧形式（monthlyFixedCost: number）からの移行
                const legacyTotal = Number(form.monthlyFixedCost) || 0;
                const items = Array.isArray(form.monthlyFixedCosts) ? form.monthlyFixedCosts :
                  (legacyTotal > 0 ? [{ id: 'fc_legacy', label: '従来設定（要内訳化）', amount: legacyTotal }] : []);
                const total = items.reduce((s, it) => s + (Number(it.amount) || 0), 0);
                const updateItem = (idx, patch) => {
                  setForm(f => {
                    const arr = (Array.isArray(f.monthlyFixedCosts) ? f.monthlyFixedCosts : items).slice();
                    arr[idx] = { ...arr[idx], ...patch };
                    return { ...f, monthlyFixedCosts: arr };
                  });
                };
                const deleteItem = (idx) => {
                  if (!confirm(`「${items[idx]?.label || '項目'}」を削除しますか？`)) return;
                  setForm(f => {
                    const arr = (Array.isArray(f.monthlyFixedCosts) ? f.monthlyFixedCosts : items).slice();
                    arr.splice(idx, 1);
                    return { ...f, monthlyFixedCosts: arr };
                  });
                };
                const addItem = () => {
                  const label = prompt('固定費の項目名を入力（例：住宅ローン、光熱費、サブスク類）');
                  if (!label || !label.trim()) return;
                  const amountStr = prompt(`「${label.trim()}」の月額（円）を入力`, '0');
                  if (amountStr === null) return;
                  const amount = parseInt(String(amountStr).replace(/[^0-9]/g, ''), 10) || 0;
                  const id = 'fc_' + Date.now().toString(36);
                  setForm(f => {
                    const arr = (Array.isArray(f.monthlyFixedCosts) ? f.monthlyFixedCosts : items).slice();
                    arr.push({ id, label: label.trim(), amount });
                    return { ...f, monthlyFixedCosts: arr };
                  });
                };
                return (
                  <>
                    <div className="table-wrap">
                      <table className="table" style={{fontSize:12.5}}>
                        <thead>
                          <tr>
                            <th>項目名</th>
                            <th style={{width:140, textAlign:'right'}}>月額</th>
                            <th style={{width:80}}></th>
                          </tr>
                        </thead>
                        <tbody>
                          {items.length === 0 ? (
                            <tr><td colSpan="3" className="text-muted" style={{textAlign:'center', padding:'20px 0'}}>
                              項目がありません。下のボタンから追加してください。
                            </td></tr>
                          ) : items.map((it, i) => (
                            <tr key={it.id || i}>
                              <td>
                                <input className="input" style={{height:28, padding:'2px 8px', fontSize:12.5}}
                                  value={it.label || ''} onChange={e => updateItem(i, { label: e.target.value })}/>
                              </td>
                              <td>
                                <input className="input mono" type="number" min="0" step="100"
                                  style={{height:28, padding:'2px 8px', fontSize:12.5, textAlign:'right'}}
                                  value={it.amount || 0}
                                  onChange={e => updateItem(i, { amount: parseInt(e.target.value, 10) || 0 })}/>
                              </td>
                              <td>
                                <button className="btn sm danger" onClick={() => deleteItem(i)} title="削除">
                                  <Icon name="trash" size={12}/>
                                </button>
                              </td>
                            </tr>
                          ))}
                        </tbody>
                        <tfoot>
                          <tr style={{background:'var(--green-50)'}}>
                            <td style={{fontWeight:700}}>月固定費 合計</td>
                            <td className="mono" style={{textAlign:'right', fontWeight:800, color:'var(--green-900)'}}>¥{total.toLocaleString()}</td>
                            <td></td>
                          </tr>
                        </tfoot>
                      </table>
                    </div>
                    <div style={{display:'flex', gap:8, marginTop:12}}>
                      <button className="btn" onClick={addItem}>
                        <Icon name="plus" size={14}/> 固定費を追加
                      </button>
                    </div>
                    <p className="text-soft" style={{margin:'10px 0 0', fontSize:11}}>
                      ※ 営業利益 = 月売上 − 広告費 − 月固定費合計（¥{total.toLocaleString()}）<br/>
                      ※ 編集後は画面下部の「設定を保存」ボタンを押してください
                    </p>
                  </>
                );
              })()}
            </div>
          </div>

          <div className="card">
            <div className="card-header"><h3 className="card-title">営業時間・定休</h3></div>
            <div className="card-body">
              <div className="hours-grid">
                {form.hours.map((h, i) => (
                  <React.Fragment key={i}>
                    <div className={'day-label' + (i === 0 ? ' sun' : i === 6 ? ' sat' : '')}>{D.WEEK_LABELS[i]}曜</div>
                    <input type="time" className="input" value={h.from} disabled={!h.open} onChange={e => updateHour(i, 'from', e.target.value)}/>
                    <input type="time" className="input" value={h.to} disabled={!h.open} onChange={e => updateHour(i, 'to', e.target.value)}/>
                    <div style={{display:'flex', alignItems:'center', gap:8}}>
                      <Toggle value={h.open} onChange={v => updateHour(i, 'open', v)}/>
                      <span style={{fontSize:11, color:h.open ? 'var(--green-800)' : 'var(--ink-mute)'}}>{h.open ? '営業' : '定休'}</span>
                    </div>
                  </React.Fragment>
                ))}
              </div>
              <div className="settings-row" style={{marginTop:14, paddingTop:14, borderTop:'1px dashed var(--line)'}}>
                <div className="settings-row-info">
                  <div className="settings-row-title">日本の祝日は休みにする</div>
                  <div className="settings-row-desc">国民の祝日（元日・成人の日・昭和の日など）を自動で定休日扱いにします。LP予約フォームでも予約不可になります。</div>
                </div>
                <Toggle value={form.holidaysClosed !== false} onChange={v => set('holidaysClosed', v)}/>
              </div>
            </div>
          </div>
        </div>

        <div>
          <div className="card" style={{marginBottom:18}}>
            <div className="card-header">
              <h3 className="card-title">スタッフ</h3>
              <button className="btn sm" style={{marginLeft:'auto'}} onClick={addStaff}><Icon name="plus" size={12}/> 追加</button>
            </div>
            <div className="card-body">
              {form.staff.map(s => (
                <div key={s.id} style={{display:'flex', alignItems:'center', gap:10, padding:'10px 0', borderBottom:'1px solid var(--line-soft)'}}>
                  <div style={{width:30, height:30, background:s.color, borderRadius:8, display:'grid', placeItems:'center', color:'#fff', fontWeight:700, fontSize:12, flexShrink:0}}>
                    {s.name.charAt(0)}
                  </div>
                  <div style={{flex:1, minWidth:0}}>
                    <input className="input" style={{padding:'4px 8px', fontSize:12.5, marginBottom:3}} value={s.name} onChange={e => updateStaff(s.id, 'name', e.target.value)}/>
                    <input className="input" style={{padding:'4px 8px', fontSize:11.5}} value={s.role} onChange={e => updateStaff(s.id, 'role', e.target.value)}/>
                  </div>
                  <Toggle value={s.active} onChange={v => updateStaff(s.id, 'active', v)}/>
                  <button className="btn sm danger icon-only" onClick={() => removeStaff(s.id)}><Icon name="trash" size={12}/></button>
                </div>
              ))}
            </div>
          </div>

          <div className="card">
            <div className="card-header">
              <h3 className="card-title">集客チャネル管理</h3>
            </div>
            <div className="card-body">
              <p className="text-soft" style={{margin:'0 0 12px', fontSize:12.5}}>
                顧客の流入経路と広告費CPAの計算に使われるチャネル一覧。新しい媒体を追加したり、不要なものを削除できます。
              </p>
              {(() => {
                const channels = Array.isArray(form.adChannels) && form.adChannels.length > 0 ? form.adChannels : (window.DEFAULT_AD_CHANNELS || []);
                const updateChannel = (idx, patch) => {
                  setForm(f => {
                    const arr = (Array.isArray(f.adChannels) && f.adChannels.length > 0 ? f.adChannels : (window.DEFAULT_AD_CHANNELS || [])).slice();
                    arr[idx] = { ...arr[idx], ...patch };
                    return { ...f, adChannels: arr };
                  });
                };
                const deleteChannel = (idx) => {
                  const ch = channels[idx];
                  const using = customers.filter(c => c.acquisitionSource === ch.label).length;
                  const msg = using > 0
                    ? `「${ch.label}」を削除します。\n${using}名のお客様がこのチャネルを使用中です。\n削除すると流入経路が空欄扱いになります。続行しますか？`
                    : `「${ch.label}」を削除しますか？`;
                  if (!confirm(msg)) return;
                  setForm(f => {
                    const arr = (Array.isArray(f.adChannels) && f.adChannels.length > 0 ? f.adChannels : (window.DEFAULT_AD_CHANNELS || [])).slice();
                    arr.splice(idx, 1);
                    return { ...f, adChannels: arr };
                  });
                };
                const addChannel = (isPaid) => {
                  const label = prompt(isPaid ? '新しい費用発生チャネル名を入力（例：折込チラシB社）' : '新しいゼロ円チャネル名を入力（例：DM）');
                  if (!label || !label.trim()) return;
                  const trimmed = label.trim();
                  if (channels.find(c => c.label === trimmed)) { alert('同じ名前のチャネルが既に存在します'); return; }
                  const key = 'ch_' + Date.now().toString(36);
                  setForm(f => {
                    const arr = (Array.isArray(f.adChannels) && f.adChannels.length > 0 ? f.adChannels : (window.DEFAULT_AD_CHANNELS || [])).slice();
                    arr.push({ key, label: trimmed, isPaid });
                    return { ...f, adChannels: arr };
                  });
                };
                return (
                  <>
                    <div className="table-wrap">
                      <table className="table" style={{fontSize:12.5}}>
                        <thead>
                          <tr>
                            <th>チャネル名</th>
                            <th style={{width:130}}>区分</th>
                            <th style={{width:90, textAlign:'right'}}>使用顧客数</th>
                            <th style={{width:80}}></th>
                          </tr>
                        </thead>
                        <tbody>
                          {channels.map((ch, i) => {
                            const using = customers.filter(c => c.acquisitionSource === ch.label).length;
                            return (
                              <tr key={ch.key || i}>
                                <td>
                                  <input className="input" style={{height:28, padding:'2px 8px', fontSize:12.5}}
                                    value={ch.label} onChange={e => updateChannel(i, { label: e.target.value })}/>
                                </td>
                                <td>
                                  <select className="select" style={{height:28, padding:'2px 8px', fontSize:12}}
                                    value={ch.isPaid ? '1' : '0'} onChange={e => updateChannel(i, { isPaid: e.target.value === '1' })}>
                                    <option value="1">費用発生</option>
                                    <option value="0">ゼロ円</option>
                                  </select>
                                </td>
                                <td className="mono" style={{textAlign:'right', color: using > 0 ? 'var(--green-900)' : 'var(--ink-mute)'}}>
                                  {using}名
                                </td>
                                <td>
                                  <button className="btn sm danger" onClick={() => deleteChannel(i)} title="削除">
                                    <Icon name="trash" size={12}/>
                                  </button>
                                </td>
                              </tr>
                            );
                          })}
                        </tbody>
                      </table>
                    </div>
                    <div style={{display:'flex', gap:8, marginTop:12}}>
                      <button className="btn" onClick={() => addChannel(true)}>
                        <Icon name="plus" size={14}/> 費用発生チャネルを追加
                      </button>
                      <button className="btn" onClick={() => addChannel(false)}>
                        <Icon name="plus" size={14}/> ゼロ円チャネルを追加
                      </button>
                    </div>
                    <p className="text-soft" style={{margin:'10px 0 0', fontSize:11}}>
                      ※「費用発生」=月別広告費を入力してCPAを計算 ／「ゼロ円」=人数のみ集計<br/>
                      ※ 編集後は画面下部の「設定を保存」ボタンを押してください。
                    </p>
                  </>
                );
              })()}
            </div>
          </div>

          <div className="card">
            <div className="card-header"><h3 className="card-title">通知設定</h3></div>
            <div className="card-body">
              <div className="settings-row">
                <div className="settings-row-info">
                  <div className="settings-row-title">新規予約の通知</div>
                  <div className="settings-row-desc">LP経由で予約が入ったときに通知します</div>
                </div>
                <Toggle value={form.notifyNewBooking} onChange={v => set('notifyNewBooking', v)}/>
              </div>
              <div className="settings-row">
                <div className="settings-row-info">
                  <div className="settings-row-title">通知音</div>
                  <div className="settings-row-desc">通知時に音を再生します</div>
                </div>
                <Toggle value={form.notifySound} onChange={v => set('notifySound', v)}/>
              </div>
            </div>
          </div>

          <div className="card">
            <div className="card-header"><h3 className="card-title">Google Calendar 連携</h3></div>
            <div className="card-body">
              <div className="settings-row" style={{flexDirection:'column', alignItems:'stretch'}}>
                <div className="settings-row-info" style={{marginBottom:10}}>
                  <div className="settings-row-title">予約をGoogleカレンダーで見る</div>
                  <div className="settings-row-desc">
                    下のURLを Google カレンダーの「URLで追加」に登録すると、admin の予約が自動で同期表示されます（一方向・読み取り専用）。家族と共有しているカレンダーにも表示できます。
                  </div>
                </div>
                {form.icalSecret ? (
                  <div style={{display:'grid', gap:8}}>
                    <div style={{padding:'8px 12px', background:'var(--green-50)', borderRadius:6, fontFamily:'monospace', fontSize:11.5, wordBreak:'break-all'}}>
                      {`${D.API_BASE}/api/calendar/${form.icalSecret}.ics`}
                    </div>
                    <div style={{display:'flex', gap:8}}>
                      <button className="btn" onClick={() => {
                        const url = `${D.API_BASE}/api/calendar/${form.icalSecret}.ics`;
                        navigator.clipboard.writeText(url);
                        alert('URLをコピーしました');
                      }}><Icon name="check" size={14}/> URLをコピー</button>
                      <button className="btn" onClick={() => {
                        if (!confirm('URLを再発行すると、現在のURLが無効になります。Googleカレンダー側で再登録が必要です。よろしいですか？')) return;
                        const newSecret = ('cal_' + Math.random().toString(36).slice(2, 10) + Math.random().toString(36).slice(2, 10));
                        set('icalSecret', newSecret);
                        // 即保存
                        onSetSettings(prev => ({ ...prev, icalSecret: newSecret }));
                      }}>URLを再発行</button>
                    </div>
                    <div style={{fontSize:11, color:'var(--ink-mute)', lineHeight:1.6}}>
                      <strong>使い方：</strong><br/>
                      1. 上のURLをコピー<br/>
                      2. Googleカレンダー（PC版）を開く<br/>
                      3. 左メニュー「他のカレンダー」の右の「+」→「URLで追加」<br/>
                      4. URLを貼り付けて「カレンダーを追加」<br/>
                      ※ Googleカレンダー側の更新は数時間ごと（即時ではありません）
                    </div>
                  </div>
                ) : (
                  <button className="btn primary" style={{alignSelf:'flex-start'}} onClick={() => {
                    const newSecret = ('cal_' + Math.random().toString(36).slice(2, 10) + Math.random().toString(36).slice(2, 10));
                    set('icalSecret', newSecret);
                    onSetSettings(prev => ({ ...prev, icalSecret: newSecret }));
                  }}>
                    <Icon name="plus" size={14}/> 連携URLを発行
                  </button>
                )}
              </div>
            </div>
          </div>

          <div className="card">
            <div className="card-header"><h3 className="card-title">データ管理</h3></div>
            <div className="card-body">
              {/* 全データバックアップ（災害対策） */}
              <div className="settings-row" style={{background:'#f4f8f0', padding:'12px 14px', borderRadius:8, border:'2px solid #c8d8be'}}>
                <div className="settings-row-info">
                  <div className="settings-row-title" style={{color:'var(--green-900)'}}>🛡️ 全データをCSVバックアップ（災害対策）</div>
                  <div className="settings-row-desc">クラウドが壊れても、スプレッドシートで運用継続できるよう全データをCSVで一括ダウンロードします。<br/>顧客・予約履歴・回数券の3ファイル（毎月実行を推奨）</div>
                </div>
                <button className="btn primary" onClick={() => downloadFullBackup()} title="顧客・予約・チケットの全データをCSVで一括DL">
                  <Icon name="check" size={14}/> 全データをDL
                </button>
              </div>
              <div className="settings-row">
                <div className="settings-row-info">
                  <div className="settings-row-title">顧客 CSV インポート</div>
                  <div className="settings-row-desc">スプレッドシートで作った顧客リスト（CSV）から一括登録します。電話番号で重複検出。</div>
                </div>
                <div style={{display:'flex', gap:8, flexWrap:'wrap'}}>
                  <button className="btn" onClick={() => downloadCustomerCsvTemplate()} title="ヘッダ＋サンプル2行のテンプレート">
                    <Icon name="check" size={14}/> 空テンプレートDL
                  </button>
                  <button className="btn primary" onClick={() => downloadDemoCsv()} title="50名のデモ顧客データをDL（テスト用）">
                    <Icon name="users" size={14}/> デモCSV(50名)
                  </button>
                  <input type="file" accept=".csv,text/csv" id="csv-input" style={{display:'none'}}
                    onChange={async (e) => {
                      const f = e.target.files[0];
                      if (!f) return;
                      try {
                        const text = await f.text();
                        const rows = parseCustomerCsv(text);
                        setCsvImport({ rows, fileName: f.name });
                      } catch (err) {
                        alert('CSV読み込みエラー: ' + err.message);
                      }
                      e.target.value = ''; // 同じファイル再選択を許可
                    }}/>
                  <button className="btn primary" onClick={() => document.getElementById('csv-input').click()}>
                    <Icon name="plus" size={14}/> CSVをインポート
                  </button>
                </div>
              </div>
              <div className="settings-row">
                <div className="settings-row-info">
                  <div className="settings-row-title">孤立予約のクリーンアップ</div>
                  <div className="settings-row-desc">顧客紐付けが切れた予約を一括削除します。練習用データの掃除に使えます。</div>
                </div>
                <button className="btn danger" onClick={async () => {
                  if (!confirm('顧客紐付けがない予約を一括削除しますか？\n\n（顧客削除後に「予約は残す」を選んだ場合の予約や、テスト投入で残った予約が対象）')) return;
                  try {
                    const API_BASE = D.API_BASE;
                    const r = await D.apiFetch(`${API_BASE}/api/bookings/orphaned`, { method: 'DELETE' });
                    const j = await r.json();
                    alert(`削除完了：${j.deleted}件 の孤立予約を削除しました。\n画面を再読み込みします。`);
                    setTimeout(() => window.location.reload(), 200);
                  } catch (e) {
                    alert('削除に失敗しました: ' + e.message);
                  }
                }}>
                  <Icon name="trash" size={14}/> 孤立予約を削除
                </button>
              </div>
              <div className="settings-row" style={{borderTop:'1px dashed var(--border)', paddingTop:14, marginTop:6}}>
                <div className="settings-row-info">
                  <div className="settings-row-title" style={{color:'var(--red)'}}>顧客リストを全削除（デモ/練習データの一括クリア）</div>
                  <div className="settings-row-desc">登録されている顧客を全件削除します。本番運用前のデモデータ一括クリア用です。実行には確認入力が必要です。</div>
                </div>
                <button className="btn danger" onClick={async () => {
                  const phrase = prompt('⚠️ 顧客リストを全削除します。\n\n削除すると元に戻せません。\n本当に実行する場合は、下に「全削除」と入力してください。');
                  if (phrase !== '全削除') {
                    if (phrase !== null) alert('入力が一致しないため、削除を中止しました。');
                    return;
                  }
                  const cascade = confirm('関連する予約も一緒に削除しますか？\n\n「OK」→ 顧客＋予約を全削除\n「キャンセル」→ 顧客のみ削除（予約は履歴として残る）');
                  try {
                    const API_BASE = D.API_BASE;
                    const url = `${API_BASE}/api/customers/all` + (cascade ? '?cascade=1' : '');
                    const r = await D.apiFetch(url, { method: 'DELETE' });
                    const j = await r.json();
                    alert(`削除完了\n顧客：${j.customersDeleted}件\n予約：${j.bookingsAffected}件 ${cascade ? '削除' : '紐付け解除'}\n\n画面を再読み込みします。`);
                    setTimeout(() => window.location.reload(), 200);
                  } catch (e) {
                    alert('削除に失敗しました: ' + e.message);
                  }
                }}>
                  <Icon name="trash" size={14}/> 顧客を全削除
                </button>
              </div>
              <div className="settings-row" style={{borderTop:'1px dashed var(--border)', paddingTop:14, marginTop:6}}>
                <div className="settings-row-info">
                  <div className="settings-row-title" style={{color:'var(--orange-dark, #a87320)'}}>広告費を一括クリア</div>
                  <div className="settings-row-desc">経営分析→チャネル別広告費に入力した金額をすべて消去します。デモ用データのリセットなどに使用。</div>
                </div>
                <button className="btn" style={{background:'#fcefdf', color:'#8a6a10', borderColor:'#e8c889'}} onClick={async () => {
                  if (!confirm('入力済みの広告費（全月・全チャネル分）をすべて消去しますか？\n\n※ チャネル定義は残ります。広告費の入力値だけが消えます。')) return;
                  try {
                    const API_BASE = D.API_BASE;
                    const cur = await (await D.apiFetch(`${API_BASE}/api/settings`)).json();
                    const next = { ...cur, adCostByChannel: {} };
                    await D.apiFetch(`${API_BASE}/api/settings`, {
                      method: 'PUT', headers: {'Content-Type':'application/json'}, body: JSON.stringify(next)
                    });
                    alert('広告費を一括クリアしました。\n画面を再読み込みします。');
                    setTimeout(() => window.location.reload(), 200);
                  } catch (e) {
                    alert('クリアに失敗しました: ' + e.message);
                  }
                }}>
                  <Icon name="trash" size={14}/> 広告費を全クリア
                </button>
              </div>
              <div className="settings-row" style={{borderTop:'1px dashed var(--border)', paddingTop:14, marginTop:6}}>
                <div className="settings-row-info">
                  <div className="settings-row-title" style={{color:'var(--orange-dark, #a87320)'}}>固定費をリセット</div>
                  <div className="settings-row-desc">月固定費の値を 0 に戻します。</div>
                </div>
                <button className="btn" style={{background:'#fcefdf', color:'#8a6a10', borderColor:'#e8c889'}} onClick={async () => {
                  if (!confirm('月固定費を 0 にリセットしますか？')) return;
                  try {
                    const API_BASE = D.API_BASE;
                    const cur = await (await D.apiFetch(`${API_BASE}/api/settings`)).json();
                    const next = { ...cur, monthlyFixedCost: 0 };
                    await D.apiFetch(`${API_BASE}/api/settings`, {
                      method: 'PUT', headers: {'Content-Type':'application/json'}, body: JSON.stringify(next)
                    });
                    alert('月固定費をリセットしました。\n画面を再読み込みします。');
                    setTimeout(() => window.location.reload(), 200);
                  } catch (e) {
                    alert('リセットに失敗しました: ' + e.message);
                  }
                }}>
                  <Icon name="trash" size={14}/> 固定費リセット
                </button>
              </div>
            </div>
          </div>
        </div>
      </div>

      <div style={{position:'sticky', bottom:0, marginTop:24, padding:'14px 0', display:'flex', alignItems:'center', gap:10, justifyContent:'flex-end'}}>
        {savedAt && <span style={{color:'var(--green-700)', fontSize:12.5}}>✓ 保存しました（{savedAt.toLocaleTimeString('ja-JP', {hour:'2-digit', minute:'2-digit'})}）</span>}
        <button className="btn primary" onClick={save}><Icon name="check" size={14}/> 設定を保存</button>
      </div>

      {csvImport && (
        <CsvImportModal
          rows={csvImport.rows}
          fileName={csvImport.fileName}
          existingCustomers={customers || []}
          onClose={() => setCsvImport(null)}
          onComplete={() => setCsvImport(null)}
        />
      )}
    </div>
  );
}

// ===== CSV インポート関連 =====
const CSV_HEADERS = ['chartNo','name','kana','tel','email','postalCode','address','birthday','gender','age','acquisitionSource','visits','totalSpent','firstVisit','lastVisit','note'];
const CSV_TEMPLATE = CSV_HEADERS.join(',') + '\n'
  + 'A-001,山田 太郎,ヤマダ タロウ,090-1234-5678,sample@example.com,463-0011,名古屋市守山区守山1-1-1,1980-01-15,男性,44,新聞折込,12,144000,2024-03-15,2026-04-20,腰痛慢性\n'
  + 'A-002,佐藤 花子,サトウ ハナコ,080-2345-6789,,,,1965-08-22,女性,60,GBP / Google検索,3,21600,2025-08-10,2026-04-25,\n'
  + 'A-003,鈴木 一郎,スズキ イチロウ,090-3456-7890,,,愛知県春日井市,,男性,52,紹介,8,64000,2023-11-02,2026-04-10,常連\n';

function downloadCustomerCsvTemplate() {
  // Excelで日本語を正しく開けるよう BOM 付き UTF-8 で出力
  const blob = new Blob(['﻿' + CSV_TEMPLATE], { type: 'text/csv;charset=utf-8' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = 'customers_template.csv';
  document.body.appendChild(a);
  a.click();
  a.remove();
  URL.revokeObjectURL(url);
}

// 🛡️ 全データバックアップ：顧客・予約・チケットを CSV 3ファイルでDL
function csvEscape(v) {
  if (v == null) return '';
  const s = String(v);
  if (s.includes(',') || s.includes('"') || s.includes('\n') || s.includes('\r')) {
    return '"' + s.replace(/"/g, '""') + '"';
  }
  return s;
}
function csvDownload(filename, rows) {
  const csv = rows.map(r => r.map(csvEscape).join(',')).join('\r\n');
  const blob = new Blob(['﻿' + csv], { type: 'text/csv;charset=utf-8' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  a.remove();
  URL.revokeObjectURL(url);
}
async function downloadFullBackup() {
  try {
    const API_BASE = D.API_BASE;
    const [customers, bookings] = await Promise.all([
      D.apiFetch(API_BASE + '/api/customers').then(r => r.json()),
      D.apiFetch(API_BASE + '/api/bookings').then(r => r.json()),
    ]);
    const today = new Date().toISOString().slice(0, 10);

    // 1. 顧客リスト
    const custHeader = ['カルテNo','名前','カナ','電話','電話2','メール','郵便番号','住所','生年月日','年齢','性別','流入経路','タグ','初回来院','最終来院','来院回数','累計売上','領収書必要','支払い方法','次回宿題','カルテメモ'];
    const custRows = [custHeader, ...customers.map(c => [
      c.chartNo, c.name, c.kana, c.tel || c.phone, c.phone2, c.email,
      c.postalCode, c.address, c.birthday, c.age, c.gender, c.acquisitionSource,
      Array.isArray(c.tags) ? c.tags.join('・') : '',
      c.firstVisit, c.lastVisit, c.visits, c.totalSpent,
      c.needsReceipt ? '✓' : '', c.paymentMethod || '',
      c.pendingMemo || '', c.note || ''
    ])];
    csvDownload(`バックアップ_顧客リスト_${today}.csv`, custRows);

    // 2. 予約履歴
    await new Promise(r => setTimeout(r, 200));
    const bkHeader = ['日付','時間','所要分','顧客名','カナ','電話','メニュー','料金','状態','区分','メモ','予約ID'];
    const bkRows = [bkHeader, ...bookings
      .slice()
      .sort((a, b) => (a.date || '').localeCompare(b.date || '') || (a.time || '').localeCompare(b.time || ''))
      .map(b => {
        const cust = customers.find(c => c.id === b.customerId);
        return [
          b.date, b.time, b.duration,
          b.name || (cust && cust.name) || '',
          b.kana || (cust && cust.kana) || '',
          b.tel || b.phone || '',
          b.menu, b.price, b.status, b.source || '',
          b.note || b.memo || '', b.id
        ];
      })];
    csvDownload(`バックアップ_予約履歴_${today}.csv`, bkRows);

    // 3. 回数券・月額プラン（残あり顧客のみ）
    await new Promise(r => setTimeout(r, 200));
    const tHeader = ['顧客名','カルテNo','種類','購入日','使用済','総回数','残','単価','残合計（前受金）'];
    const tRows = [tHeader];
    for (const c of customers) {
      for (const t of (c.tickets || [])) {
        if (t.type === 'ticket10') {
          const used = t.usedCount || 0;
          const total = t.totalCount || 10;
          const rem = total - used;
          if (rem <= 0) continue; // 完全消化済はスキップ
          tRows.push([c.name, c.chartNo, '回数券10回', t.purchasedAt, used, total, rem, t.pricePerVisit, rem * (t.pricePerVisit || 0)]);
        } else if (t.type === 'monthly4') {
          const used = t.cycleUsed || 0;
          const total = t.monthlyTotal || 4;
          const rem = total - used;
          tRows.push([c.name, c.chartNo, '月額プラン', t.contractedAt || t.purchasedAt, used, total, rem, t.pricePerVisit, rem * (t.pricePerVisit || 0)]);
        }
      }
    }
    csvDownload(`バックアップ_回数券月額_${today}.csv`, tRows);

    alert(`✅ バックアップ完了！\n\n3つのCSVファイルがダウンロードされました：\n・顧客リスト（${customers.length}名）\n・予約履歴（${bookings.length}件）\n・回数券・月額プラン（${tRows.length - 1}件）\n\nクラウド障害時の復旧用に保管してください。\n推奨：毎月1回バックアップ実行`);
  } catch (e) {
    alert('バックアップ失敗: ' + e.message);
  }
}

// 50名のデモ顧客データを生成してCSVダウンロード（テスト用）
function downloadDemoCsv() {
  const FIRST = ['健太','美咲','翔太','彩花','大樹','理沙','拓海','麻衣','悠斗','沙織','颯','遥','蓮','陽菜','陸','心愛','大和','咲良','航','花','直樹','奈々','光','由紀','晃','智子','学','千秋','和也','典子','順子','茂','靖子','勇','京子','雅','悠','洋','満','信','幸','清','広','正','一','勝','繁','豊','宏','治'];
  const LAST = ['田中','佐藤','鈴木','高橋','伊藤','渡辺','山本','中村','小林','加藤','吉田','山田','松本','井上','木村','清水','林','斉藤','山口','森','池田','橋本','石川','前田','藤田','後藤','岡田','長谷川','石井','村上'];
  const SOURCES = [['Google広告',5],['新聞折込',5],['Instagram広告',2],['SEO',1],['リベシティ',1],['GBP / Google検索',3],['紹介',4],['看板',2],['通りすがり',1],['その他',1]];
  const total = SOURCES.reduce((s,x)=>s+x[1],0);
  const pickSource = () => { let r = Math.random()*total; for (const [l,w] of SOURCES) { r-=w; if (r<=0) return l; } return SOURCES[0][0]; };
  const rand = (a,b) => Math.floor(Math.random()*(b-a+1))+a;
  const pick = a => a[Math.floor(Math.random()*a.length)];
  const today = new Date();
  const isoDate = d => d.toISOString().slice(0,10);
  const dateAdd = (d,n) => { const x = new Date(d); x.setDate(x.getDate()+n); return x; };
  const lines = [CSV_HEADERS.join(',')];
  for (let i = 0; i < 50; i++) {
    const ln = pick(LAST), fn = pick(FIRST);
    const visits = (Math.random()<0.4)?rand(1,2):(Math.random()<0.5?rand(3,6):rand(7,14));
    const monthsAgo = rand(0,12);
    const fv = dateAdd(today, -monthsAgo*30 - rand(0,29));
    const lv = visits>1 ? dateAdd(fv, rand(20,40)*(visits-1)) : fv;
    const lvFinal = lv > today ? today : lv;
    const totalSpent = 5980 + (visits-1)*8000;
    const phone = `090-${String(rand(1000,9999)).padStart(4,'0')}-${String(i).padStart(4,'0')}`;
    const row = [
      `D-${String(i+1).padStart(3,'0')}`, `${ln} ${fn}（デモ）`, '',
      phone, '', '', '', pickSource(), visits, totalSpent,
      isoDate(fv), isoDate(lvFinal), '',
    ];
    lines.push(row.map(c => { const s = String(c); return /[,"\n]/.test(s) ? `"${s.replace(/"/g,'""')}"` : s; }).join(','));
  }
  const blob = new Blob(['﻿' + lines.join('\n') + '\n'], { type: 'text/csv;charset=utf-8' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = 'demo_customers_50.csv';
  document.body.appendChild(a);
  a.click();
  a.remove();
  URL.revokeObjectURL(url);
}

function parseCsvLine(line) {
  const out = [];
  let cur = '';
  let inQ = false;
  for (let i = 0; i < line.length; i++) {
    const ch = line[i];
    if (inQ) {
      if (ch === '"' && line[i+1] === '"') { cur += '"'; i++; }
      else if (ch === '"') inQ = false;
      else cur += ch;
    } else {
      if (ch === ',') { out.push(cur); cur = ''; }
      else if (ch === '"') inQ = true;
      else cur += ch;
    }
  }
  out.push(cur);
  return out;
}

function parseCustomerCsv(text) {
  // BOM 除去
  if (text.charCodeAt(0) === 0xFEFF) text = text.slice(1);
  const lines = text.split(/\r?\n/).filter(l => l.length > 0);
  if (lines.length === 0) throw new Error('空のCSVです');
  const header = parseCsvLine(lines[0]).map(h => h.trim());
  // ヘッダー検証
  const hasName = header.includes('name');
  if (!hasName) throw new Error('1行目に「name」列が必要です。テンプレートをダウンロードしてご利用ください。');
  const rows = [];
  for (let i = 1; i < lines.length; i++) {
    const cells = parseCsvLine(lines[i]);
    const obj = {};
    header.forEach((h, idx) => {
      let v = (cells[idx] || '').trim();
      if (h === 'visits' || h === 'totalSpent' || h === 'age') {
        v = v === '' ? 0 : (parseInt(v, 10) || 0);
      }
      obj[h] = v;
    });
    if (obj.name) rows.push(obj);
  }
  return rows;
}

function CsvImportModal({ rows, fileName, existingCustomers, onClose, onComplete }) {
  const [overwrite, setOverwrite] = useState(false);
  const [importing, setImporting] = useState(false);
  const [progress, setProgress] = useState(0);
  const [result, setResult] = useState(null);

  // 重複検出マップ（電話番号下10桁）
  const phoneMap = useMemo(() => {
    const m = new Map();
    existingCustomers.forEach(c => {
      const digits = String(c.tel || c.phone || '').replace(/[^0-9]/g, '');
      if (digits.length >= 10) m.set(digits, c);
    });
    return m;
  }, [existingCustomers]);

  const previewRows = rows.slice(0, 5);
  const totalCount = rows.length;
  const dupRows = rows.filter(r => {
    const digits = String(r.tel || '').replace(/[^0-9]/g, '');
    return digits.length >= 10 && phoneMap.has(digits);
  });

  const runImport = async () => {
    setImporting(true);
    setProgress(0);
    let added = 0, skipped = 0, updated = 0, errors = 0;
    const API_BASE = D.API_BASE;
    for (let i = 0; i < rows.length; i++) {
      const row = rows[i];
      const digits = String(row.tel || '').replace(/[^0-9]/g, '');
      const existing = digits.length >= 10 ? phoneMap.get(digits) : null;
      try {
        if (existing) {
          if (!overwrite) { skipped++; }
          else {
            await D.apiFetch(`${API_BASE}/api/customers/${existing.id}`, {
              method: 'PUT',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify(row),
            });
            updated++;
          }
        } else {
          await D.apiFetch(`${API_BASE}/api/customers`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(row),
          });
          added++;
        }
      } catch (e) {
        errors++;
      }
      setProgress(i + 1);
    }
    setResult({ added, skipped, updated, errors });
    setImporting(false);
  };

  return (
    <Modal
      title={`CSVインポート（${fileName}）`}
      onClose={importing ? () => {} : onClose}
      size="lg"
      footer={
        result ? (
          <button className="btn primary" onClick={() => { onComplete && onComplete(); setTimeout(() => window.location.reload(), 200); }}>
            完了 → 画面を更新
          </button>
        ) : (
          <>
            <button className="btn" onClick={onClose} disabled={importing}>キャンセル</button>
            <button className="btn primary" onClick={runImport} disabled={importing}>
              <Icon name="check" size={14}/> {importing ? `投入中... ${progress}/${totalCount}` : `${totalCount}件を投入する`}
            </button>
          </>
        )
      }
    >
      {result ? (
        <div>
          <h4 className="card-title" style={{marginTop:0}}>インポート完了</h4>
          <div className="detail-grid">
            <dt>新規追加</dt><dd className="mono" style={{fontWeight:700, color:'var(--green-700)'}}>{result.added} 名</dd>
            <dt>更新（重複→上書き）</dt><dd className="mono">{result.updated} 名</dd>
            <dt>スキップ（重複→上書きしない）</dt><dd className="mono">{result.skipped} 名</dd>
            <dt>エラー</dt><dd className="mono" style={{color: result.errors > 0 ? '#a85a1f' : ''}}>{result.errors} 名</dd>
          </div>
        </div>
      ) : (
        <div>
          <div style={{display:'flex', gap:14, marginBottom:14, fontSize:13}}>
            <div><strong>{totalCount}</strong> 件のデータを検出</div>
            {dupRows.length > 0 && <div style={{color:'#a85a1f'}}>※ うち <strong>{dupRows.length}</strong> 件は電話番号で既存と重複</div>}
          </div>

          {dupRows.length > 0 && (
            <label style={{display:'flex', alignItems:'center', gap:8, padding:'8px 12px', background:'#fdf0d6', borderRadius:6, fontSize:12.5, marginBottom:14}}>
              <input type="checkbox" checked={overwrite} onChange={e => setOverwrite(e.target.checked)}/>
              重複時は既存データを上書きする（チェックなし＝スキップ）
            </label>
          )}

          <h4 className="card-title" style={{margin:'0 0 8px', fontSize:13.5}}>プレビュー（最初 {previewRows.length} 件）</h4>
          <div className="table-wrap" style={{maxHeight:260, overflow:'auto'}}>
            <table className="table" style={{margin:0, fontSize:11.5}}>
              <thead><tr>
                {CSV_HEADERS.map(h => <th key={h}>{h}</th>)}
              </tr></thead>
              <tbody>
                {previewRows.map((r, i) => (
                  <tr key={i}>
                    {CSV_HEADERS.map(h => <td key={h} style={{whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis', maxWidth:120}}>{r[h] || ''}</td>)}
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
          <div style={{fontSize:11, color:'var(--ink-mute)', marginTop:8}}>
            ※ 投入は1件ずつ API へ POST します。{totalCount}件で約{Math.ceil(totalCount * 0.4)}秒かかります。
          </div>
        </div>
      )}
    </Modal>
  );
}

window.StorePage = StorePage;
