// OTA NOW Resort Hotel — Group & Party Stays (団体・グループ宿泊) // 意匠は ota-now-banquet.jsx と同一。数値は公式サイトの確定値のみを使い、推測値は書かない。 const IMG = (typeof window !== 'undefined' && window.__IMG_BASE) || 'images'; const L = (fileName) => `${IMG}/${fileName}`; const RES_URL = 'index.html#tripla-slot'; // ホームの予約バー(tripla)へ誘導 const FORM_ENDPOINT = 'group-inquiry.php'; // 同一オリジン const MIN_GROUP_GUESTS = 15; // 団体として承る最少人数 const HOTEL = { nameJa: '太田ナウリゾートホテル', addr: '〒373-0851 群馬県太田市飯田町1267', tel: '0276-49-2111', telHours: '9:00〜20:00', fax: '0276-50-1171', access: '東武伊勢崎線 太田駅 南口より徒歩5分', }; // このページ内のナビ。既存3ページの NAV(8項目)は変更しない。 // 自分自身を現在地として示すため、このページでは「宿泊プラン」に代えて「団体予約」を置く。 const NAV = [ { ja: 'トップ', en: 'Top', href: 'index.html' }, { ja: '客室', en: 'Rooms', href: 'index.html#rooms' }, { ja: 'サウナスイート', en: 'Sauna', href: 'sauna.html' }, { ja: '朝食', en: 'Breakfast', href: 'index.html#breakfast' }, { ja: '館内・施設', en: 'Facility', href: 'index.html#facility' }, { ja: '宴会・会議', en: 'Banquet', href: 'banquet.html' }, { ja: '団体予約', en: 'Group', href: '#top' }, { ja: '宿泊プラン', en: 'Plans', href: 'index.html#plans' }, { ja: 'アクセス', en: 'Access', href: 'index.html#access' }, ]; // === 客室 ================================================================== // 公式サイト(ota-now-final.jsx)の確定値。本館57室+別館20室=全77室。 // isGroupInventory … 団体でのお取り扱いに含めるか(サウナスイート1室のみ除外) const ROOMS = [ { id: 'sgl-s', wing: '本館', ja: '喫煙シングル', size: '14㎡', capacity: '1名', units: 17, note: '110cm幅ベッド × 1', isGroupInventory: true }, { id: 'sgl-n', wing: '本館', ja: '禁煙シングル', size: '14㎡', capacity: '1名', units: 31, note: '110cm幅ベッド × 1', isGroupInventory: true }, { id: 'twn', wing: '本館', ja: 'ツイン', size: '28㎡', capacity: '2〜3名', units: 8, note: 'ベッド2台+エキストラベッド対応可・禁煙', isGroupInventory: true }, { id: 'ste', wing: '本館', ja: 'サウナスイート', size: '48㎡', capacity: '2名', units: 1, note: '客室内プライベートサウナ付・1室限定', isGroupInventory: false }, { id: 'atw', wing: '別館アネックス', ja: 'ツイン', size: '34㎡', capacity: '2名', units: 8, note: 'ミニキッチン・冷蔵庫・洗濯機 完備・禁煙', isGroupInventory: true }, { id: 'adb', wing: '別館アネックス', ja: 'ダブル', size: '34㎡', capacity: '2名', units: 10, note: 'ミニキッチン・冷蔵庫・洗濯機 完備', isGroupInventory: true }, { id: 'afo', wing: '別館アネックス', ja: 'フォース', size: '34㎡', capacity: '4名', units: 2, note: 'ミニキッチン・冷蔵庫・洗濯機 完備・禁煙', isGroupInventory: true }, ]; const SINGLE_ROOM_COUNT = 48; // 喫煙17 + 禁煙31 const TOTAL_ROOM_COUNT = 77; // 本館57 + 別館20 const ANNEX_ROOM_COUNT = 20; // 別館アネックス。全室に洗濯機 const PARKING_CAR_COUNT = 45; // 提携パークレポ駐車場 // 宴会場の収容人数は、既存サイト内で 27名 / 52名 / 250名 と記載が食い違っており // 実態が確認できていない。誤った数字を出さないよう、このページには人数も利用時間も書かない。 // 確認が取れたら、ここに定数を置いて各所に反映する。 // === SHARED ================================================================ const OHead = ({ num, en, ja, title, lead, light }) => (
{num && № {num}} {ja && {ja}}
{en &&

{en}

} {title &&
{title}
} {lead &&

{lead}

}
); const Section = ({ id, tone = 'paper', children }) => (
{children}
); const Nav = () => { const [isOpen, setIsOpen] = React.useState(false); return ( ); }; const Hero = () => (
太田ナウリゾートホテル 外観
Group & Party Stays太田駅 徒歩 5 分{MIN_GROUP_GUESTS} 名様 — 以上
Business · Training · Sports Camps

GROUP

団 体 ・ グ ル ー プ 宿 泊

{MIN_GROUP_GUESTS}名様以上の団体・グループのご宿泊を承ります。
企業の研修・出張から、スポーツ合宿・部活動の遠征まで。

Scroll to explore
); // === № 01 イントロ ========================================================= const FACTS = [ { k: 'Rooms', v: `全${TOTAL_ROOM_COUNT}室`, s: '本館57室 / 別館20室' }, { k: 'Single', v: `${SINGLE_ROOM_COUNT}室`, s: '1名1室で最大48名様' }, { k: 'Annex', v: `${ANNEX_ROOM_COUNT}室`, s: '全室に洗濯機・ミニキッチン' }, { k: 'Access', v: '徒歩5分', s: '太田駅 南口より' }, ]; const Intro = () => (
{FACTS.map((f, i) => (
{f.k} {f.v} {f.s}
))}
); // === № 02 用途別のご提案 =================================================== const USE_CASES = [ { en: 'Business', ja: '法人研修・会議・出張', img: 'room-single.jpg', lead: '泊まるところと会議するところが、同じ建物の中にあります。', points: [ `シングル${SINGLE_ROOM_COUNT}室。1名1室のまま最大48名様までご宿泊いただけます`, '宴会・会議室を館内に併設。移動時間ゼロで研修が組めます', 'プロジェクター・スクリーン・マイク・ホワイトボード・高速Wi-Fi をご用意', '禁煙・喫煙どちらのシングルもご用意。ご希望に合わせて割り振ります', '太田駅 南口より徒歩5分。前泊・後泊の移動が読みやすい立地です', ], }, { en: 'Sports', ja: 'スポーツ合宿・部活動遠征', img: 'annex-twin.jpg', lead: 'ユニフォームの洗濯が、客室の中で終わります。', points: [ `別館アネックス${ANNEX_ROOM_COUNT}室は全室に洗濯機・ミニキッチン・冷蔵庫を完備`, '本館にはコインランドリーもございます', '別館フォース(4名・34㎡)、ツイン(エキストラベッド対応可)で人数に合わせた部屋割りが可能', '和朝食(焼き魚・和惣菜・特製朝カレー)。お時間のご相談に応じます', '大型バスでお越しの場合は駐車について事前にご相談ください', ], }, ]; const UseCases = () => (
{USE_CASES.map((u, i) => (
{u.ja} {u.en}

{u.ja}

{u.lead}

    {u.points.map((p, j) =>
  • {p}
  • )}
))}
); // === № 03 客室と収容人数 =================================================== // 人数のレンジで並べると「多いほど使える部屋の種類が減る」ように読めてしまうため、 // 部屋の組み方(考え方)で並べる。 const ROOM_ALLOCATION = [ { range: '1名1室でご用意', plan: `シングル${SINGLE_ROOM_COUNT}室。48名様までは、全員おひとり部屋でお泊まりいただけます。` }, { range: 'ご予算を抑える', plan: 'ツイン(2〜3名)・別館(2〜4名)を相部屋でご利用いただくと、お一人あたりの料金を抑えられます。' }, { range: '49名様以上', plan: 'シングル48室に、ツイン・別館の相部屋を組み合わせてご案内します。' }, { range: '部屋割りはお任せ', plan: 'ご人数・男女比・ご予算をお伺いし、こちらで部屋割りの案をご提案します。' }, ]; const Rooms = () => (
タイプ広さ定員室数設備
{ROOMS.map((r) => (
{r.wing} {r.ja} {r.size} {r.capacity} {r.units}室 {r.note}
))}

