GASを使ったシンプルな予約受付システム 

## はじめに

Google Apps Script (GAS) を使ってシンプルな予約受付システムを構築する方法をご紹介します。


## 概略

- user用のhtmlとadmin用のhtmlを作ります
- user.html画面
![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311614/75408a53-cf6f-5e48-bbfc-c06d5cd72506.png)
- admin.html画面
![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311614/e0a3e93b-4e03-2732-3bad-8198cc1e1c5c.png)


- user用のhtmlで、userが名前、テキストを記入し、`送信ボタン`を押すと、記入内容は作成日時をファイル名としたcsvとして所定のGoogleDriveホルダに送られます 

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311614/008107ff-96f9-92cb-c435-a106fec166dc.png)

- 所定のGoogleDriveホルダにcsvが集まります

![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311614/7cfe256b-9535-aa2c-c0bf-dac18fa8272e.png)

- admin用のhtmlで、`まとめボタン`を押すと集まったcsvをワークシートに一覧表としてまとめ、`ゲットボタン`を押すと一覧表を画面に表示します 

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311614/30d40879-a3d8-ecd1-7f11-2f6815e70b88.png)

- GoogleDriveホルダにadminというスプレッドシートがあり、ボタン（`送信ボタン`、`まとめボタン`、`ゲットボタン`）の挙動を定義するGASが搭載されています

- それぞれのGASはWebアプリ化することにより、html上のボタンからの起動を設定することができます

- user.htmlはWeb公開して複数のuserが使用し、admin.htmlは管理者がローカルで非公開にて使用することを想定しています

## ユーザー用HTMLインターフェース（user.html）
```
    <script>
        function submitForm() {
            const name = document.getElementById('name').value;
            const text = document.getElementById('text').value;
            const now = new Date();
            const formattedDate = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}_${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}${String(now.getSeconds()).padStart(2, '0')}`;

            // post.gsのWebアプリのURL
            const scriptUrl = 'https://script.google.com/macros/s/XXXXXX/exec';

            fetch(scriptUrl, {
                method: 'POST',
                mode: 'no-cors',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    name: name,
                    text: text,
                    fileName: `${formattedDate}.csv`
                })
            }).then(() => {
                document.getElementById('output').innerText = `名前: ${name}\nテキスト: ${text}\nファイル名: ${formattedDate}.csv\n送信完了`;
            }).catch(error => {
                console.error('Error:', error);
                document.getElementById('output').innerText = 'エラーが発生しました。';
            });
        }
    </script>
```
## 管理者用HTMLインターフェース（admin.html）
```
    <script>
        // sum.gsのWebアプリのURL
        const webAppUrl1 = 'https://script.google.com/macros/s/XXXXXX/exec'; 
        
        // doGet.gsのWebアプリのURL
        const webAppUrl2 = 'https://script.google.com/macros/s/XXXXXX/exec'; 

        function summarizeData() {
            fetch(`${webAppUrl1}?action=summarize`)
                .then(response => response.text())
                .then(message => alert(message))
                .catch(error => console.error('Error:', error));
        }

        function getSummaryData() {
            fetch(`${webAppUrl2}?action=getData`)
                .then(response => response.json())
                .then(data => {
                    const headerRow = document.getElementById('header-row');
                    const dataBody = document.getElementById('data-body');
                    headerRow.innerHTML = ''; // Clear existing headers
                    dataBody.innerHTML = '';  // Clear existing data

                    if (data && data.length > 0) {
                        // Add headers
                        data[0].forEach(header => {
                            const th = document.createElement('th');
                            th.textContent = header;
                            headerRow.appendChild(th);
                        });

                        // Add data rows
                        for (let i = 1; i < data.length; i++) {
                            const tr = document.createElement('tr');
                            data[i].forEach(cell => {
                                const td = document.createElement('td');
                                td.textContent = cell;
                                tr.appendChild(td);
                            });
                            dataBody.appendChild(tr);
                        }
                    }
                })
                .catch(error => console.error('Error:', error));
        }
    </script>
```
## Google Apps Scriptバックエンド
### post.gs: 記入内容をcsvとしてGoogleDriveホルダに送信する
```
function summarizeCsvDataToSheet() {
  const folderId = 'XXXXXX';
  const folder = DriveApp.getFolderById(folderId);
  const files = folder.getFiles();
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const sheetName = 'summary'; 
  let sheet = spreadsheet.getSheetByName(sheetName);

  if (!sheet) {
    sheet = spreadsheet.insertSheet(sheetName);
  } else {
    sheet.clear();
  }

  sheet.appendRow(['Date', 'Name', 'Text']); 

  while (files.hasNext()) {
    const file = files.next();
    const content = file.getBlob().getDataAsString().trim();
    const rows = content.split('\n').map(row => row.split(',').map(cell => cell.trim()));

    if (rows.length > 1 && rows[1].length >= 2) {
      const fileName = file.getName().replace('.csv', '');
      const name = rows[1][0];
      const text = rows[1][1];

      // データを明示的にStringオブジェクトに変換
      const dataToAppend = [String(fileName), String(name), String(text)];

      // シートへの書き込み
      sheet.appendRow(dataToAppend);
    }
  }
}
```
### sum.gs: csvをワークシート上に一覧表としてまとめる
```
function summarizeCsvDataToSheet() {
  const folderId = 'XXXXXX';
  const folder = DriveApp.getFolderById(folderId);
  const files = folder.getFiles();
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const sheetName = 'summary'; 
  let sheet = spreadsheet.getSheetByName(sheetName);

  if (!sheet) {
    sheet = spreadsheet.insertSheet(sheetName);
  } else {
    sheet.clear();
  }

  sheet.appendRow(['Date', 'Name', 'Text']); 

  while (files.hasNext()) {
    const file = files.next();
    const content = file.getBlob().getDataAsString().trim();
    const rows = content.split('\n').map(row => row.split(',').map(cell => cell.trim()));

    if (rows.length > 1 && rows[1].length >= 2) {
      const fileName = file.getName().replace('.csv', '');
      const name = rows[1][0];
      const text = rows[1][1];

      // データを明示的にStringオブジェクトに変換
      const dataToAppend = [String(fileName), String(name), String(text)];

      // シートへの書き込み
      sheet.appendRow(dataToAppend);
    }
  }
}
```
### doGet.gs: 一覧表の情報を入手する 
```
function doGet(e) {
  const action = e.parameter.action;
  const data = getSummaryData();
  return ContentService.createTextOutput(JSON.stringify(data)).setMimeType(ContentService.MimeType.JSON);
}

function getSummaryData() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  const data = sheet.getDataRange().getValues();
  return data;
}

```
## システム概略図
システム全体の概略図を示します。
![fig.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311614/19ec44c0-05e2-6eb8-9fa9-fe79bfabf016.png)


## 終わりに

今回、シンプルな予約受付システムとし紹介しましたが、さらに機能を発展させたり、他の用途（アンケート、注文受付、など）にも応用できると思います。ぜひお試しください。