LoginSignup
0
0

【備忘録】休業日と営業日の表示を非同期通信で切り替えるRailsアプリの実装

Posted at

内容

休業日と営業日を切り替えて表示するために、以下のコードを実装しました。
備忘録なので説明は省いています(面倒くさいので・・・)

app/controllers/reservations_controller.rb

def update_slots
  date = params[:date]
  @date = Date.parse(date)
  @company = current_company
  @time_slots = generate_time_slots(@company)
  @non_business_day = ScheduleService.is_non_business_day?(@company, @date)
  @services = @company.services
  @service_availability = ScheduleService.calculate_availability(@company, @date, @services, @time_slots)
  response_data = {
    services: @services.map { |service|
      {
        name: service.name,
        duration: format_duration(service.duration),
        time_slots: @time_slots.map { |slot| { availability: @service_availability.dig(service.id, slot) || '-' } },
      }
    },
    time_slots: @time_slots,
    non_business_day: @non_business_day
  }
  render json: response_data
end

ビュー (app/views/schedules/show.html.erb)

<div class="schedule-container">
  <% if @non_business_day %>
    <h2><%= "#{@date.strftime("%Y年%m月%d日")}は休業日です" %></h2>
  <% else %>
    <div>
      ○: 空き枠あり、△: 空き枠わずか、- : 要確認、×: 受付不可
    </div>

    <div class="table-responsive">
      <table class="table table-bordered text-center">
        <thead class="thead-light">
          <tr>
            <th class="sticky-left">サービス</th>
            <th class="sticky-duration">所要時間</th>
            <% @time_slots.each do |slot| %>
              <th><%= slot %></th>
            <% end %>
          </tr>
        </thead>
        <tbody>
          <% @services.each do |service| %>
            <tr>
              <th class="service-name sticky-left"><%= service.name %></th>
              <th class="service-duration sticky-duration"><%= format_duration(service.duration) %></th>
              <% @time_slots.each do |slot| %>
                <td>
                  <%= @service_availability[service.id][slot] %>
                </td>
              <% end %>
            </tr>
          <% end %>
        </tbody>
      </table>
    </div>
    <%= paginate @services %>
  <% end %>
</div>

JavaScript (app/javascript/packs/application.js)

// ある日のスケジュールとかを取得するときの非同期通信用
document.addEventListener('turbo:load', function() {
  const dateField = document.querySelector('#for_customer_index_date_field');
  if (dateField) {
    const companyId = dateField.dataset.companyId;
    const scheduleTitle = document.querySelector('.page-index');
    let companyName = '';

    if (scheduleTitle && scheduleTitle.textContent) {
      const match = scheduleTitle.textContent.match(/^(.*?)の/);
      if (match) {
        companyName = match[1];
      }
    }

    const debouncedFetch = debounce(function(date) {
      fetch(`/reservations/update_slots?date=${date}&company_id=${companyId}`, {
        headers: {
          'Accept': 'application/json',
          'X-Requested-With': 'XMLHttpRequest',
          'X-CSRF-Token': document.querySelector("[name='csrf-token']").content
        },
        credentials: 'include'
      })
      .then(response => response.json())
      .then(data => {
        if (data && data.services && data.time_slots !== undefined) {
          updateTable({ ...data, date });
          if (companyName) {
            scheduleTitle.textContent = `${formatDate(date)}のスケジュール`; // 安全に更新
          }
        } else {
          console.error("Invalid data format received:", data);
        }
      })
      .catch(error => console.error('Error fetching data:', error));
    }, 500);

    dateField.addEventListener('input', function() {
      debouncedFetch(this.value);
    });
  }
});

function updateTable(data) {
  const scheduleContainer = document.querySelector('.schedule-container');

  if (data.non_business_day) {
    // 休業日の場合、全ての内容を削除して休業日メッセージを表示
    scheduleContainer.innerHTML = `<h2>${formatDate(data.date)}は休業日です</h2>`;
  } else {
    // 営業日の場合の表示
    scheduleContainer.innerHTML = `
      <div>
        ○: 空き枠あり、△: 空き枠わずか、- : 要確認、×: 受付不可
      </div>
      <div class="table-responsive">
        <table class="table table-bordered text-center">
          <thead class="thead-light">
            <tr>
              <th class="sticky-left">サービス</th>
              <th class="sticky-duration">所要時間</th>
              ${data.time_slots.map(slot => `<th>${slot}</th>`).join('')}
            </tr>
          </thead>
          <tbody>
            ${data.services.map(service => `
              <tr>
                <th class="service-name sticky-left">${service.name}</th>
                <th class="service-duration sticky-duration">${service.duration}</th>
                ${service.time_slots.map(slot => `<td>${slot.availability}</td>`).join('')}
              </tr>
            `).join('')}
          </tbody>
        </table>
      </div>
    `;
  }
}

// 年月日のフォーマット関数
function formatDate(date) {
  const d = new Date(date);
  const year = d.getFullYear();
  const month = ('0' + (d.getMonth() + 1)).slice(-2); // 月を2桁にフォーマット
  const day = ('0' + d.getDate()).slice(-2); // 日を2桁にフォーマット
  return `${year}${month}${day}日`;
}

// デバウンス関数
function debounce(func, wait) {
  let timeout;
  return function() {
    const context = this, args = arguments;
    const later = function() {
      timeout = null;
      func.apply(context, args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0