※ サウナスイート(1室限定)は団体でのお取り扱いに含めておりません。ご希望の場合は個別にご相談ください。

部屋割りの考え方

{ROOM_ALLOCATION.map((a, i) => (
{a.range} {a.plan}
))}

※ 空室状況により組み合わせは変わります。上記はあくまで目安としてご覧ください。

); // === № 04 団体のお客様へ =================================================== const SERVICES = [ { t: '一括チェックイン', d: 'ご到着時、ご代表者様にまとめてお手続きいただけます。ご宿泊者名簿を事前にお預かりします。' }, { t: '朝食のお時間のご相談', d: '和朝食は7:00〜9:30(最終入場9:00)。ご出発のお時間に合わせた対応はご相談に応じます。' }, { t: '宴会・会議室との組み合わせ', d: '館内の宴会・会議室を、研修や懇親会にあわせてご利用いただけます。ご人数に合わせた会場をご案内します。' }, { t: '洗濯設備', d: `別館アネックス${ANNEX_ROOM_COUNT}室は全室に洗濯機を完備。本館にはコインランドリーがございます。` }, { t: '大型バスの駐車', d: `提携駐車場は乗用車${PARKING_CAR_COUNT}台(24時間最大500円)。大型バスでお越しの場合は事前にご相談ください。` }, { t: 'お支払方法のご相談', d: 'ご到着時またはご出発時に一括でご精算いただけます。お支払い方法はご相談ください。' }, ]; const Services = () => (
{SERVICES.map((s, i) => (
{String(i + 1).padStart(2, '0')}

{s.t}

{s.d}

))}
); // === № 05 ご予約の流れ ===================================================== const FLOW_STEPS = [ { t: 'お問い合わせ', d: '下記フォーム(24時間受付)またはお電話(9:00〜20:00)でご連絡ください。' }, { t: '空室確認・お見積り', d: 'ご希望の日程の空室状況を確認し、1〜2営業日以内にお見積りをご回答します。' }, { t: 'ご確認・仮予約', d: '内容をご確認のうえ、お日にちを押さえます。' }, { t: 'ご宿泊者名簿のご提出', d: 'ご到着日の前までに、ご宿泊者名簿をお送りください。部屋割りを確定します。' }, { t: '当日・一括チェックイン', d: 'ご代表者様にまとめてお手続きいただき、お部屋の鍵をお渡しします。' }, ]; const Flow = () => (
    {FLOW_STEPS.map((s, i) => (
  1. {String(i + 1).padStart(2, '0')}

    {s.t}

    {s.d}

  2. ))}
); // === № 06 団体予約お問い合わせフォーム ===================================== // 検証はクライアントとサーバーの両方で行う。 // ここは入力体験のためのもので、セキュリティ上の担保は group-inquiry.php 側が持つ。 const PURPOSES = [ '法人研修・会議', '出張・ビジネス', 'スポーツ合宿・部活動遠征', '学校行事・ゼミ合宿', '冠婚葬祭・法要・同窓会', '工事・プロジェクトの長期滞在', 'その他', ]; const BREAKFAST_OPTIONS = ['必要', '不要', '未定']; const BANQUET_OPTIONS = ['希望する', '検討中', '不要']; const BUS_OPTIONS = ['あり', 'なし', '未定']; const PAYMENT_OPTIONS = ['当日一括精算', '請求書でのお支払い', '未定']; const CONTACT_PREF_OPTIONS = ['メール', '電話']; const MAX_GUESTS = 200; const MAX_NIGHTS = 60; const MESSAGE_MAX_LENGTH = 2000; // 室数の上限は実在庫に合わせる const ROOM_LIMITS = { roomsSingle: 48, roomsTwin: 8, roomsAnnex: 20 }; const INITIAL_FORM = { groupName: '', groupNameKana: '', contactName: '', contactNameKana: '', phone: '', email: '', purpose: '', checkinDate: '', nights: '1', guests: '', roomsSingle: '', roomsTwin: '', roomsAnnex: '', breakfast: '', hasAgreed: false, orgName: '', agencyName: '', banquet: '', bus: '', payment: '', contactPref: '', contactTime: '', message: '', companyUrl: '', }; // 必須項目の定義。ラベルはエラー表示にそのまま使う。 const REQUIRED_FIELDS = [ ['groupName', '団体・グループ名'], ['groupNameKana', '団体・グループ名(フリガナ)'], ['contactName', 'ご担当者名'], ['contactNameKana', 'ご担当者名(フリガナ)'], ['phone', '電話番号'], ['email', 'メールアドレス'], ['purpose', 'ご利用目的'], ['checkinDate', 'チェックイン日'], ['guests', 'ご宿泊人数'], ['breakfast', 'ご朝食'], ]; const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const validateForm = (form) => { const errors = {}; for (const [key, label] of REQUIRED_FIELDS) { if (String(form[key]).trim() === '') errors[key] = `${label}をご入力ください`; } if (form.email && !EMAIL_PATTERN.test(form.email)) { errors.email = 'メールアドレスの形式をご確認ください'; } const guests = Number(form.guests); if (form.guests !== '' && (!Number.isInteger(guests) || guests < MIN_GROUP_GUESTS || guests > MAX_GUESTS)) { errors.guests = `ご宿泊人数は${MIN_GROUP_GUESTS}〜${MAX_GUESTS}名でご入力ください`; } const nights = Number(form.nights); if (!Number.isInteger(nights) || nights < 1 || nights > MAX_NIGHTS) { errors.nights = `泊数は1〜${MAX_NIGHTS}泊でご入力ください`; } for (const [key, limit] of Object.entries(ROOM_LIMITS)) { if (form[key] === '') continue; const rooms = Number(form[key]); if (!Number.isInteger(rooms) || rooms < 0 || rooms > limit) { errors[key] = `0〜${limit}室でご入力ください`; } } if (form.message.length > MESSAGE_MAX_LENGTH) { errors.message = `${MESSAGE_MAX_LENGTH}文字以内でご入力ください`; } if (!form.hasAgreed) { errors.hasAgreed = '個人情報の取扱いにご同意ください'; } return errors; }; const submitInquiry = async (form) => { const response = await fetch(FORM_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...form, nights: Number(form.nights), guests: Number(form.guests), roomsSingle: Number(form.roomsSingle || 0), roomsTwin: Number(form.roomsTwin || 0), roomsAnnex: Number(form.roomsAnnex || 0), }), }); // サーバーが JSON を返さない場合(500 で HTML が返る等)もここで拾う const result = await response.json().catch(() => null); if (!response.ok || !result || result.status !== 'ok') { const reason = result && result.message ? result.message : `送信に失敗しました。お手数ですがお電話(${HOTEL.tel})でご連絡ください。`; throw new Error(reason); } return result; }; const Field = ({ label, isRequired, error, hint, children }) => (
{children} {hint && {hint}} {error && {error}}
); const InquiryForm = () => { const [form, setForm] = React.useState(INITIAL_FORM); const [errors, setErrors] = React.useState({}); const [isSending, setIsSending] = React.useState(false); const [isDone, setIsDone] = React.useState(false); const [sendError, setSendError] = React.useState(''); const update = (key) => (event) => { const value = event.target.type === 'checkbox' ? event.target.checked : event.target.value; setForm((prev) => ({ ...prev, [key]: value })); }; const handleSubmit = async (event) => { event.preventDefault(); setSendError(''); const foundErrors = validateForm(form); setErrors(foundErrors); if (Object.keys(foundErrors).length > 0) { // 最初のエラー項目までスクロールして気づけるようにする const firstKey = Object.keys(foundErrors)[0]; const element = document.querySelector(`[name="${firstKey}"]`); if (element) element.scrollIntoView({ behavior: 'smooth', block: 'center' }); return; } setIsSending(true); try { await submitInquiry(form); setIsDone(true); } catch (error) { // 利用者には一般的な文言を出し、原因は握りつぶさずコンソールに残す console.error('[group-inquiry] 送信失敗:', error); setSendError(error.message); } finally { setIsSending(false); } }; if (isDone) { return (

ご入力いただいたメールアドレス宛に、受付確認のメールをお送りしました。
空室状況を確認のうえ、1〜2営業日以内にご担当者様よりご連絡いたします。

確認メールが届かない場合は、迷惑メールフォルダをご確認のうえ、 お手数ですがお電話({HOTEL.tel}/{HOTEL.telHours})までご連絡ください。

); } return (
{/* ハニーポット。人間には見えない。ボットが埋めたら送信しない */}

ご連絡先

ご宿泊の内容

ご希望のお部屋タイプ・室数

お決まりの範囲でご記入ください。空欄でも構いません。部屋割りのご提案も承ります。

ご要望(任意)