const { useState, useEffect, useMemo } = React;
const PER_PAGE = 5;
const CAT_LABELS = { info: 'お知らせ', media: 'メディア掲載', press: 'プレスリリース', blog: '社内ブログ' };
const CAT_STYLES = {
press: { fontSize: 11.5, fontWeight: 600, color: '#3331A4', background: 'rgba(51,49,164,.07)', padding: '3px 10px', borderRadius: 5 },
media: { fontSize: 11.5, fontWeight: 600, color: '#1a6b3a', background: 'rgba(26,107,58,.07)', padding: '3px 10px', borderRadius: 5 },
info: { fontSize: 11.5, fontWeight: 600, color: '#5E5E63', background: '#F7F6F4', padding: '3px 10px', borderRadius: 5 },
blog: { fontSize: 11.5, fontWeight: 600, color: '#9A6B00', background: 'rgba(154,107,0,.08)', padding: '3px 10px', borderRadius: 5 },
};
function formatNewsDate(dateStr) {
const d = new Date(dateStr);
return `${d.getFullYear()}.${String(d.getMonth() + 1).padStart(2, '0')}.${String(d.getDate()).padStart(2, '0')}`;
}
function stripTags(html) {
return (html || '').replace(/<[^>]+>/g, '').trim();
}
function FilterTabs({ filter, onChange }) {
const options = [
{ key: 'all', label: 'すべて' },
{ key: 'info', label: 'お知らせ' },
{ key: 'media', label: 'メディア掲載' },
{ key: 'press', label: 'プレスリリース' },
{ key: 'blog', label: '社内ブログ' },
];
return (
{options.map((o) => {
const active = filter === o.key;
return (
);
})}
);
}
function NewsRow({ item }) {
return (
{item.date}
{item.tag}
{item.title}
→
);
}
function Pagination({ page, totalPages, onChange }) {
if (totalPages <= 1) return null;
const pages = Array.from({ length: totalPages }, (_, i) => i + 1);
const btnStyle = (active, disabled) => ({
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
height: 38, minWidth: 38, padding: '0 14px', borderRadius: 8,
fontSize: 14, fontWeight: active ? 700 : 500,
background: active ? '#111111' : '#F7F6F4',
color: disabled ? '#CBCAC6' : (active ? '#fff' : '#111111'),
border: active ? 'none' : '1px solid #ECEAE6',
cursor: disabled ? 'default' : 'pointer',
fontFamily: 'inherit',
});
return (
{pages.map((p) => (
))}
);
}
function NewsApp() {
const [posts, setPosts] = useState(null);
const [filter, setFilter] = useState('all');
const [page, setPage] = useState(1);
useEffect(() => {
Promise.all([
fetch(`${WP_API}/wp/v2/news?per_page=100&orderby=date&order=desc&_fields=id,date,link,title,news_category`)
.then((r) => (r.ok ? r.json() : [])),
fetch(`${WP_API}/wp/v2/news_category?per_page=100&_fields=id,slug,name`)
.then((r) => (r.ok ? r.json() : [])),
])
.then(([data, terms]) => {
const termById = {};
(terms || []).forEach((t) => { termById[t.id] = t; });
const mapped = (data || []).map((item) => {
const catTerm = termById[(item.news_category || [])[0]];
return {
id: item.id,
date: formatNewsDate(item.date),
cat: catTerm ? catTerm.slug : 'info',
tag: catTerm ? catTerm.name : 'お知らせ',
title: stripTags(item.title.rendered),
link: item.link,
};
});
setPosts(mapped);
})
.catch(() => setPosts([]));
}, []);
const filtered = useMemo(() => {
if (!posts) return [];
return filter === 'all' ? posts : posts.filter((p) => p.cat === filter);
}, [posts, filter]);
const totalPages = Math.max(1, Math.ceil(filtered.length / PER_PAGE));
const currentPage = Math.min(page, totalPages);
const pageItems = filtered.slice((currentPage - 1) * PER_PAGE, currentPage * PER_PAGE);
const handleFilterChange = (key) => {
setFilter(key);
setPage(1);
};
if (posts === null) {
return ;
}
return (
<>
{pageItems.length === 0 ? (
該当するお知らせはありません。
) : (
pageItems.map((item) =>
)
)}
>
);
}
ReactDOM.createRoot(document.getElementById('news-app')).render();