/* =============================================================
全国ページ — フィルタ / 検索 / ソート / 3つの表示モード
============================================================= */
const { useState, useMemo, useEffect } = React;
const YEARS = [
{ key: "y2023_range", label: "2023" },
{ key: "y2024_range", label: "2024" },
{ key: "y2025_range", label: "2025" }
];
/* セグメント型トグル */
function Segmented({ options, value, onChange, label }) {
return (
{options.map(o => (
))}
);
}
/* レンジ絞り込みチップ */
function RangeChips({ selected, onToggle }) {
return (
{window.RANGES.map(r => {
const on = selected.includes(r);
return (
);
})}
);
}
/* ---- 1行=1施設(テーブル行 / カード / レンジ重視) ---- */
function YearProgression({ row }) {
return (
{YEARS.map((y, i) => (
{y.label}
{i < YEARS.length - 1 && }
))}
);
}
function FacilityCard({ row, prefHref }) {
return (
{row.facility}
{row.address &&
{row.address}
}
{row.note && {row.note}
}
{row.pref}のページへ
);
}
/* 地図セクションの表示切替(false で非表示) */
const SHOW_MAP = false;
function App() {
const data = window.SURVEY_DATA;
const prefsWithData = useMemo(() => {
const s = new Set(data.map(d => d.pref_slug));
return window.PREF_LIST.filter(p => s.has(p.slug));
}, []);
const [q, setQ] = useState("");
const [pref, setPref] = useState("all");
const [type, setType] = useState("all");
const [hasOnly, setHasOnly] = useState(false); // 有のみ
const [rangeYear, setRangeYear] = useState("y2025_range");
const [ranges, setRanges] = useState([]); // 空=すべて
const [sort, setSort] = useState("name"); // name | range
const [view, setView] = useState("table"); // table | card | trend
const toggleRange = (r) =>
setRanges(s => s.includes(r) ? s.filter(x => x !== r) : [...s, r]);
const results = useMemo(() => {
let rows = data.filter(d => {
if (q && !d.facility.toLowerCase().includes(q.trim().toLowerCase())) return false;
if (pref !== "all" && d.pref_slug !== pref) return false;
if (type !== "all" && d.type !== type) return false;
if (hasOnly && d.has_surgery !== "有") return false;
if (ranges.length && !ranges.includes(d[rangeYear])) return false;
return true;
});
if (sort === "name") {
rows = [...rows].sort((a, b) => a.facility.localeCompare(b.facility, "ja"));
} else {
rows = [...rows].sort((a, b) => {
const diff = (window.RANGE_ORDER[b.y2025_range] ?? -2) - (window.RANGE_ORDER[a.y2025_range] ?? -2);
return diff !== 0 ? diff : a.facility.localeCompare(b.facility, "ja");
});
}
return rows;
}, [q, pref, type, hasOnly, ranges, rangeYear, sort]);
const prefHref = (slug) => `都道府県別ページ.html?pref=${slug}`;
const activeFilters = (pref !== "all") + (type !== "all") + (hasOnly ? 1 : 0) + (ranges.length ? 1 : 0) + (q ? 1 : 0);
const resetAll = () => { setQ(""); setPref("all"); setType("all"); setHasOnly(false); setRanges([]); setSort("name"); };
const pickFromMap = (slug) => {
setPref(slug);
const f = document.getElementById("finder");
if (f) f.scrollIntoView({ behavior: "smooth", block: "start" });
};
return (
{/* HERO */}
{/* MAP + FINDER */}
{SHOW_MAP && }
回答施設をさがす
該当 {results.length} 件 / 全 {data.length} 件(回答施設)
{sort === "range" &&
「2025レンジ順」は便宜的な区分の並びです(500件以上>300–499>150–299>60–149>30–59>10–29>0–9>未回答)。ランキングや優劣を示すものではありません。
}
{/* 結果 */}
{results.length === 0 ? (
条件に一致する回答施設が見つかりませんでした。
) : view === "table" ? (
| 都道府県 | 施設名 | 種別 |
実施有無 | 2023 | 2024 | 2025 |
リンク |
{results.map((r, i) => (
| {r.pref} |
{r.facility}
{r.address && {r.address}}
{r.note && {r.note}}
|
|
|
|
|
|
|
))}
) : view === "card" ? (
{results.map((r, i) => )}
) : (
{results.map((r, i) => (
))}
)}
{/* 都道府県ショートカット */}
);
}
ReactDOM.createRoot(document.getElementById("root")).render();