PageSourceSearch

https://mens-eminal.jp/reserve/assets/js/calendar_view.js?v=...

js mens-eminal.jp collected 2026-09-30 22:32:36 UTC 7,374 bytes, 182 lines download raw bytes

1// =========================================================
2// 🎨 カレンダーUI 共通描画モジュール (calendar_view.js)
3// =========================================================
4
5const CalendarView = {
6  /**
7   * カレンダーのHTML文字列を構築して返す
8   * @param {string} shopName - 店舗名
9   * @param {string} startDateStr - カレンダーの開始日 (YYYY-MM-DD)
10   * @param {string} shopTel - カレンダーの開始日 (YYYY-MM-DD)
11   * @param {Object} calendarData - APIから取得した dates, times, grid を含むオブジェクト
12   * @param {boolean} isMember - 会員モードかどうかのフラグ(警告文などの出し分け用)
13   * @returns {string} - 生成されたHTML文字列
14   */
15  buildHTML: function (shopName, startDateStr, calendarData, isMember = false, shopTel = '') {
16    // 🚀 スケジュールが存在しない時(60日以上先のデータなど)
17    if (!calendarData || !calendarData.dates || calendarData.dates.length === 0) {
18      const startDate = new Date(startDateStr + 'T00:00:00');
19      const prevDate = new Date(startDate);
20      prevDate.setDate(prevDate.getDate() - 7);
21
22      const formatDate = (d) => {
23        const y = d.getFullYear();
24        const m = String(d.getMonth() + 1).padStart(2, '0');
25        const day = String(d.getDate()).padStart(2, '0');
26        return `${y}-${m}-${day}`;
27      };
28
29      const navHtml = `
30        <div class="calendar-nav-wrap" style="display: flex; justify-content: space-between; margin: 15px 0;">
31          <button type="button" class="btn-nav prev" data-date="${formatDate(prevDate)}">◁ 前の週</button>
32          <div></div>
33        </div>
34      `;
35
36      // 👑 メッセージの基本形
37      let emptyMsgHtml = `
38          <p style="font-weight: bold; color: #e43a6e; margin-bottom: 10px; font-size: 1.1rem;">■指定日以降のスケジュールは確定していません。</p>
39          <p style="font-size: 0.95rem; color: #555;">日付を変更してカレンダーを更新するか、もうしばらくお待ちください。</p>
40      `;
41
42      // 👑 メンバー(isMember)なら、電話番号入りの追加メッセージをくっつける!
43      if (isMember) {
44        emptyMsgHtml += `
45          <p style="font-size: 0.95rem; color: #555; margin-top: 15px; font-weight: bold;">
46            ■現在より61日以上先の日付のご予約はお電話、エミナルクリニックお問い合わせ、LINEでご相談ください。<br>
47            ${shopName} <a href="tel:${shopTel}" style="text-decoration:underline; color:#D21577;">${shopTel}</a><br>
48            <a href="https://eminal-clinic.jp/contact/" target="_blank" style="text-decoration:underline; color:#D21577;">エミナルクリニックお問い合わせ</a>
49          </p>
50        `;
51      }
52
53      return `
54        <div id="calendar-top-anchor" class="calendar-shop-header">
55          <div class="shop-name">${shopName}</div>
56        </div>
57        ${navHtml}
58        <div style="text-align: center; padding: 3rem 1rem; background: #fffafb; border: 1px solid #e43a6e; border-radius: 8px; margin: 2rem 0; line-height: 1.6;">
59          ${emptyMsgHtml}
60        </div>
61        ${navHtml}
62      `;
63    }
64
65    const { dates, times, grid } = calendarData;
66    const startDate = new Date(startDateStr + 'T00:00:00');
67    const today = new Date();
68    today.setHours(0, 0, 0, 0);
69
70    const formatDate = (d) => {
71      const y = d.getFullYear();
72      const m = String(d.getMonth() + 1).padStart(2, '0');
73      const day = String(d.getDate()).padStart(2, '0');
74      return `${y}-${m}-${day}`;
75    };
76
77    const prevDate = new Date(startDate);
78    prevDate.setDate(prevDate.getDate() - 7);
79    const nextDate = new Date(startDate);
80    nextDate.setDate(nextDate.getDate() + 7);
81
82    const isPrevDisabled = startDate <= today;
83
84    const dispYear = startDate.getFullYear();
85    const dispMonth = String(startDate.getMonth() + 1).padStart(2, '0');
86    const dispDay = String(startDate.getDate()).padStart(2, '0');
87    const rangeText = `${dispYear}年${dispMonth}月${dispDay}日から7日間`;
88
89    const navHtml = `
90      <div class="calendar-nav-wrap" style="display: flex; justify-content: space-between; margin: 15px 0;">
91          ${!isPrevDisabled ? `<button type="button" class="btn-nav prev" data-date="${formatDate(prevDate)}">◁ 前の週</button>` : '<div></div>'}
92          <button type="button" class="btn-nav next" data-date="${formatDate(nextDate)}">次の週 ▷</button>
93      </div>
94    `;
95
96    let html = `
97      <div id="calendar-top-anchor" class="calendar-shop-header">
98          <div class="shop-name">${shopName}</div>
99          <div class="date-range">${rangeText}</div>
100      </div>
101      ${navHtml}
102      <div class="table-responsive">
103          <table class="timetable">
104              <thead>
105    `;
106
107    // 月のヘッダー生成
108    let monthCounts = [];
109    let curMonth = '';
110    let curCount = 0;
111    dates.forEach(dStr => {
112      const m = parseInt(dStr.substring(4, 6), 10) + "月";
113      if (m !== curMonth) {
114        if (curMonth !== '') monthCounts.push({ month: curMonth, count: curCount });
115        curMonth = m;
116        curCount = 1;
117      } else {
118        curCount++;
119      }
120    });
121    monthCounts.push({ month: curMonth, count: curCount });
122
123    html += `<tr>`;
124    monthCounts.forEach(mc => {
125      html += `<th colspan="${mc.count}" class="month-row">${mc.month}</th>`;
126    });
127    html += `</tr><tr>`;
128
129    // 日付と曜日のヘッダー生成
130    const weekNames = ['日', '月', '火', '水', '木', '金', '土'];
131    const formatDates = [];
132    dates.forEach((dStr) => {
133      const y = dStr.substring(0, 4);
134      const m = dStr.substring(4, 6);
135      const d = dStr.substring(6, 8);
136      const fDate = `${y}-${m}-${d}`;
137      formatDates.push(fDate);
138
139      const dateObj = new Date(`${fDate}T00:00:00`);
140      const wNum = dateObj.getDay();
141      let thClass = wNum === 0 ? 'sun' : (wNum === 6 ? 'sat' : '');
142      html += `<th class="day-row ${thClass}">${parseInt(d, 10)}<br><span class="week">(${weekNames[wNum]})</span></th>`;
143    });
144    html += `</tr></thead><tbody>`;
145
146    // タイムテーブル(行)の生成
147    times.forEach(t => {
148      const timeLabel = t.substring(0, 2) + ':' + t.substring(2, 4);
149      html += `<tr>`;
150      dates.forEach((dStr, index) => {
151        const cell = grid[t][dStr];
152        const fDate = formatDates[index];
153        if (!cell || cell.type === 'span') return;
154
155        if (cell.type === 'ok') {
156          html += `<td rowspan="${cell.rowspan}" class="cell-ok">
157                      <button type="button" class="btn-slot ok" data-date="${fDate}" data-time="${timeLabel}">
158                          <span class="time-text">${timeLabel}</span><br><span class="mark">◎</span>
159                      </button>
160                  </td>`;
161        } else {
162          html += `<td class="cell-ng"></td>`;
163        }
164      });
165      html += `</tr>`;
166    });
167
168    html += `</tbody></table></div>`;
169    html += navHtml;
170
171    // 💡 会員向けの特記事項を追加可能
172    if (isMember) {
173      html += `
174        <div style="margin-top: 2rem; font-size: 0.85rem; color: #666; text-align: center;">
175          <p>※ご希望の日時(◎)をタップしてください。</p>
176        </div>
177      `;
178    }
179
180    return html;
181  }
182};

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.