いまロード中
×

[コードあり]知識ほぼなし高校生が学校祭予約システムを作った話と感想

こんにちは。

あっという間にもう8月に入ってしまいましたね。

私が所属する高校では7月中旬に学生にとっての一大イベント、学校祭が開催されました。

今回はその学校祭で私がチャレンジした「予約管理システム開発」についてお話したいと思います。

※タイトルに「コードあり」と書いて、あたかもコード配布している感じになってしまいましたが、この記事に載っているコードをコピペして真似したりするとこはお勧めしません。

予約システムを作ろうと思ったきっかけ

私の高校では生徒会主導で、各出し物(アトラクション)の待ち時間を減らす仕組みとして「予約カード」が配られていました。

お客さんがきたらその予約カードに受付番号、だいたい何分後に入れるか…など計算しカードに書いて、お客さんに渡し混雑を解消する、という使い方です。

その制度を悪く言うつもりはないですが(僕の友人が発案したものらしいですし)、なかなかそのような作業を受付係(だいたい2人)でやるのはかなり厳しく、効率が良くないと思い、自分のクラス独自の予約システムを作ることになったわけです。

完成品

完成品はこれから話すことにも関係するので先に載せておきます。

htmlコード

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    body {
      font-family: "Helvetica Neue", sans-serif;
      margin: 0;
      padding: 0;
      background-color: #f5f5f5;
      color: #333;
    }

    header {
      background-color: #2196F3;
      color: white;
      padding: 1em;
      text-align: center;
      font-size: 1.5em;
    }

    main {
      padding: 1em;
    }

    section {
      margin-bottom: 1.5em;
      background-color: white;
      border-radius: 10px;
      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
      padding: 1em;
    }

    footer {
      text-align: center;
      font-size: 0.9em;
      color: #777;
      padding: 1em 0;
    }
  </style>
</head>

<body>

  <header>
    予約確認サイトvar.2.0
  </header>

  <main>

    <section>
      <div class="container">
      <h1 class="text1">今待っている上5組&emsp;(全<span id="cus">...</span>組中)</h1>
      <p>※ご案内5つ前になりましたら教室前に必ずお越しください。</p>
        <div class="values1">
          <p id="1-value"></p>
          <p id="2-value"></p>
          <p id="3-value"></p>
          <p id="4-value"></p>
          <p id="5-value"></p>
        </div>
    </div>
    </section>

    <section>
      <h2>待ち時間を計算</h2>
      <p>あなたの受付番号を入力すると何人待ちかや推定時間を確認できます。</p>
      <input type="number" id="userNumber" placeholder="受付番号を入力してください" />
      <button onclick="calculateWait()">確認する</button>
      <p id="waitResult">...</p>
    </section>

  </main>

  <footer>
    &copy; 2025 hokke. All rights reserved.
  </footer>

  <script>
    function fetchAndCheckValue(){
      google.script.run.withSuccessHandler(function(value) {document.getElementById('1-value').innerText = value;}).get1();
      google.script.run.withSuccessHandler(function(value) {document.getElementById('2-value').innerText = value;}).get2();
      google.script.run.withSuccessHandler(function(value) {document.getElementById('3-value').innerText = value;}).get3();
      google.script.run.withSuccessHandler(function(value) {document.getElementById('4-value').innerText = value;}).get4();
      google.script.run.withSuccessHandler(function(value) {document.getElementById('5-value').innerText = value;}).get5();
      google.script.run.withSuccessHandler(function(value) {document.getElementById('time').innerText = value;}).CalWaiting();
      google.script.run.withSuccessHandler(function(value) {document.getElementById('cus').innerText = value;}).cus();
    }
    fetchAndCheckValue();
    setInterval(fetchAndCheckValue, 15000);

   function calculateWait() {
    const userNum = parseInt(document.getElementById('userNumber').value, 10);
    if (isNaN(userNum)) {
      document.getElementById('waitResult').innerText = "番号は半角で正しく入力してください。";
      return;
    }
    google.script.run.withSuccessHandler(function(currentNum) {
      currentNum = parseInt(currentNum, 10);
      const remaining = userNum - currentNum;

      if (remaining <= 0) {
        document.getElementById('waitResult').innerText = "ただいまご案内中、またはすでに呼ばれています。";
      }else {
        const estimatedTime = remaining * 5; // 1人5分で計算(必要に応じて変更)
        document.getElementById('waitResult').innerText =
          `あと ${remaining} 組待ち(約 ${estimatedTime} 分)です。`;
      }
    }).getCurrentNumber();
   }
    </script>

