PageSourceSearch

https://touseki-app.web.app/js/search.js?v=20260824b

js touseki-app.web.app collected 2026-10-03 12:38:31 UTC 81,007 bytes, 1,939 lines download raw bytes

1// 検索機能の実装
2
3// グローバル変数
4let selectedMapPosition = null;
5
6// お気に入り施設のキャッシュ(重複呼び出し防止)
7let savedFacilitiesCache = null;
8let savedFacilitiesCacheTime = 0;
9const SAVED_FACILITIES_CACHE_TTL = 2000; // 2秒間キャッシュ
10
11// loadUserFavoritesが完了したかどうかのフラグ
12window.userFavoritesLoaded = false;
13let selectedAddressPosition = null;
14let mapClickListener = null;
15let currentSearchResultIndices = null; // 現在の検索結果のインデックス
16
17// ログイン状態に応じてマイ施設オプションの有効/無効を切り替え
18function updateMyFacilityOption() {
19  const myFacilityOption = document.getElementById('myFacilityOption');
20  const myFacilityRadio = document.getElementById('myFacilityRadio');
21
22  if (window.currentUser && window.currentUser.facilityName) {
23    // ログイン中:有効化
24    myFacilityOption.classList.remove('disabled');
25    myFacilityRadio.disabled = false;
26  } else {
27    // ログアウト中:無効化
28    myFacilityOption.classList.add('disabled');
29    myFacilityRadio.disabled = true;
30
31    // もし選択されていたら、GPSに切り替え
32    if (myFacilityRadio.checked) {
33      document.querySelector('input[name="baseLocation"][value="gps"]').checked = true;
34    }
35  }
36}
37
38// 検索モーダルを開く時にオプションを更新
39window.addEventListener('DOMContentLoaded', updateMyFacilityOption);
40
41// タブ切り替え
42function switchSearchTab(tabName) {
43  // すべてのタブとコンテンツの active クラスを削除
44  document.querySelectorAll('.search-tab').forEach(tab => {
45    tab.classList.remove('active');
46  });
47  document.querySelectorAll('.search-tab-content').forEach(content => {
48    content.classList.remove('active');
49  });
50
51  // 選択されたタブとコンテンツに active クラスを追加
52  document.querySelector(`[data-tab="${tabName}"]`).classList.add('active');
53  document.getElementById(`tab-${tabName}`).classList.add('active');
54
55  // 保存済みタブが選択されたら、保存済み施設を読み込む
56  if (tabName === 'saved') {
57    loadSavedFacilities();
58  }
59
60  // 履歴タブが選択されたら、履歴を読み込む
61  if (tabName === 'history') {
62    loadSearchHistory();
63  }
64}
65
66// 位置選択の変更処理
67function handleLocationChange() {
68  const selectedValue = document.querySelector('input[name="baseLocation"]:checked').value;
69  const mapPositionInfo = document.getElementById('mapPositionInfo');
70  const addressInput = document.getElementById('addressInput');
71
72  // すべて非表示にする
73  mapPositionInfo.style.display = 'none';
74  addressInput.style.display = 'none';
75
76  if (selectedValue === 'map') {
77    // 地図選択モード - モーダルを一時的に隠す
78    mapPositionInfo.style.display = 'block';
79    const modal = document.getElementById('searchModal');
80    modal.style.display = 'none';
81
82    // 通知を表示
83    if (typeof showNotification === 'function') {
84      showNotification('地図上で位置をクリックしてください', 5000);
85    } else {
86      alert('地図上で位置をクリックしてください');
87    }
88
89    enableMapSelection();
90  } else if (selectedValue === 'address') {
91    // 住所入力モード
92    addressInput.style.display = 'block';
93    disableMapSelection();
94  } else {
95    // 他のモード
96    disableMapSelection();
97  }
98}
99
100// 住所をジオコーディング
101window.geocodeAddress = function() {
102  const addressInput = document.getElementById('searchAddress').value;
103  const addressResult = document.getElementById('addressResult');
104
105  if (!addressInput) {
106    alert('住所を入力してください');
107    return;
108  }
109
110  // geocoderを取得または作成
111  let geocoderService = window.geocoder || (typeof geocoder !== 'undefined' ? geocoder : null);
112
113  if (!geocoderService) {
114    geocoderService = new google.maps.Geocoder();
115    window.geocoder = geocoderService;
116  }
117
118  addressResult.textContent = '検索中...';
119  addressResult.style.display = 'block';
120
121  geocoderService.geocode({ address: addressInput }, (results, status) => {
122    if (status === 'OK' && results[0]) {
123      const location = results[0].geometry.location;
124      selectedAddressPosition = {
125        lat: location.lat(),
126        lng: location.lng()
127      };
128
129      addressResult.innerHTML = `✅ 住所が見つかりました<br>${results[0].formatted_address}`;
130      addressResult.style.background = '#e8f0fe';
131      addressResult.style.color = '#1a73e8';
132
133      console.log('Address geocoded:', selectedAddressPosition);
134    } else {
135      addressResult.innerHTML = '❌ 住所が見つかりませんでした。別の住所で試してください。';
136      addressResult.style.background = '#fce8e6';
137      addressResult.style.color = '#d93025';
138      selectedAddressPosition = null;
139    }
140  });
141};
142
143// 地図選択モードを有効化
144function enableMapSelection() {
145  console.log('Map selection mode enabled');
146
147  if (!window.map) {
148    console.error('Map not initialized yet');
149    return;
150  }
151
152  if (mapClickListener) {
153    // 既存のリスナーがあれば削除
154    google.maps.event.removeListener(mapClickListener);
155  }
156
157  // 地図クリックイベントを追加
158  mapClickListener = map.addListener('click', function(event) {
159    selectedMapPosition = {
160      lat: event.latLng.lat(),
161      lng: event.latLng.lng()
162    };
163
164    console.log('Selected position:', selectedMapPosition);
165
166    // 選択位置を表示
167    document.getElementById('mapPositionInfo').innerHTML =
168      `📌 選択した位置: 緯度 ${selectedMapPosition.lat.toFixed(5)}, 経度 ${selectedMapPosition.lng.toFixed(5)}`;
169
170    // モーダルを再表示
171    const modal = document.getElementById('searchModal');
172    modal.style.display = 'flex';
173
174    // 通知を表示
175    if (typeof showNotification === 'function') {
176      showNotification('位置が選択されました', 2000);
177    }
178  });
179}
180
181// 地図選択モードを無効化
182function disableMapSelection() {
183  console.log('Map selection mode disabled');
184
185  if (mapClickListener) {
186    google.maps.event.removeListener(mapClickListener);
187    mapClickListener = null;
188  }
189
190  selectedMapPosition = null;
191}
192
193// 近くの施設を検索
194async function applyNearbySearch() {
195  console.log('=== applyNearbySearch called ===');
196
197  const baseLocationRadio = document.querySelector('input[name="baseLocation"]:checked');
198  if (!baseLocationRadio) {
199    console.error('No base location selected');
200    alert('基準位置を選択してください');
201    return;
202  }
203
204  const baseLocation = baseLocationRadio.value;
205  const distanceRadio = document.querySelector('input[name="distance"]:checked');
206  const distance = distanceRadio ? distanceRadio.value : 'all';
207  const searchPeritoneal = document.getElementById('searchPeritoneal').checked;
208  const searchHomeDialysis = document.getElementById('searchHomeDialysis').checked;
209  const searchNightDialysis = document.getElementById('searchNightDialysis').checked;
210  const searchVAIVT = document.getElementById('searchVAIVT').checked;
211  const searchParking = document.getElementById('searchParking').checked;
212  const searchInpatient = document.getElementById('searchInpatient').checked;
213
214  console.log('Search params:', { baseLocation, distance, searchPeritoneal, searchHomeDialysis, searchNightDialysis, searchVAIVT, searchParking, searchInpatient });
215
216  // 基準位置を取得
217  let basePos = null;
218
219  if (baseLocation === 'my-facility') {
220    // マイ施設から
221    if (window.currentUser && window.currentUser.facilityName) {
222      const myFacilityIndex = window.facilities.findIndex(f => f.name === window.currentUser.facilityName);   // 名前OK:施設名(人物ではない)
223
224      if (myFacilityIndex !== -1 && window.facilityMarkers && window.facilityMarkers[myFacilityIndex]) {
225        // マーカーから座標を取得
226        const marker = window.facilityMarkers[myFacilityIndex];
227        const position = marker.getPosition();
228        basePos = { lat: position.lat(), lng: position.lng() };
229      } else {
230        alert('マイ施設の位置が見つかりません。');
231        return;
232      }
233    } else {
234      alert('マイ施設を使用するにはログインが必要です。');
235      return;
236    }
237  } else if (baseLocation === 'gps') {
238    // GPS位置から
239    if (window.userLocation && window.userLocation.lat) {
240      basePos = { lat: window.userLocation.lat, lng: window.userLocation.lng };
241    } else {
242      /* ★以前は「取得できません」と出して終わりだった(2026-08-24 オーナー指摘)。
243         位置をまだ聞いていないだけのことが多いので、ここで**その場で聞き直す**。
244         断られた・取れなかった場合は、原因ごとに何をすればよいかを出す。 */
245      requestGpsThenSearch();
246      return;
247    }
248  } else if (baseLocation === 'map') {
249    // 地図上の選択位置から
250    if (selectedMapPosition) {
251      basePos = selectedMapPosition;
252    } else {
253      alert('地図上で位置を選択してください。');
254      return;
255    }
256  } else if (baseLocation === 'address') {
257    // 住所から
258    if (selectedAddressPosition) {
259      basePos = selectedAddressPosition;
260    } else {
261      alert('住所を検索してください。');
262      return;
263    }
264  }
265
266  console.log('Base position:', basePos);
267
268  // 施設リストを距離計算してソート
269  const facilitiesWithDistance = window.facilities.map((facility, index) => {
270    // マーカーから座標を取得
271    let facilityLat, facilityLng;
272
273    if (window.facilityMarkers && window.facilityMarkers[index]) {
274      const marker = window.facilityMarkers[index];
275      const position = marker.getPosition();
276      facilityLat = position.lat();
277      facilityLng = position.lng();
278    } else {
279      console.warn(`No marker found for facility ${index}: ${facility.name}`);
280      return { ...facility, distance: NaN, originalIndex: index };
281    }
282
283    const dist = calculateDistance(
284      basePos.lat, basePos.lng,
285      facilityLat, facilityLng
286    );
287
288    // デバッグ:最初の3件の距離計算を表示
289    if (index < 3) {
290      console.log(`Facility ${index}: ${facility.name}`, {
291        facilityLat: facilityLat,
292        facilityLng: facilityLng,
293        baseLat: basePos.lat,
294        baseLng: basePos.lng,
295        distance: dist
296      });
297    }
298
299    return { ...facility, distance: dist, originalIndex: index, lat: facilityLat, lng: facilityLng };
300  });
301
302  console.log(`Total facilities with distance: ${facilitiesWithDistance.length}`);
303  console.log(`First 3 distances:`, facilitiesWithDistance.slice(0, 3).map(f => ({ name: f.name, distance: f.distance })));
304
305  // 条件でフィルタリング
306  let filteredFacilities = facilitiesWithDistance;
307
308  // 距離フィルター
309  if (distance !== 'all') {
310    const maxDist = parseInt(distance);
311    console.log(`Filtering by distance <= ${maxDist} km`);
312    filteredFacilities = filteredFacilities.filter(f => {
313      const isValid = !isNaN(f.distance) && f.distance <= maxDist;
314      if (!isValid && f.distance !== undefined) {
315        console.log(`Filtered out: ${f.name}, distance: ${f.distance}`);
316      }
317      return isValid;
318    });
319    console.log(`After distance filter: ${filteredFacilities.length} facilities`);
320  }
321
322  // 腹膜透析フィルター
323  if (searchPeritoneal) {
324    filteredFacilities = filteredFacilities.filter(f => f.peritoneal === 'あり');
325  }
326
327  // 在宅透析フィルター
328  if (searchHomeDialysis) {
329    filteredFacilities = filteredFacilities.filter(f =>
329 f.homeDialysis === 'あり');
330  }
331
332  // 夜間透析フィルター
333  if (searchNightDialysis) {
334    filteredFacilities = filteredFacilities.filter(f => f.nightDialysis === 'あり');
335  }
336
337  // VAIVTフィルター
338  if (searchVAIVT) {
339    filteredFacilities = filteredFacilities.filter(f => f.vaivt && f.vaivt !== 'なし' && f.vaivt !== 'いいえ');
340  }
341
342  // 駐車場フィルター
343  if (searchParking) {
344    filteredFacilities = filteredFacilities.filter(f => f.parking === 'あり');
345  }
346
347  // 入院ベッドフィルター
348  if (searchInpatient) {
349    filteredFacilities = filteredFacilities.filter(f => f.inpatient === 'あり');
350  }
351
352  // 距離でソート
353  filteredFacilities.sort((a, b) => a.distance - b.distance);
354
355  console.log(`Found ${filteredFacilities.length} facilities`);
356
357  // 検索履歴に追加
358  addToSearchHistory({ baseLocation, distance, facilityCount: filteredFacilities.length });
359
360  // 結果を施設一覧に表示
361  displaySearchResults(filteredFacilities);
362
363  // 検索解除ボタンを表示(サイドバーのボタン)
364  const clearBtn = document.getElementById('searchClearBtn');
365  if (clearBtn) {
366    clearBtn.style.display = 'flex';
367  }
368
369  // モーダル内の検索解除ボタンも表示(モーダル閉じる前に)
370  const modalClearBtn = document.getElementById('searchModalClearBtn');
371  if (modalClearBtn) {
372    modalClearBtn.style.display = 'block';
373  }
374
375  // モーダルを閉じる(検索解除ボタンは次回開いた時に表示される)
376  hideSearchModal();
377
378  // マップを基準位置に移動(アニメーションとエフェクト付き)
379  if (basePos && map) {
380    const targetPosition = new google.maps.LatLng(basePos.lat, basePos.lng);
381
382    // スムーズにズームとパン
383    map.panTo(targetPosition);
384
385    // ズームアニメーション
386    const currentZoom = map.getZoom();
387    const targetZoom = 13;
388
389    if (Math.abs(currentZoom - targetZoom) > 0.5) {
390      let step = 0;
391      const steps = 10;
392      const zoomDiff = targetZoom - currentZoom;
393      const zoomStep = zoomDiff / steps;
394
395      const zoomInterval = setInterval(() => {
396        step++;
397        map.setZoom(currentZoom + (zoomStep * step));
398
399        if (step >= steps) {
400          clearInterval(zoomInterval);
401          map.setZoom(targetZoom);
402
403          // エフェクト追加(バウンドマーカー)
404          const bounceMarker = new google.maps.Marker({
405            position: targetPosition,
406            map: map,
407            animation: google.maps.Animation.BOUNCE,
408            icon: {
409              path: google.maps.SymbolPath.CIRCLE,
410              scale: 15,
411              fillColor: '#4285f4',
412              fillOpacity: 0.8,
413              strokeColor: '#ffffff',
414              strokeWeight: 3
415            },
416            zIndex: 10000
417          });
418
419          // 放射状エフェクト
420          if (typeof createPulseEffect === 'function') {
421            createPulseEffect(bounceMarker, targetPosition);
422          }
423
424          // 3秒後にバウンドマーカーを削除
425          setTimeout(() => {
426            bounceMarker.setMap(null);
427          }, 3000);
428        }
429      }, 50);
430    } else {
431      // ズーム不要の場合もエフェクト表示
432      setTimeout(() => {
433        const bounceMarker = new google.maps.Marker({
434          position: targetPosition,
435          map: map,
436        animation: google.maps.Animation.BOUNCE,
437        icon: {
438          path: google.maps.SymbolPath.CIRCLE,
439          scale: 15,
440          fillColor: '#4285f4',
441          fillOpacity: 0.8,
442          strokeColor: '#ffffff',
443          strokeWeight: 3
444        },
445        zIndex: 10000
446        });
447
448        if (typeof createPulseEffect === 'function') {
449          createPulseEffect(bounceMarker, targetPosition);
450        }
451
452        setTimeout(() => {
453          bounceMarker.setMap(null);
454        }, 3000);
455      }, 500);
456    }
457  }
458}
459
460// 2点間の距離を計算(km)
461function calculateDistance(lat1, lon1, lat2, lon2) {
462  // 数値型に変換
463  const lat1Num = Number(lat1);
464  const lon1Num = Number(lon1);
465  const lat2Num = Number(lat2);
466  const lon2Num = Number(lon2);
467
468  // NaNチェック
469  if (isNaN(lat1Num) || isNaN(lon1Num) || isNaN(lat2Num) || isNaN(lon2Num)) {
470    console.error('Invalid coordinates:', { lat1, lon1, lat2, lon2 });
471    return NaN;
472  }
473
474  const R = 6371; // 地球の半径(km)
475  const dLat = (lat2Num - lat1Num) * Math.PI / 180;
476  const dLon = (lon2Num - lon1Num) * Math.PI / 180;
477  const a =
478    Math.sin(dLat / 2) * Math.sin(dLat / 2) +
479    Math.cos(lat1Num * Math.PI / 180) * Math.cos(lat2Num * Math.PI / 180) *
480    Math.sin(dLon / 2) * Math.sin(dLon / 2);
481  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
482  const distance = R * c;
483  return distance;
484}
485
486// 検索結果を表示
487function displaySearchResults(facilities) {
488  // お気に入りフィルターを解除
489  const favoriteFilterBtn = document.getElementById('favoriteFilterBtn');
490  if (favoriteFilterBtn && favoriteFilterBtn.classList.contains('active')) {
491    favoriteFilterBtn.classList.remove('active');
492    if (favoriteFilterBtn.querySelector('.btn-icon')) {
493      favoriteFilterBtn.querySelector('.btn-icon').textContent = '☆';
494    }
495  }
496
497  const listContainer = document.getElementById("facilityListItems");
498  listContainer.innerHTML = "";
499
500  if (facilities.length === 0) {
501    listContainer.innerHTML = `
502      <div style="padding: 40px 20px; text-align: center; color: #666;">
503        条件に一致する施設が見つかりませんでした
504      </div>
505    `;
506    return;
507  }
508
509  facilities.forEach((facility) => {
510    const colorData = getPatientColor(facility.patients);
511    const facilityItem = document.createElement("div");
512    facilityItem.className = "facility-item";
513    facilityItem.dataset.index = facility.originalIndex;
514    facilityItem.style.backgroundColor = colorData.lightColor;
515    facilityItem.style.borderLeftColor = colorData.color;
516
517    // 距離情報を追加
518    const distanceText = `<div style="color: #1a73e8; font-weight: bold; margin-bottom: 4px; display: flex; align-items: center; gap: 6px;">
519      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16">
520        <path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
521      </svg>
522      ${facility.distance.toFixed(1)} km
523    </div>`;
524
525    facilityItem.innerHTML = `
526      ${distanceText}
527      <div style="display: flex; justify-content: space-between; align-items: center;">
528        <div class="facility-name">${facility.name}</div>
529        <button class="facility-star-btn" data-facility-index="${facility.originalIndex}" title="${isFacilitySaved(facility.originalIndex) ? '保存済み' : 'お気に入りに追加'}">
530          ${isFacilitySaved(facility.originalIndex)
531            ? '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#fbbf24" width="20" height="20"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>'
532            : '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#9ca3af" stroke-width="2" width="20" height="20"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>'
533          }
534        </button>
535      </div>
536      <div class="facility-patients" style="color: ${colorData.color};">
537        患者数: ${facility.patients}人 | ベッド数: ${facility.beds}床
538        <button class="detail-btn" data-facility-index="${facility.originalIndex}">詳細</button>
539      </div>
540      <div class="facility-details" style="margin-top: 8px;">
541        ${facility.postalCode ? `
542        <div class="facility-detail-row">
543          <span class="facility-detail-label">〒:</span>
544          <span class="facility-detail-value">${facility.postalCode}</span>
545        </div>
546        ` : ''}
547        <div class="facility-detail-row">
548          <span class="facility-detail-label">住所:</span>
549          <span class="facility-detail-value">${facility.address}</span>
550        </div>
551        <div class="facility-detail-row">
552          <span class="facility-detail-label">電話:</span>
553          <span class="facility-detail-value">${facility.phone}</span>
554        </div>
555        <div class="facility-detail-row">
556          <span class="facility-detail-label">FAX:</span>
557          <span class="facility-detail-value">${facility.fax && facility.fax !== "undefined" ? facility.fax : "情報なし"}</span>
558        </div>
559        ${facility.website && facility.website !== null ? `
560        <div class="facility-detail-row">
561          <span class="facility-detail-label">HP:</span>
562          <span class="facility-detail-value">
563            <a href="${typeof normalizeUrl === 'function' ? normalizeUrl(facility.website) : facility.website}" target="_blank" rel="noopener noreferrer">
564              ${facility.website}
565            </a>
566          </span>
567        </div>
568        ` : `
569        <div class="facility-detail-row">
570          <span class="facility-detail-label">HP:</span>
571          <span class="facility-detail-value">未設定</span>
572        </div>
573        `}
574      </div>
575      <div class="facility-extended-details" id="extended_${facility.originalIndex}" style="display: none;">
576        <div class="detail-row"><span>送迎対応:</span><span>${getExtendedData(facility.originalIndex, "transport", "未設定")}</span></div>
577        <div class="detail-row"><span>駐車場:</span><span>${getExtendedData(facility.originalIndex, "parking", "未設定")}</span></div>
578        <div class="detail-row"><span>入院ベッド:</span><span>${getExtendedData(facility.originalIndex, "inpatient", "未設定")}</span></div>
579        <div class="detail-row"><span>活動制限:</span><span>${getExtendedData(facility.originalIndex, "activity", "未設定")}</span></div>
580        <div class="detail-row"><span>生活保護受け入れ:</span><span>${getExtendedData(facility.originalIndex, "welfare", "未設定")}</span></div>
581        <div class="detail-row"><span>精神疾患受け入れ:</span><span>${getExtendedData(facility.originalIndex, "mental", "未設定")}</span></div>
582        <div class="detail-row"><span>腹膜透析受け入れ:</span><span>${getExtendedData(facility.originalIndex, "peritoneal", "未設定")}</span></div>
583        <div class="detail-row"><span>在宅透析:</span><span>${getExtendedData(facility.originalIndex, "homeDialysis", "未設定")}</span></div>
584        <div class="detail-row"><span>夜間透析:</span><span>${getExtendedData(facility.originalIndex, "nightDialysis", "未設定")}</span></div>
585        <div class="detail-row"><span>VAIVT(PTA)対応施設:</span><span>${getExtendedData(facility.originalIndex, "vaivt", "未設定")}</span></div>
586        ${window.getVaivtReferralDisplay ? window.getVaivtReferralDisplay(facility.originalIndex) : ''}
587        <div class="detail-row"><span>旅行透析の受け入れ:</span><span>${getExtendedData(facility.originalIndex, "travelDialysis", "未設定")}</span></div>
588        <div style="margin-top: 10px;">
589          <strong>案内情報:</strong><br>
590          ${getExtendedData(facility.originalIndex, "info", "未設定")}
591        </div>
592        ${window.generateOccupancyCharts ? window.generateOccupancyCharts(facility) : ''}
593      </div>
594    `;
595
596    // 星ボタンのイベントリスナーを追加
597    const starBtn = facilityItem.querySelector('.facility-star-btn');
598    if (starBtn) {
599      const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
600
601      const handleStarClick = (e) => {
602        e.preventDefault();
603        e.stopPropagation();
604        const facilityIndex = parseInt(starBtn.getAttribute('data-facility-index'));
605        console.log('Star button activated:', facilityIndex);
606        toggleSaveFacility(facilityIndex);
607      };
608
609      if (isTouchDevice) {
610        starBtn.addEventListener('touchend', handleStarClick, { passive: false });
611      }
612
613      // すべてのデバイスでclickイベントも追加(フォールバック)
614      starBtn.addEventListener('click', handleStarClick);
615    }
616
617    // 詳細ボタンのイベントリスナーを追加 (Windows対応)
618    const detailBtn = facilityItem.querySelector('.detail-btn');
619    if (detailBtn) {
620      const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
621
622      const handleDetailClick = (e) => {
623        e.preventDefault();
624        e.stopPropagation();
625        const facilityIndex = parseInt(detailBtn.getAttribute('data-facility-index'));
626        console.log('Detail button activated for facility:', facilityIndex);
627
628        // 少し遅延させて確実に実行
629        setTimeout(() => {
630          if (typeof window.toggleFacilityDetails === 'function') {
631            window.toggleFacilityDetails(facilityIndex);
632          }
633        }, 0);
634      };
635
636      if (isTouchDevice) {
637        detailBtn.addEventListener('touchend', handleDetailClick, { passive: false });
638      }
639
640      // すべての環境でclickもリッスン(フォールバック)
641      detailBtn.addEventListener('click', handleDetailClick);
642    }
643
644    // 紹介ボタンのイベントリスナーを追加
645    const referralBtn = facilityItem.querySelector('.referral-btn');
646    if (referralBtn) {
647      const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
648
649      const handleReferralClick = (e) => {
650        e.preventDefault();
651        e.stopPropagation();
652        const facilityIndex = parseInt(referralBtn.getAttribute('data-facility-index'));
653        const facilityName = referralBtn.getAttribute('data-facility-name');
654        const vaivt = referralBtn.getAttribute('data-vaivt');
655        console.log('Referral button clicked for facility:', facilityName);
656
657        // VAIVT対応施設かどうかで選択肢を表示
658        if (typeof window.showReferralOptions === 'function') {
659          window.showReferralOptions(facilityIndex, facilityName, vaivt);
660        }
661      };
662
663      if (isTouchDevice) {
664        referralBtn.addEventListener('touchend', handleReferralClick, { passive: false });
665      }
666      referralBtn.addEventListener('click', handleReferralClick);
667    }
668
669    facilityItem.addEventListener("click", (e) => {
670      if (!e.target.closest('.facility-star-btn') && !e.target.closest('.detail-btn') && !e.target.closest('.referral-btn')) {
671        selectFacilityFromList(facility.originalIndex);
672      }
673    });
674
675    listContainer.appendChild(facilityItem);
676  });
677
678  // 検索結果のインデックスを取得して保存
679  const searchResultIndices = facilities.map(f => f.originalIndex);
680  currentSearchResultIndices = searchResultIndices;
681
682  // マップ上のマーカーとラベルも検索結果に絞り込む
683  if (window.facilityMarkers) {
684    window.facilityMarkers.forEach((marker, index) => {
685      if (marker) {
686        marker.setVisible(searchResultIndices.includes(index));
687      }
688    });
689  }
690
691  if (window.facilityLabels) {
692    window.facilityLabels.forEach((label, index) => {
693      if (label && typeof label.setMap === 'function') {
694        if (searchResultIndices.includes(index)) {
695          if (window.map) {
696            label.setMap(window.map);
697          }
698        } else {
699          label.setMap(null);
700        }
701      }
702    });
703  }
704
705  // 緊急マーカーの表示も更新
706  if (typeof window.updateEmergencyMarkersVisibility === 'function') {
707    window.updateEmergencyMarkersVisibility();
708  }
709
710  // ログイン中のユーザー施設のハイライトを適用
711  if (window.currentUser && window.facilities) {
712    const userFacilityIndex = window.facilities.findIndex(f =>
713      window.currentUser.facilityName && f.name === window.currentUser.facilityName   // 名前OK:施設名(人物ではない)
714    );
715    if (userFacilityIndex !== -1) {
716      setTimeout(() => {
717        const facilityItem = document.querySelector(`[data-index="${userFacilityIndex}"]`);
718        if (facilityItem) {
719          facilityItem.classList.add('user-facility');
720        }
721      }, 100);
722    }
723  }
724}
725
726// 検索条件をリセット
727function resetSearch() {
728  // 基準位置をデフォルトに
729  const myFacilityRadio = document.querySelector('input[name="baseLocation"][value="my-facility"]');
730  if (myFacilityRadio) {
731    myFacilityRadio.checked = true;
732  }
733
734  // 距離をallに
735  const allDistanceRadio = document.querySelector('input[name="distance"][value="all"]');
736  if (allDistanceRadio) {
737    allDistanceRadio.checked = true;
738  }
739
740  // チェックボックスをすべてオフ
741  document.getElementById('searchPeritoneal').checked = false;
742  document.getElementById('searchHomeDialysis').checked = false;
743  document.getElementById('searchVAIVT').checked = false;
744  document.getElementById('searchParking').checked = false;
745  document.getElementById('searchInpatient').checked = false;
746
747  // 地図選択モードを無効化
748  handleLocationChange();
749
750  console.log('Search conditions reset');
751}
752
753// 施設を保存/解除(施設名ベース、施設IDに紐づけて保存)
754async function toggleSaveFacility(facilityIndex) {
755  // 施設名を取得
756  const facility = window.facilities[facilityIndex];
757  if (!facility) {
758    console.error('Facility not found at index:', facilityIndex);
759    return;
760  }
761  const facilityName = facility.name;
762
763  // 安全チェック:施設名が文字列でない場合はエラー
764  if (typeof facilityName !== 'string' || !facilityName) {
765    console.error('❌ 施設名が無効です(文字列のみ許可):', facilityName);
766    return;
767  }
768
769  const savedFacilities = getSavedFacilities();
770  const index = savedFacilities.indexOf(facilityName);
771
772  if (index > -1) {
773    // 既に保存済みなら削除
774    savedFacilities.splice(index, 1);
775    console.log('Facility removed from saved:', facilityName);
776  } else {
777    // 未保存なら追加
778    savedFacilities.push(facilityName);
779    console.log('Facility added to saved:', facilityName);
780  }
781
782  // ログイン状態に応じて保存先を変更
783  console.log('Saving favorites - checking conditions:', {
784    hasCurrentUser: !!window.currentUser,
785    facilityId: window.currentUser?.facilityId,
786    hasFirebaseDB: !!window.firebaseDB,
787    hasFirebaseDBFunctions: !!window.firebaseDBFunctions
788  });
789
790  if (window.currentUser && window.currentUser.facilityId && window.firebaseDB && window.firebaseDBFunctions) {
791    // ログイン中:施設ドキュメントに保存
792    const currentFacilityId = window.currentUser.facilityId;
793    console.log('Attempting to save to Firestore for facility:', currentFacilityId);
794    const cacheKey = 'savedFacilities_' + currentFacilityId;
795    try {
796      const { doc, setDoc } = window.firebaseDBFunctions;
797      const facilityDoc = doc(window.firebaseDB, 'facilities', currentFacilityId);
798      await setDoc(facilityDoc, {
799        savedFacilities: savedFacilities
800      }, { merge: true });
801      console.log('Favorites saved to Firestore successfully:', savedFacilities);
802      // ローカルキャッシュも更新
803      localStorage.setItem(cacheKey, JSON.stringify(savedFacilities));
804      // currentUserにも反映
805      window.currentUser.savedFacilities = savedFacilities;
806    } catch (error) {
807      console.error('Error saving favorites to Firestore:', error);
808      // Firestoreへの保存に失敗した場合、localStorageにフォールバック
809      localStorage.setItem(cacheKey, JSON.stringify(savedFacilities));
810      window.currentUser.savedFacilities = savedFacilities;
811      console.log('Fell back to localStorage due to error');
812    }
813  } else {
814    // ログアウト中:localStorageに保存(永続的に保存)
815    localStorage.setItem('savedFacilities_guest', JSON.stringify(savedFacilities));
816    console.log('Favorites saved to localStorage (persistent)');
817  }
818
819  // キャッシュを無効化
820  invalidateSavedFacilitiesCache();
821
822  // ボタンの表示を更新(非同期処理完了後)
823  setTimeout(() => {
824    // お気に入りフィルターがアクティブな場合は、リストを更新
825    const favoriteFilterBtn = document.getElementById('favoriteFilterBtn');
826    const isFavoriteActive = favoriteFilterBtn && favoriteFilterBtn.classList.contains('active');
827
828    if (isFavoriteActive) {
829      // お気に入りフィルター有効時は、施設リストを更新(追加/削除を反映)
830      filterFavoriteFacilities();
831    } else {
832      // 通常時は星アイコンのみ更新(リスト全体を再生成しない)
833      updateStarButtons();
834    }
835    // 付け外しの後は一番上から見せる(2026-08-16 オーナー指示)
836    if (typeof window.scrollFacilityListTop === 'function') window.scrollFacilityListTop();
837  }, 100);
838}
839
840// 保存済み施設の施設名配列を取得(施設IDに紐づけて取得)
841// キャッシュ機構付き:短時間の重複呼び出しを防止
842function getSavedFacilities() {
843  // キャッシュが有効な場合はキャッシュから返す
844  const now = Date.now();
845  if (savedFacilitiesCache !== null && (now - savedFacilitiesCacheTime) < SAVED_FACILITIES_CACHE_TTL) {
846    return savedFacilitiesCache;
847  }
848
849  let result = [];
850
851  if (window.currentUser && window.currentUser.facilityId) {
852    // ログイン中の場合:施設IDをキーに取得
853    const cacheKey = 'savedFacilities_' + window.currentUser.facilityId;
854    const cached = localStorage.getItem(cacheKey);
855
856    if (cached) {
857      try {
858        const parsed = JSON.parse(cached);
859        if (Array.isArray(parsed) && parsed.length > 0) {
860          window.currentUser.savedFacilities = parsed;
861          result = parsed;
862        }
863      } catch (e) {
864        console.warn('⚠️ Failed to parse cached favorites:', e);
865      }
866    }
867
868    if (result.length === 0 && Array.isArray(window.currentUser.savedFacilities) && window.currentUser.savedFacilities.length > 0) {
869      result = window.currentUser.savedFacilities;
870    }
871  } else if (window.authStateResolved) {
872    // ログアウト済み確定:お気に入りを表示しない
873    result = [];
874  } else {
875    // 認証状態未確定(ページ読み込み中):localStorageからsavedFacilities_*を検索
876    for (let i = 0; i < localStorage.length; i++) {
877      const key = localStorage.key(i);
878      if (key && key.startsWith('savedFacilities_') && key !== 'savedFacilities_guest') {
879        try {
880          const data = JSON.parse(localStorage.getItem(key));
881          if (Array.isArray(data) && data.length > 0) {
882            result = data;
883            console.log('📂 getSavedFacilities: localStorageから発見:', key);
884            break;
885          }
886        } catch (e) { /* ignore */ }
887      }
888    }
889
890    // 見つからない場合はゲスト用を確認
891    if (result.length === 0) {
892      const saved = localStorage.getItem('savedFacilities_guest');
893      result = saved ? JSON.parse(saved) : [];
894    }
895  }
896
897  // 数字データを除外(文字列の施設名のみを保持)
898  const originalLength = result.length;
899  result = result.filter(item => typeof item === 'string');
900
901  // 数字データが含まれていた場合、localStorageとFirebaseをクリーンアップ
902  if (result.length !== originalLength) {
903    console.log('🧹 古い数字データを削除:', originalLength - result.length, '件');
904
905    // localStorageの全savedFacilities_*キーをクリーンアップ
906    for (let i = 0; i < localStorage.length; i++) {
907      const key = localStorage.key(i);
908      if (key && key.startsWith('savedFacilities_')) {
909        try {
910          const data = JSON.parse(localStorage.getItem(key));
911          if (Array.isArray(data)) {
912            const cleaned = data.filter(item => typeof item === 'string');
913            localStorage.setItem(key, JSON.stringify(cleaned));
914          }
915        } catch (e) { /* ignore */ }
916      }
917    }
918
919    // Firestoreもクリーンアップ(ログイン中のみ)
920    if (window.currentUser && window.currentUser.facilityId && window.firebaseDB && window.firebaseDBFunctions) {
921      const { doc, setDoc } = window.firebaseDBFunctions;
922      const facilityDoc = doc(window.firebaseDB, 'facilities', window.currentUser.facilityId);
923      setDoc(facilityDoc, { savedFacilities: result }, { merge: true })
924        .then(() => console.log('🧹 Firestoreの古い数字データを削除しました'))
925        .catch(e => console.warn('Firestoreクリーンアップエラー:', e));
926    }
927  }
928
929  // キャッシュを更新
930  savedFacilitiesCache = result;
931  savedFacilitiesCacheTime = now;
932
933  return result;
934}
935
936// キャッシュを無効化(お気に入りの追加/削除時に呼ぶ)
937function invalidateSavedFacilitiesCache() {
938  savedFacilitiesCache = null;
939  savedFacilitiesCacheTime = 0;
940}
941window.invalidateSavedFacilitiesCache = invalidateSavedFacilitiesCache;
942window.updateStarButtons = updateStarButtons;
943
944// Firestoreからお気に入りを読み込む(ログイン時に呼ばれる)
945// 施設ドキュメント(facilities/{facilityId})からお気に入りを取得
946async function loadUserFavorites() {
947  if (!window.currentUser || !window.currentUser.facilityId || !window.firebaseDB || !window.firebaseDBFunctions) {
948    console.log('⚠️ loadUserFavorites: 必要な条件が揃っていません', {
949      hasCurrentUser: !!window.currentUser,
950      facilityId: window.currentUser?.facilityId,
951      hasFirebaseDB: !!window.firebaseDB
952    });
953    return;
954  }
955
956  const currentFacilityId = window.currentUser.facilityId;
957  const cacheKey = 'savedFacilities_' + currentFacilityId;
958  console.log('📥 loadUserFavorites: 施設ID =', currentFacilityId);
959
960  // まずlocalStorageのキャッシュを取得
961  let cachedFavorites = [];
962  const cached = localStorage.getItem(cacheKey);
963  if (cached) {
964    try {
965      const parsed = JSON.parse(cached);
966      if (Array.isArray(parsed)) {
967        cachedFavorites = parsed;
968      }
969    } catch (e) {
970      console.warn('⚠️ キャッシュのパースに失敗:', e);
971    }
972  }
973
974  try {
975    const { doc, getDoc, setDoc } = window.firebaseDBFunctions;
976    const facilityDoc = doc(window.firebaseDB, 'facilities', currentFacilityId);
977    const docSnap = await getDoc(facilityDoc);
978
979    if (docSnap.exists() && docSnap.data().savedFacilities && docSnap.data().savedFacilities.length > 0) {
980      // Firestoreにデータがある場合はそれを使用
981      window.currentUser.savedFacilities = docSnap.data().savedFacilities;
982      console.log('✅ Firestoreからお気に入りを読み込みました:', window.currentUser.savedFacilities);
983
984      // localStorageにキャッシュを更新
985      try {
986        localStorage.setItem(cacheKey, JSON.stringify(window.currentUser.savedFacilities));
987        console.log('💾 お気に入りをキャッシュしました');
988      } catch (e) {
989        console.warn('⚠️ Failed to cache favorites:', e);
990      }
991    } else if (cachedFavorites.length > 0) {
992      // Firestoreにはないが、localStorageにキャッシュがある場合
993      // キャッシュを保持し、Firestoreに同期
994      window.currentUser.savedFacilities = cachedFavorites;
995      console.log('📦 キャッシュからお気に入りを復元:', cachedFavorites);
996      console.log('🔄 Firestoreにお気に入りを同期中...');
997
998      try {
999        await setDoc(facilityDoc, {
1000          savedFacilities: cachedFavorites
1001        }, { merge: true });
1002        console.log('✅ Firestoreにお気に入りを同期しました');
1003      } catch (syncError) {
1004        console.warn('⚠️ Firestoreへの同期に失敗(キャッシュは保持):', syncError);
1005      }
1006    } else {
1007      // どちらにもデータがない場合のみ空配列
1008      window.currentUser.savedFacilities = [];
1009      console.log('ℹ️ お気に入りデータはありません');
1010    }
1011
1012    // メモリキャッシュを無効化(古いデータを破棄)
1013    invalidateSavedFacilitiesCache();
1014    console.log('🗑️ お気に入りキャッシュを無効化');
1015
1016    // loadUserFavorites完了フラグを設定
1017    window.userFavoritesLoaded = true;
1018    console.log('✅ userFavoritesLoaded = true');
1019
1020    // ボタンの表示を更新
1021    updateStarButtons();
1022
1023    // お気に入りフィルターがアクティブな場合は再適用
1024    const favoriteFilterBtn = document.getElementById('favoriteFilterBtn');
1025    if (favoriteFilterBtn && favoriteFilterBtn.classList.contains('active')) {
1026      console.log('🔄 お気に入りフィルターを再適用');
1027      filterFavoriteFacilities();
1028    }
1029
1030    // お気に入りフィルター状態を復元(loadUserFavorites完了後に呼び出し)
1031    console.log('🔄 loadUserFavorites完了、お気に入りフィルター状態を復元');
1032    restoreFavoriteFilterState();
1033  } catch (error) {
1034    console.error('❌ お気に入りの読み込みエラー:', error);
1035    // エラー時はキャッシュを使用(上書きしない)
1036    if (cachedFavorites.length > 0) {
1037      window.currentUser.savedFacilities = cachedFavorites;
1038      console.log('🔄 エラーのためキャッシュを使用:', cachedFavorites);
1039      updateStarButtons();
1040    } else {
1041      window.currentUser.savedFacilities = [];
1042    }
1043    // エラー時もフラグを設定(リトライを止めるため)
1044    window.userFavoritesLoaded = true;
1045  }
1046}
1047
1048// 施設が保存済みかチェック(施設名で判定)
1049function isFacilitySaved(facilityIndex) {
1050  const facility = window.facilities[facilityIndex];
1051  if (!facility) return false;
1052
1053  const savedFacilities = getSavedFacilities();
1054  return savedFacilities.includes(facility.name);
1055}
1056
1057// 星ボタンの表示を更新(リスト全体を再生成せず、星アイコンのみ更新)
1058function updateStarButtons() {
1059  // 全ての星ボタンを取得して、個別にアイコンを更新
1060  const starButtons = document.querySelectorAll('.facility-star-btn');
1061
1062  starButtons.forEach(btn => {
1063    const facilityIndex = parseInt(btn.getAttribute('data-facility-index'));
1064    if (isNaN(facilityIndex)) return;
1065
1066    const isSaved = isFacilitySaved(facilityIndex);
1067
1068    // アイコンを更新
1069    if (isSaved) {
1070      btn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#fbbf24" width="20" height="20"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>';
1071      btn.title = '保存済み';
1072    } else {
1073      btn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#9ca3af" stroke-width="2" width="20" height="20"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>';
1074      btn.title = 'お気に入りに追加';
1075    }
1076  });
1077
1078  console.log('⭐ 星ボタンを更新しました(リスト再生成なし)');
1079}
1080
1081// 保存済み施設を読み込んで表示(施設名ベース)
1082function loadSavedFacilities() {
1083  const savedFacilityNames = getSavedFacilities();
1084  const savedFacilitiesList = document.getElementById('savedFacilitiesList');
1085
1086  if (savedFacilityNames.length === 0) {
1087    savedFacilitiesList.innerHTML = `
1088      <p style="text-align: center; color: #666; padding: 40px 20px;">
1089        保存済みの施設はありません<br>
1090        施設一覧の<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#fbbf24" width="16" height="16" style="vertical-align: middle; margin: 0 2px;"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>マークから保存できます
1091      </p>
1092    `;
1093    return;
1094  }
1095
1096  savedFacilitiesList.innerHTML = '';
1097
1098  savedFacilityNames.forEach(savedName => {
1099    const index = window.facilities.findIndex(f => f.name === savedName);   // 名前OK:施設名(人物ではない)
1100    if (index === -1) {
1101      console.warn('⚠️ 保存済み施設が見つかりません:', savedName);
1102      return;
1103    }
1104    const facility = window.facilities[index];
1105
1106    const item = document.createElement('div');
1107    item.className = 'saved-facility-item';
1108    item.innerHTML = `
1109      <div class="saved-facility-name">${facility.name}</div>
1110      <div class="saved-facility-address">${facility.address}</div>
1111    `;
1112
1113    item.addEventListener('click', () => {
1114      selectFacilityFromList(index);
1115      hideSearchModal();
1116    });
1117
1118    savedFacilitiesList.appendChild(item);
1119  });
1120}
1121
1122// 検索履歴に追加
1123function addToSearchHistory(searchParams) {
1124  const history = getSearchHistory();
1125
1126  const historyItem = {
1127    timestamp: new Date().toISOString(),
1128    baseLocation: searchParams.baseLocation,
1129    distance: searchParams.distance,
1130    facilityCount: searchParams.facilityCount
1131  };
1132
1133  history.unshift(historyItem);
1134
1135  // 最大10件まで保存
1136  if (history.length > 10) {
1137    history.pop();
1138  }
1139
1140  localStorage.setItem('searchHistory', JSON.stringify(history));
1141}
1142
1143// 検索履歴を取得
1144function getSearchHistory() {
1145  const history = localStorage.getItem('searchHistory');
1146  return history ? JSON.parse(history) : [];
1147}
1148
1149// 検索履歴を表示
1150function loadSearchHistory() {
1151  const history = getSearchHistory();
1152  const historyList = document.getElementById('searchHistoryList');
1153
1154  if (history.length === 0) {
1155    historyList.innerHTML = `
1156      <p style="text-align: center; color: #666; padding: 40px 20px;">
1157        検索履歴はありません
1158      </p>
1159    `;
1160    return;
1161  }
1162
1163  historyList.innerHTML = '';
1164
1165  history.forEach((item, index) => {
1166    const historyItem = document.createElement('div');
1167    historyItem.className = 'history-item';
1168
1169    const locationText =
1170      item.baseLocation === 'my-facility' ? 'マイ施設から' :
1171      item.baseLocation === 'gps' ? '現在地(GPS)から' :
1172      '地図上の位置から';
1173
1174    const distanceText = item.distance === 'all' ? 'すべて表示' : `${item.distance}km以内`;
1175
1176    const date = new Date(item.timestamp);
1177    const timeText = `${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${String(date.getMinutes()).padStart(2, '0')}`;
1178
1179    historyItem.innerHTML = `
1180      <div style="font-weight: 500; margin-bottom: 4px;">${locationText} · ${distanceText}</div>
1181      <div style="font-size: 13px; color: #5f6368;">${timeText} · ${item.facilityCount}件</div>
1182    `;
1183
1184    historyList.appendChild(historyItem);
1185  });
1186}
1187
1188// モーダルを閉じる
1189/* モーダルの外側を押す・Escで閉じる(2026-08-24 オーナー要望)。
1190   中身を押したときに閉じないよう、押した場所がモーダル本体そのものの時だけ閉じる */
1191(function bindSearchModalDismiss() {
1192  function bind() {
1193    var m = document.getElementById('searchModal');
1194    if (!m) { setTimeout(bind, 300); return; }
1195    if (m._dismissBound) return;
1196    m._dismissBound = true;
1197    m.addEventListener('mousedown', function (e) {
1198      if (e.target === m) hideSearchModal();          // 外側(暗い部分)を押した
1199    });
1200    document.addEventListener('keydown', function (e) {
1201      if (e.key === 'Escape' && m.style.display !== 'none') hideSearchModal();
1202    });
1203  }
1204  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bind);
1205  else bind();
1206})();
1207
1208/* 位置情報をその場で聞き直して、取れたら検索を続ける(2026-08-24)。
1209   取れないときは原因ごとに手順を出す。使っている端末・ブラウザで案内を変える。 */
1210function requestGpsThenSearch() {
1211  var btn = document.querySelector('.search-apply-btn');
1212  var label = btn ? btn.textContent : '';
1213  if (btn) { btn.disabled = true; btn.textContent = '現在地を確認しています…'; }
1214  var restore = function () { if (btn) { btn.disabled = false; btn.textContent = label; } };
1215
1216  if (!navigator.geolocation) {
1217    restore();
1218    _gpsHelp('このブラウザは位置情報に対応していません。', ['「マイ施設から」または「地図上の位置から」をお使いください。']);
1219    return;
1220  }
1221  navigator.geolocation.getCurrentPosition(
1222    function (pos) {
1223      window.userLocation = { lat: pos.coords.latitude, lng: pos.coords.longitude };
1224      restore();
1225      applyNearbySearch();                 // 位置が取れたので、そのまま検索を続ける
1226    },
1227    function (err) {
1228      restore();
1229      var isMac = /Macintosh/.test(navigator.userAgent);
1230      var isFx = /Firefox/.test(navigator.userAgent);
1231      var isSafari = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
1232      var steps = [];
1233      if (err && err.code === 1) {          // 断られた
1234        if (isFx) steps.push('アドレスバー左の錠前アイコンを押し、「位置情報へのアクセス」を「許可」に変えてください。');
1235        else if (isSafari) steps.push('Safari の設定 →「Webサイト」→「位置情報」で、このサイトを「許可」にしてください。');
1236        else steps.push('アドレスバー左のアイコンから、このサイトの位置情報を「許可」にしてください。');
1237        if (isMac) steps.push('それでも出ないときは、Mac の「システム設定 → プライバシーとセキュリティ → 位置情報サービス」で、お使いのブラウザが入になっているか確認してください。');
1238        else steps.push('スマートフォンの場合は、本体の設定で位置情報サービスが入になっているか確認してください。');
1239      } else if (err && err.code === 3) {   // 時間切れ
1240        steps.push('電波の届く場所でもう一度お試しください。');
1241      }
1241 else {                              // 取れない
1242        steps.push('端末の位置情報サービスが入になっているか確認してください。');
1243        if (isMac) steps.push('Mac は「システム設定 → プライバシーとセキュリティ → 位置情報サービス」で、お使いのブラウザを入にします。');
1244      }
1245      steps.push('急ぐときは「マイ施設から」または「地図上の位置から」を選ぶと、現在地なしで探せます。');
1246      _gpsHelp('現在地を取得できませんでした。', steps);
1247    },
1248    { enableHighAccuracy: false, timeout: 10000, maximumAge: 5 * 60 * 1000 }
1249  );
1250}
1251
1252/* 案内の出し方。alert だと1行で読みにくいので、検索の画面の中に出す */
1253function _gpsHelp(title, steps) {
1254  var host = document.querySelector('#tab-nearby') || document.querySelector('.search-modal-content');
1255  if (!host) { alert(title + '\n' + steps.join('\n')); return; }
1256  var old = document.getElementById('gpsHelpBox'); if (old) old.remove();
1257  var box = document.createElement('div');
1258  box.id = 'gpsHelpBox';
1259  box.style.cssText = 'margin:10px 0;padding:12px 14px;border:1px solid #fecaca;background:#fff5f5;border-radius:10px;font-size:13px;color:#7f1d1d;line-height:1.7;';
1260  box.innerHTML = '<b>' + title + '</b><ul style="margin:6px 0 0;padding-left:18px;">' +
1261    steps.map(function (t) { return '<li>' + t + '</li>'; }).join('') + '</ul>' +
1262    '<div style="text-align:right;margin-top:8px;"><button type="button" style="border:1px solid #fecaca;background:#fff;color:#7f1d1d;border-radius:6px;padding:3px 10px;cursor:pointer;" onclick="this.closest(\'#gpsHelpBox\').remove()">閉じる</button></div>';
1263  host.insertBefore(box, host.firstChild);
1264  box.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
1265}
1266window.requestGpsThenSearch = requestGpsThenSearch;
1267
1268function hideSearchModal() {
1269  document.getElementById('searchModal').style.display = 'none';
1270  disableMapSelection();
1271}
1272
1273// モーダルを開く
1274function showSearchModal() {
1275  document.getElementById('searchModal').style.display = 'flex';
1276
1277  // ログイン状態に応じてマイ施設オプションを更新
1278  updateMyFacilityOption();
1279
1280  // デフォルトタブを近くの施設に
1281  switchSearchTab('nearby');
1282
1283  // 基準位置をGPSに設定
1284  const gpsRadio = document.querySelector('input[name="baseLocation"][value="gps"]');
1285  if (gpsRadio) {
1286    gpsRadio.checked = true;
1287  }
1288
1289  // 距離をallに設定
1290  const allDistanceRadio = document.querySelector('input[name="distance"][value="all"]');
1291  if (allDistanceRadio) {
1292    allDistanceRadio.checked = true;
1293  }
1294
1295  // 検索解除ボタンの表示状態を更新
1296  updateClearButtonVisibility();
1297
1298  handleLocationChange();
1299}
1300
1301// 検索解除ボタンの表示状態を更新
1302function updateClearButtonVisibility() {
1303  const clearBtn = document.getElementById('searchClearBtn');
1304  const modalClearBtn = document.getElementById('searchModalClearBtn');
1305
1306  if (clearBtn && modalClearBtn) {
1307    const isVisible = clearBtn.style.display === 'flex';
1308    modalClearBtn.style.display = isVisible ? 'block' : 'none';
1309  }
1310}
1311
1312// 検索結果を解除して全施設を表示
1313window.clearSearchResults = function() {
1314  console.log('Clearing search results...');
1315
1316  // 検索結果インデックスをクリア
1317  currentSearchResultIndices = null;
1318
1319  // 検索解除ボタンを非表示(両方)
1320  const clearBtn = document.getElementById('searchClearBtn');
1321  if (clearBtn) {
1322    clearBtn.style.display = 'none';
1323  }
1324  const modalClearBtn = document.getElementById('searchModalClearBtn');
1325  if (modalClearBtn) {
1326    modalClearBtn.style.display = 'none';
1327  }
1328
1329  // お気に入りフィルターを解除
1330  const favoriteFilterBtn = document.getElementById('favoriteFilterBtn');
1331  if (favoriteFilterBtn && favoriteFilterBtn.classList.contains('active')) {
1332    favoriteFilterBtn.classList.remove('active');
1333    if (favoriteFilterBtn.querySelector('.btn-icon')) {
1334      favoriteFilterBtn.querySelector('.btn-icon').textContent = '☆';
1335    }
1336  }
1337
1338  // 特殊フィルターがONかチェック
1339  const vaivtBtn = document.getElementById('vaivtBtn');
1340  const peritonealBtn = document.getElementById('peritonealBtn');
1341  const homeDialysisBtn = document.getElementById('homeDialysisBtn');
1342  const nightDialysisBtn = document.getElementById('nightDialysisBtn');
1343
1344  const isVAIVTActive = vaivtBtn && vaivtBtn.classList.contains('active');
1345  const isPeritonealActive = peritonealBtn && peritonealBtn.classList.contains('active');
1346  const isHomeDialysisActive = homeDialysisBtn && homeDialysisBtn.classList.contains('active');
1347  const isNightDialysisActive = nightDialysisBtn && nightDialysisBtn.classList.contains('active');
1348
1349  const hasSpecialFilter = isVAIVTActive || isPeritonealActive || isHomeDialysisActive || isNightDialysisActive;
1350
1351  // 施設リストを表示
1352  if (hasSpecialFilter) {
1353    // 特殊フィルターがONの場合、そのフィルターに応じた施設リストを表示
1354    if (isVAIVTActive && typeof window.filterFacilityList === 'function') {
1355      window.filterFacilityList('vaivt');
1356    } else if (isPeritonealActive && typeof window.filterFacilityList === 'function') {
1357      window.filterFacilityList('peritoneal');
1358    } else if (isHomeDialysisActive && typeof window.filterFacilityList === 'function') {
1359      window.filterFacilityList('homeDialysis');
1360    } else if (isNightDialysisActive && typeof window.filterFacilityList === 'function') {
1361      window.filterFacilityList('nightDialysis');
1362    }
1363  } else if (typeof createFacilityList === 'function') {
1364    // 特殊フィルターがOFFなら全施設を表示
1365    createFacilityList();
1366  }
1367
1368  // マップ上のマーカーとラベルを地域フィルターと特殊フィルターに従って再表示
1369  const currentRegionFilter = window.currentRegionFilter || 'all';
1370  const allFacilities = window.allFacilities || window.facilities || [];
1371
1372  if (window.facilityMarkers) {
1373    window.facilityMarkers.forEach((marker, index) => {
1374      if (marker) {
1375        const facility = allFacilities[index];
1376        let shouldShow = true;
1377
1378        // 地域フィルターチェック
1379        if (currentRegionFilter !== 'all' && facility) {
1380          const regionInfo = window.getRegionInfo ? window.getRegionInfo(facility.prefecture || "宮城") : null;
1381          shouldShow = regionInfo && (regionInfo.region === currentRegionFilter || facility.prefecture === currentRegionFilter);
1382        }
1383
1384        // 特殊フィルターチェック
1385        if (shouldShow && hasSpecialFilter && facility) {
1386          if (isVAIVTActive) {
1387            shouldShow = facility.vaivt === "はい";
1388          } else if (isPeritonealActive) {
1389            shouldShow = facility.peritoneal === "あり";
1390          } else if (isHomeDialysisActive) {
1391            shouldShow = facility.homeDialysis === "あり";
1392          } else if (isNightDialysisActive) {
1393            shouldShow = facility.nightDialysis === "あり";
1394          }
1395        }
1396
1397        marker.setVisible(shouldShow);
1398      }
1399    });
1400  }
1401
1402  if (typeof window.updateLabelsVisibility === 'function') {
1403    window.updateLabelsVisibility();
1404  }
1405
1406  // 通知を表示
1407  if (typeof showNotification === 'function') {
1408    showNotification('検索を解除しました', 2000);
1409  }
1410};
1411
1412// お気に入りフィルターを切り替え
1413function toggleFavoriteFilter() {
1414  const favoriteFilterBtn = document.getElementById('favoriteFilterBtn');
1415  if (!favoriteFilterBtn) return;
1416
1417  // デバッグログ:現在のボタン状態
1418  console.log('🔍 toggleFavoriteFilter 呼び出し:', {
1419    classList: Array.from(favoriteFilterBtn.classList),
1420    hasActive: favoriteFilterBtn.classList.contains('active'),
1421    innerHTML: favoriteFilterBtn.innerHTML.substring(0, 50),
1422    _favoriteFilterRestored: window._favoriteFilterRestored
1423  });
1424
1425  // 施設データがまだロードされていない場合は警告を表示
1426  if (!window.facilitiesDataLoaded || !window.facilities || window.facilities.length === 0) {
1427    console.warn('⚠️ 施設データがまだロードされていません。少しお待ちください。');
1428    alert('施設データを読み込み中です。少しお待ちください。');
1429    return;
1430  }
1431
1432  // ★以前はここでスクロール位置を保存して復元していたが、絞り込むと中身が変わるため
1433  //   途中の位置に戻ると分かりにくかった。一番上から見せる(2026-08-16 オーナー指示)
1434  const listContent = document.querySelector('.facility-list-content');
1435
1436  const isActive = favoriteFilterBtn.classList.contains('active');
1437  console.log('🔍 toggleFavoriteFilter: isActive =', isActive);
1438
1439  if (isActive) {
1440    // フィルター解除
1441    favoriteFilterBtn.classList.remove('active');
1442    favoriteFilterBtn.querySelector('.btn-icon').textContent = '☆';
1443
1444    // 重要: createFacilityListを呼ぶ前にlocalStorageを更新する
1445    // これによりcreateFacilityList内でactiveクラスが再追加されることを防ぐ
1446    localStorage.setItem('favoriteFilterActive', 'false');
1447    window._favoriteFilterRestored = true; // 復元処理をスキップさせる
1448
1449    // 特殊フィルターがONかチェック
1450    const vaivtBtn = document.getElementById('vaivtBtn');
1451    const peritonealBtn = document.getElementById('peritonealBtn');
1452    const homeDialysisBtn = document.getElementById('homeDialysisBtn');
1453    const nightDialysisBtn = document.getElementById('nightDialysisBtn');
1454
1455    const isVAIVTActive = vaivtBtn && vaivtBtn.classList.contains('active');
1456    const isPeritonealActive = peritonealBtn && peritonealBtn.classList.contains('active');
1457    const isHomeDialysisActive = homeDialysisBtn && homeDialysisBtn.classList.contains('active');
1458    const isNightDialysisActive = nightDialysisBtn && nightDialysisBtn.classList.contains('active');
1459
1460    const hasSpecialFilter = isVAIVTActive || isPeritonealActive || isHomeDialysisActive || isNightDialysisActive;
1461
1462    // 施設リストを表示
1463    if (hasSpecialFilter) {
1464      // 特殊フィルターがONの場合、そのフィルターに応じた施設リストを表示
1465      if (isVAIVTActive && typeof window.filterFacilityList === 'function') {
1466        window.filterFacilityList('vaivt');
1467      } else if (isPeritonealActive && typeof window.filterFacilityList === 'function') {
1468        window.filterFacilityList('peritoneal');
1469      } else if (isHomeDialysisActive && typeof window.filterFacilityList === 'function') {
1470        window.filterFacilityList('homeDialysis');
1471      } else if (isNightDialysisActive && typeof window.filterFacilityList === 'function') {
1472        window.filterFacilityList('nightDialysis');
1473      }
1474    } else if (typeof createFacilityList === 'function') {
1475      // 特殊フィルターがOFFなら全施設を表示
1476      createFacilityList();
1477    }
1478
1479    // マップ上のマーカーとラベルを地域フィルターと特殊フィルターに従って再表示
1480    const currentRegionFilter = window.currentRegionFilter || 'all';
1481    const allFacilities = window.allFacilities || window.facilities || [];
1482
1483    if (window.facilityMarkers) {
1484      window.facilityMarkers.forEach((marker, index) => {
1485        if (marker) {
1486          const facility = allFacilities[index];
1487          let shouldShow = true;
1488
1489          // 地域フィルターチェック
1490          if (currentRegionFilter !== 'all' && facility) {
1491            const regionInfo = window.getRegionInfo ? window.getRegionInfo(facility.prefecture || "宮城") : null;
1492            shouldShow = regionInfo && (regionInfo.region === currentRegionFilter || facility.prefecture === currentRegionFilter);
1493          }
1494
1495          // 特殊フィルターチェック
1496          if (shouldShow && hasSpecialFilter && facility) {
1497            if (isVAIVTActive) {
1498              shouldShow = facility.vaivt === "はい";
1499            } else if (isPeritonealActive) {
1500              shouldShow = facility.peritoneal === "あり";
1501            } else if (isHomeDialysisActive) {
1502              shouldShow = facility.homeDialysis === "あり";
1503            } else if (isNightDialysisActive) {
1504              shouldShow = facility.nightDialysis === "あり";
1505            }
1506          }
1507
1508          marker.setVisible(shouldShow);
1509        }
1510      });
1511    }
1512
1513    if (window.facilityLabels && typeof window.updateLabelsVisibility === 'function') {
1514      window.updateLabelsVisibility();
1515    }
1516
1517    // 緊急マーカーの表示更新
1518    if (typeof window.updateEmergencyMarkersVisibility === 'function') {
1519      window.updateEmergencyMarkersVisibility();
1520    }
1521  } else {
1522    // フィルター有効化
1523    favoriteFilterBtn.classList.add('active');
1524    favoriteFilterBtn.querySelector('.btn-icon').textContent = '⭐';
1525
1526    // 重要: フィルター状態を即座に保存して復元フラグを設定
1527    localStorage.setItem('favoriteFilterActive', 'true');
1528    window._favoriteFilterRestored = true;
1529
1530    // お気に入り施設のみを表示
1531    filterFavoriteFacilities();
1532
1533    // 緊急マーカーの表示更新
1534    if (typeof window.updateEmergencyMarkersVisibility === 'function') {
1535      window.updateEmergencyMarkersVisibility();
1536    }
1537  }
1538
1539  // お気に入りフィルター状態をlocalStorageに保存(既に上で設定済みだが念のため)
1540  const newState = favoriteFilterBtn.classList.contains('active');
1541  localStorage.setItem('favoriteFilterActive', newState ? 'true' : 'false');
1542  console.log('💾 お気に入りフィルター状態を保存:', newState);
1543
1544  // 一番上に戻す(DOMの更新後に実行)
1545  setTimeout(() => {
1546    if (listContent) {
1547      listContent.scrollTop = 0;
1548    }
1549  }, 0);
1550}
1551
1552// お気に入りフィルター状態を復元
1553function restoreFavoriteFilterState() {
1554  const savedState = localStorage.getItem('favoriteFilterActive');
1555  console.log('📂 お気に入りフィルター状態を読み込み:', savedState);
1556
1557  if (savedState === 'true') {
1558    let retryCount = 0;
1559    const maxRetries = 100; // 最大10秒待機
1560
1561    // 施設データとお気に入りデータがロードされるまで待機
1562    const checkAndApply = () => {
1563      retryCount++;
1564
1565      // 施設データのチェック
1566      const facilitiesReady = window.facilitiesDataLoaded && window.facilities && window.facilities.length > 0;
1567
1568      // お気に入りデータのチェック
1569      let favoritesReady = false;
1570      if (window.currentUser && window.currentUser.facilityId) {
1571        // ログイン中:loadUserFavoritesが完了したかチェック
1572        if (!window.userFavoritesLoaded) {
1573          console.log('📂 restoreFavoriteFilterState: loadUserFavoritesがまだ完了していないため待機');
1574          favoritesReady = false;
1575        } else {
1576          // loadUserFavorites完了済み
1577          favoritesReady = true;
1578        }
1579      } else if (window.authStateResolved && !window.currentUser) {
1580        // 認証状態が確定済みでユーザーなし → ログアウト状態(リトライ不要)
1581        console.log('📂 restoreFavoriteFilterState: ログアウト状態のため復元中止');
1582        return;
1583      } else if (window.currentUser && !window.currentUser.facilityId) {
1584        // currentUserは存在するがfacilityIdがまだ設定されていない → 認証処理中、待機
1585        console.log('📂 restoreFavoriteFilterState: facilityIdがまだ設定されていないため待機');
1586        favoritesReady = false;
1587      } else if (!window.authStateResolved) {
1588        // 認証状態がまだ確定していない → 待機
1589        favoritesReady = false;
1590      } else {
1591        // その他のケース(ゲストユーザーなど)
1592        favoritesReady = true;
1593      }
1594
1595      console.log('📂 restoreFavoriteFilterState チェック:', {
1596        facilitiesReady,
1597        favoritesReady,
1598        retryCount,
1599        hasFacilityId: window.currentUser?.facilityId
1600      });
1601
1602      if (facilitiesReady && favoritesReady) {
1603        const favoriteFilterBtn = document.getElementById('favoriteFilterBtn');
1604        if (favoriteFilterBtn && !favoriteFilterBtn.classList.contains('active')) {
1605          console.log('✅ お気に入りフィルターを復元(データロード完了)');
1606          toggleFavoriteFilter();
1607        }
1608      } else if (retryCount < maxRetries) {
1609        // まだロードされていない場合は100ms後に再試行
1610        setTimeout(checkAndApply, 100);
1611      } else {
1612        console.warn('⚠️ お気に入りフィルター復元タイムアウト');
1613      }
1614    };
1615    // 少し遅延させてからチェック開始
1616    setTimeout(checkAndApply, 200);
1617  }
1618}
1619
1620// お気に入り施設のみを表示(施設名ベース)
1621function filterFavoriteFacilities() {
1622  const savedFacilityNames = getSavedFacilities();
1623  console.log('filterFavoriteFacilities: savedFacilityNames =', savedFacilityNames);
1624
1625  // 施設データがまだロードされていない場合は警告を表示して終了
1626  if (!window.facilities || window.facilities.length === 0) {
1627    console.warn('⚠️ 施設データがまだロードされていません。少しお待ちください。');
1628    const listContainer = document.getElementById("facilityListItems");
1629    if (listContainer) {
1630      listContainer.innerHTML = `
1631        <div style="padding: 40px 20px; text-align: center; color: #666;">
1632          施設データを読み込み中です...<br>
1633          少しお待ちください
1634        </div>
1635      `;
1636    }
1637    return;
1638  }
1639
1640  if (savedFacilityNames.length === 0) {
1641    const listContainer = document.getElementById("facilityListItems");
1642    listContainer.innerHTML = `
1643      <div style="padding: 40px 20px; text-align: center; color: #666;">
1644        お気に入りに登録された施設はありません<br>
1645        施設一覧の⭐マークから登録できます
1646      </div>
1647    `;
1648
1649    // マーカーも非表示にする
1650    if (window.facilityMarkers) {
1651      window.facilityMarkers.forEach(marker => {
1652        if (marker) marker.setVisible(false);
1653      });
1654    }
1655    if (window.facilityLabels) {
1656      window.facilityLabels.forEach(label => {
1657        if (label && typeof label.setMap === 'function') {
1658          label.setMap(null);
1659        }
1660      });
1661    }
1662    return;
1663  }
1664
1665  // お気に入り施設を施設名で検索して取得
1666  let favoriteFacilities = [];
1667  savedFacilityNames.forEach(savedName => {
1668    const index = window.facilities.findIndex(f => f.name === savedName);   // 名前OK:施設名(人物ではない)
1669    if (index !== -1) {
1670      const facility = window.facilities[index];
1671      favoriteFacilities.push({ ...facility, originalIndex: index });
1672    } else {
1673      console.warn('⚠️ お気に入り施設が見つかりません:', savedName);
1674    }
1675  });
1676
1677  // 現在の地域フィルターを適用
1678  const currentRegionFilter = window.currentRegionFilter || 'all';
1679  if (currentRegionFilter !== 'all') {
1680    favoriteFacilities = favoriteFacilities.filter(facility => {
1681      const regionInfo = window.getRegionInfo ? window.getRegionInfo(facility.prefecture || "宮城") : null;
1682      return regionInfo && (regionInfo.region === currentRegionFilter || facility.prefecture === currentRegionFilter);
1683    });
1684  }
1685
1686  displayFavoriteFacilities(favoriteFacilities);
1687
1688  // マップ上のマーカーとラベルも更新(地域フィルターも考慮)
1689  if (window.facilityMarkers) {
1690    window.facilityMarkers.forEach((marker, index) => {
1691      if (marker) {
1692        const facility = window.facilities[index];
1693        const isFavorite = facility && savedFacilityNames.includes(facility.name);
1694        let isInRegion = true;
1695
1696        if (currentRegionFilter !== 'all' && facility) {
1697          const regionInfo = window.getRegionInfo ? window.getRegionInfo(facility.prefecture || "宮城") : null;
1698          isInRegion = regionInfo && (regionInfo.region === currentRegionFilter || facility.prefecture === currentRegionFilter);
1699        }
1700
1701        marker.setVisible(isFavorite && isInRegion);
1702      }
1703    });
1704  }
1705
1706  if (window.facilityLabels) {
1707    window.facilityLabels.forEach((label, index) => {
1708      const facility = window.facilities[index];
1709      const isFavorite = facility && savedFacilityNames.includes(facility.name);
1710      let isInRegion = true;
1711
1712      if (currentRegionFilter !== 'all' && facility) {
1713        const regionInfo = window.getRegionInfo ? window.getRegionInfo(facility.prefecture || "宮城") : null;
1714        isInRegion = regionInfo && (regionInfo.region === currentRegionFilter || facility.prefecture === currentRegionFilter);
1715      }
1716
1717      if (label && typeof label.setMap === 'function') {
1718        if (isFavorite && isInRegion) {
1719          if (window.map) {
1720            label.setMap(window.map);
1721          }
1722        } else {
1723          label.setMap(null);
1724        }
1725      }
1726    });
1727  }
1728}
1729
1730// お気に入り施設を表示
1731function displayFavoriteFacilities(facilities) {
1732  const listContainer = document.getElementById("facilityListItems");
1733  listContainer.innerHTML = "";
1734
1735  // スクロール位置は保持する(ユーザー体験向上のため)
1736
1737  facilities.forEach((facility) => {
1738    const colorData = getPatientColor(facility.patients);
1739    const facilityItem = document.createElement("div");
1740    facilityItem.className = "facility-item";
1741    facilityItem.dataset.index = facility.originalIndex;
1742    facilityItem.style.backgroundColor = colorData.lightColor;
1743    facilityItem.style.borderLeftColor = colorData.color;
1744
1745    facilityItem.innerHTML = `
1746      <div style="display: flex; justify-content: space-between; align-items: center;">
1747        <div class="facility-name">${facility.name}</div>
1748        <button class="facility-star-btn" data-facility-index="${facility.originalIndex}" title="お気に入りから削除">
1749          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#fbbf24" width="20" height="20"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>
1750        </button>
1751      </div>
1752      <div class="facility-patients" style="color: ${colorData.color}; display: flex; align-items: center; justify-content: space-between; min-height: 24px;">
1753        <span>患者数: ${facility.patients}人 | ベッド数: ${facility.beds}床</span>
1754        <div style="display: flex; gap: 8px; flex-shrink: 0;">
1755          <button class="detail-btn" data-facility-index="${facility.originalIndex}">詳細</button>
1756          ${window.currentUser ? `<button class="referral-btn" data-facility-index="${facility.originalIndex}" data-facility-name="${facility.name}" data-vaivt="${facility.vaivt}">紹介</button>` : ''}
1757        </div>
1758      </div>
1759      <div class="facility-details" style="margin-top: 8px;">
1760        ${facility.postalCode ? `
1761        <div class="facility-detail-row">
1762          <span class="facility-detail-label">〒:</span>
1763          <span class="facility-detail-value">${facility.postalCode}</span>
1764        </div>
1765        ` : ''}
1766        <div class="facility-detail-row">
1767          <span class="facility-detail-label">住所:</span>
1768          <span class="facility-detail-value">${facility.address}</span>
1769        </div>
1770        <div class="facility-detail-row">
1771          <span class="facility-detail-label">電話:</span>
1772          <span class="facility-detail-value">${facility.phone}</span>
1773        </div>
1774        <div class="facility-detail-row">
1775          <span class="facility-detail-label">FAX:</span>
1776          <span class="facility-detail-value">${facility.fax && facility.fax !== "undefined" ? facility.fax : "情報なし"}</span>
1777        </div>
1778        ${facility.website && facility.website !== null ? `
1779        <div class="facility-detail-row">
1780          <span class="facility-detail-label">HP:</span>
1781          <span class="facility-detail-value">
1782            <a href="${typeof normalizeUrl === 'function' ? normalizeUrl(facility.website) : facility.website}" target="_blank" rel="noopener noreferrer">
1783              ${facility.website}
1784            </a>
1785          </span>
1786        </div>
1787        ` : `
1788        <div class="facility-detail-row">
1789          <span class="facility-detail-label">HP:</span>
1790          <span class="facility-detail-value">未設定</span>
1791        </div>
1792        `}
1793      </div>
1794      <div class="facility-extended-details" id="extended_${facility.originalIndex}" style="display: none;">
1795        <div class="detail-row"><span>送迎対応:</span><span>${getExtendedData(facility.originalIndex, "transport", "未設定")}</span></div>
1796        <div class="detail-row"><span>駐車場:</span><span>${getExtendedData(facility.originalIndex, "parking", "未設定")}</span></div>
1797        <div class="detail-row"><span>入院ベッド:</span><span>${getExtendedData(facility.originalIndex, "inpatient", "未設定")}</span></div>
1798        <div class="detail-row"><span>活動制限:</span><span>${getExtendedData(facility.originalIndex, "activity", "未設定")}</span></div>
1799        <div class="detail-row"><span>生活保護受け入れ:</span><span>${getExtendedData(facility.originalIndex, "welfare", "未設定")}</span></div>
1800        <div class="detail-row"><span>精神疾患受け入れ:</span><span>${getExtendedData(facility.originalIndex, "mental", "未設定")}</span></div>
1801        <div class="detail-row"><span>腹膜透析受け入れ:</span><span>${getExtendedData(facility.originalIndex, "peritoneal", "未設定")}</span></div>
1802        <div class="detail-row"><span>在宅透析:</span><span>${getExtendedData(facility.originalIndex, "homeDialysis", "未設定")}</span></div>
1803        <div class="detail-row"><span>夜間透析:</span><span>${getExtendedData(facility.originalIndex, "nightDialysis", "未設定")}</span></div>
1804        <div class="detail-row"><span>VAIVT(PTA)対応施設:</span><span>${getExtendedData(facility.originalIndex, "vaivt", "未設定")}</span></div>
1805        ${window.getVaivtReferralDisplay ? window.getVaivtReferralDisplay(facility.originalIndex) : ''}
1806        <div class="detail-row"><span>旅行透析の受け入れ:</span><span>${getExtendedData(facility.originalIndex, "travelDialysis", "未設定")}</span></div>
1807        <div style="margin-top: 10px;">
1808          <strong>案内情報:</strong><br>
1809          ${getExtendedData(facility.originalIndex, "info", "未設定")}
1810        </div>
1811        ${window.generateOccupancyCharts ? window.generateOccupancyCharts(facility) : ''}
1812      </div>
1813    `;
1814
1815    // 星ボタンのイベントリスナーを追加
1816    const starBtn = facilityItem.querySelector('.facility-star-btn');
1817    if (starBtn) {
1818      const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
1819
1820      const handleStarClick = (e) => {
1821        e.preventDefault();
1822        e.stopPropagation();
1823        const facilityIndex = parseInt(starBtn.getAttribute('data-facility-index'));
1824        console.log('Star button activated:', facilityIndex);
1825        toggleSaveFacility(facilityIndex);
1826      };
1827
1828      if (isTouchDevice) {
1829        starBtn.addEventListener('touchend', handleStarClick, { passive: false });
1830      }
1831
1832      // すべてのデバイスでclickイベントも追加(フォールバック)
1833      starBtn.addEventListener('click', handleStarClick);
1834    }
1835
1836    // 詳細ボタンのイベントリスナーを追加 (Windows対応)
1837    const detailBtn = facilityItem.querySelector('.detail-btn');
1838    if (detailBtn) {
1839      const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
1840
1841      const handleDetailClick = (e) => {
1842        e.preventDefault();
1843        e.stopPropagation();
1844        const facilityIndex = parseInt(detailBtn.getAttribute('data-facility-index'));
1845        console.log('Detail button activated for facility:', facilityIndex);
1846
1847        // 少し遅延させて確実に実行
1848        setTimeout(() => {
1849          if (typeof window.toggleFacilityDetails === 'function') {
1850            window.toggleFacilityDetails(facilityIndex);
1851          }
1852        }, 0);
1853      };
1854
1855      if (isTouchDevice) {
1856        detailBtn.addEventListener('touchend', handleDetailClick, { passive: false });
1857      }
1858
1859      // すべての環境でclickもリッスン(フォールバック)
1860      detailBtn.addEventListener('click', handleDetailClick);
1861    }
1862
1863    // 紹介ボタンのイベントリスナーを追加
1864    const referralBtn = facilityItem.querySelector('.referral-btn');
1865    if (referralBtn) {
1866      const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
1867
1868      const handleReferralClick = (e) => {
1869        e.preventDefault();
1870        e.stopPropagation();
1871        const facilityIndex = parseInt(referralBtn.getAttribute('data-facility-index'));
1872        const facilityName = referralBtn.getAttribute('data-facility-name');
1873        const vaivt = referralBtn.getAttribute('data-vaivt');
1874        console.log('Referral button clicked for facility:', facilityName);
1875
1876        // VAIVT対応施設かどうかで選択肢を表示
1877        if (typeof window.showReferralOptions === 'function') {
1878          window.showReferralOptions(facilityIndex, facilityName, vaivt);
1879        }
1880      };
1881
1882      if (isTouchDevice) {
1883        referralBtn.addEventListener('touchend', handleReferralClick, { passive: false });
1884      }
1885      referralBtn.addEventListener('click', handleReferralClick);
1886    }
1887
1888    facilityItem.addEventListener("click", (e) => {
1889      if (!e.target.closest('.facility-star-btn') && !e.target.closest('.detail-btn') && !e.target.closest('.referral-btn')) {
1890        selectFacilityFromList(facility.originalIndex);
1891      }
1892    });
1893
1894    listContainer.appendChild(facilityItem);
1895  });
1896
1897  // ログイン中のユーザー施設のハイライトを適用
1898  if (window.currentUser && window.facilities) {
1899    const userFacilityIndex = window.facilities.findIndex(f =>
1900      window.currentUser.facilityName && f.name === window.currentUser.facilityName   // 名前OK:施設名(人物ではない)
1901    );
1902    if (userFacilityIndex !== -1) {
1903      setTimeout(() => {
1904        const facilityItem = document.querySelector(`[data-index="${userFacilityIndex}"]`);
1905        if (facilityItem) {
1906          facilityItem.classList.add('user-facility');
1907        }
1908      }, 100);
1909    }
1910  }
1911  if (typeof window.scrollFacilityListTop === 'function') window.scrollFacilityListTop();
1912}
1913
1914// グローバル関数として公開
1915window.switchSearchTab = switchSearchTab;
1916window.handleLocationChange = handleLocationChange;
1917window.applyNearbySearch = applyNearbySearch;
1918window.resetSearch = resetSearch;
1919window.toggleSaveFacility = toggleSaveFacility;
1920window.getSavedFacilities = getSavedFacilities;
1921window.isFacilitySaved = isFacilitySaved;
1922window.hideSearchModal = hideSearchModal;
1923window.showSearchModal = showSearchModal;
1924window.loadUserFavorites = loadUserFavorites;
1925window.toggleFavoriteFilter = toggleFavoriteFilter;
1926window.restoreFavoriteFilterState = restoreFavoriteFilterState;
1927
1928// グローバル変数として公開
1929Object.defineProperty(window, 'currentSearchResultIndices', {
1930  get: function() { return currentSearchResultIndices; },
1931  set: function(value) { currentSearchResultIndices = value; }
1932});
1933
1934// search.js読み込み完了時にお気に入りフィルター復元を自動呼び出し
1935console.log('📂 search.js 読み込み完了、お気に入りフィルター復元をスケジュール');
1936setTimeout(() => {
1937  console.log('📂 search.js からお気に入りフィルター復元を実行');
1938  restoreFavoriteFilterState();
1939}, 1500);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.