</body>

</html>

GoogleAppScript

function CreateRepnumber(e) {
  if (!e) {
    console.log('エディタから起動できません。フォームから回答してください。');
    return;
  }
  const range = e.range;
  const sheet = range.getSheet();
  const row = range.getRow();
  let numberReceipt = 998 + row;
  sheet.getRange(row, 1).setValue(numberReceipt);
  const nextNumber = numberReceipt + 1;
  const form = FormApp.openByUrl(sheet.getFormUrl());
  form.setConfirmationMessage(`受付番号は、${nextNumber} です。\\n番号を控えておき、入場の際はこの番号をお知らせください。`);
}
function CountReset(){
  const ss = SpreadsheetApp.openById('your id').getSheetByName('name');
  const sh = ss.getActiveSheet();
  const range = sh.getRange("B4");
  range.setValue("0");
}
function CountSubmit() {
  const sh = SpreadsheetApp.openById('your id').getSheetByName('name');
  const range = sh.getRange("B4");
  let number = range.getValue();
  number++;

  range.setValue(number);
}
function CalWaiting(){
  const sh = SpreadsheetApp.openById('your id').getSheetByName('name');
  const range = sh.getRange("F4");
  let number = range.getValue();
  let WatingTimenumber = number*5;
  return "約" + WatingTimenumber + "分";

}

function CopySheet() {
  const sourceSheetId = 'sheet1 id';
  const sourceSheetName = 'a';

  const targetSheetId = 'sheet2 id';
  const targetSheetName = 'b';

  const sourceSpreadsheet = SpreadsheetApp.openById(sourceSheetId);
  const sourceSheet = sourceSpreadsheet.getSheetByName(sourceSheetName);
  const value = sourceSheet.getRange('A1').getValue();

  const targetSpreadsheet = SpreadsheetApp.openById(targetSheetId);
  const targetSheet = targetSpreadsheet.getSheetByName(targetSheetName);
  targetSheet.getRange('D4').setValue(value);
}

function doGet() {
  const htmlOutput = HtmlService.createTemplateFromFile("index").evaluate();
  htmlOutput.setTitle('予約確認');
  return htmlOutput;
}
function get1() {
  const sheet = SpreadsheetApp.openById('your id').getSheetByName('name');
  const value = sheet.getRange("D4").getValue();
  const cus = sheet.getRange("F4").getValue();
  let number = value + 1000;
  let text = "今待ち組はありません";

  if(cus<=0){
    return text;
  }else{
    return number;
  }

}
function get2() {
  const sheet = SpreadsheetApp.openById('your id').getSheetByName('name');
  const value = sheet.getRange("D4").getValue();
  const cus = sheet.getRange("F4").getValue();
  let number = value + 1001;
  let text = "";

  if(cus<=1){
    return text;
  }else{
    return number;
  }

}
function get3() {
  const sheet = SpreadsheetApp.openById('your id').getSheetByName('name');
  const value = sheet.getRange("D4").getValue();
  const cus = sheet.getRange("F4").getValue();
  let number = value + 1002;
  let text = "";

  if(cus<=2){
    return text;
  }else{
    return number;
  }

}
function get4() {
  const sheet = SpreadsheetApp.openById('your id').getSheetByName('name');
  const value = sheet.getRange("D4").getValue();
  const cus = sheet.getRange("F4").getValue();
  let number = value + 1003;
  let text = "";

  if(cus<=3){
    return text;
  }else{
    return number;
  }

}
function get5() {
  const sheet = SpreadsheetApp.openById('your id').getSheetByName('name');
  const value = sheet.getRange("D4").getValue();
  const cus = sheet.getRange("F4").getValue();
  let number = value + 1004;
  let text = "";

  if(cus<=4){
    return text;
  }else{
    return number;
  }

}
function cus(){
  const sheet = SpreadsheetApp.openById('your id').getSheetByName('name');
  const cus = sheet.getRange("F4").getValue();
  return cus;
}

function getCurrentNumber() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("name");
  const number = sheet.getRange("D4").getValue();
  const currentNumber = number + 1000;
  return currentNumber;

簡単な解説

今回はシステム構築にGAS(Google App Script)を使用しています。これはGoogleアカウントを持っていれば容量の許す限り使用できる神ツールです。

何がすごいって、特に特別な開発環境(ライブラリ、とか)がいらず、Googleドライブにアクセスできる端末とインターネット環境があればだれでも開発できるということです。

学校斡旋の低スペックChromebookでここまで開発できてることが何よりの証明かと思います。

また今回の開発の相棒、協力者としてGoogle製の生成AI、「Gemini (旧Bard)」を採用いたしました。

GASも同じGoogleのシステムなので、あえて課金してあるChatGPTではなくGeminiを使用しました。GASについての知識はあまりなかったのでほぼ全部Geminiに聞きました。

こういうコードをひとりで書けるようになるまでが一人前なんだな~って思っていたりします。

予約システムのコードについては、「your id」とか「name」などで実際スプレッドシートのIDやシート名を隠してあります。

動きをざっくり言うと

受付番号と案内完了数から今の待ち組を計算し、サイトに反映する。

って感じです。

問題点がいろいろと

いまご覧になったのは最終的な完成品でしたが、これに行きつくまでいろいろな問題に直面しましたので、それについてお話します。

1つのスプレッドシートで2つのフォームの回答が管理できない

先述した通り、「予約フォーム」の入力数から作られる受付番号と「案内完了フォーム」の入力数の差分で待ち組数を計算しているわけですが、その計算を1つのスプレッドシートで処理することができませんでした。

これについては、自作関数の「copysheet」で解決でさせました。

関数の内容はシート1(予約フォームの入力数をカウントしている)に、シート2の内容(案内完了フォームの入力数をカウントする別シート)をコピペする、というものです。

簡単に言えば、予約、案内完了のカウントをそれぞれ別でカウントし、カウントの結果を1つのシートに特別な関数を使ってまとめた感じです。

これのデメリットは、同期に時間がかかるということです。

この関数が必要なければ、入力したあとすぐに値が反映されるのですが、この方法を使うと分単位でラグが出てしまいます。でも最大ラグが1分半とかだったのでこの方法をそのままとりました。

時間通り来ないお客さんがいる

あとの二つは技術的な問題ではありませんですが、これが一番深刻でした。

対策として一応もうすぐ入れる方の受付番号をWebサイトに掲載して、少なめに見積もった待ち時間を表示しておいたのですが、、、、

時間になっても予約してた人が来ない。

これは受付担当の人が悪いわけでもなく、もちろん私も悪くありません (よね?)。

やはりWebサイトに掲載するだけでなく、スマホに通知を送るくらいしないとなくならないのかもしれません。

だからと言ってWebサイトから通知を飛ばそうとしても許可しない設定だとか、「許可してください」のポップアップに無意識で許可しないを押してそうな方には効きません。

じゃあメールアドレスで送信すればいいじゃないか、とも思いましたが、学校祭の予約ごときでメールアドレス入力するってめんどくさくないか…?と思ったり。正解がわからないまま、解決せず終わってしまいました。

受付データベース、Webサイトが読み込み遅い

これは知識不足がわかりやすく出たところなのですが、

コードがぐちゃぐちゃで読みにくいということです。

htmlファイルの<script>タグ内とかがわかりやすいです。

google.script.run.withSuccessHandler(function(value) {document.getElementById('1-value').innerText = value;}).get1();
google.script.run.withSuccessHandler(function(value) {document.getElementById('2-value').innerText = value;}).get2();
google.script.run.withSuccessHandler(function(value) {document.getElementById('3-value').innerText = value;}).get3(); 
google.script.run.withSuccessHandler(function(value) {document.getElementById('4-value').innerText = value;}).get4();      google.script.run.withSuccessHandler(function(value) {document.getElementById('5-value').innerText = value;}).get5();      google.script.run.withSuccessHandler(function(value) {document.getElementById('time').innerText = value;}).CalWaiting();      google.script.run.withSuccessHandler(function(value) {document.getElementById('cus').innerText = value;}).cus();

なんとかならないんですかね、これ笑

たぶんこういうことが原因で読み込みが遅くなっているんだろうと思います。

もともとインターネット上、しかもGASでWebサイトを立ち上げているのでなおさらです。

感想・まとめ

最後までご覧いただきありがとうございました。

学校祭準備期間2-3週間の長い開発期間でしたが、なかなか開発すること、自分が開発したシステムが身近なところで使われること、とても新鮮で面白いな~と感じました。

これからもこんな開発していきたいと思っておりますので、新しい記事が投稿されましたらまたご覧いただけると嬉しいです!

Share this content:

コメントを送信