2025年5月3日土曜日

【無料で簡単】予約管理Webツールの作り方

【無料で簡単】Excel不要!あらゆる業種に使える「予約管理Webツール」の作り方

💭こんな悩み、ありませんか?

  • Excelでの予約管理が煩雑で、入力ミスや更新漏れが起きる
  • Googleフォームや有料予約システムは導入が難しい、または高い
  • 電話や紙での予約管理が非効率で、スタッフ間で情報が共有できない

👨‍⚕️実際の現場からー

クリニック勤務の裏方の友人は、こんな悩みを抱えていました:

  • 「先生がExcelで予約表を作っているけど、セルを探して名前を入力するのが面倒で…」
  • 「電話が鳴るたびにパソコンを開いて、間違えずに書き込むのが大変です」
  • 「Web上で簡単に予約できて、そのままExcelみたいに使えるといいんだけどなぁ…」
  • 「そもそもExcelって入れるのに1万円ぐらいかかっちゃうんだよね…」

「Excelだと不便。そもそも入れると高い。でも高機能なツールは不要」という中小規模の現場には、シンプルで無料な予約管理ツールが求められています。

✅この記事を読むとできること

  • Excel不要!Webブラウザで予約管理ができるシンプルなシステムを自作できます
  • PowerShellとHTMLだけで完結。インターネット接続も不要
  • 整骨院、美容室、学習塾、カフェ、ジムなど、業種問わず活用可能

こんな感じの予約表ができます!

🧑‍🔧ステップ1: PowerShellで予約用CSVを自動生成

以下のPowerShellスクリプトを使って、日付・時間スロット入りのCSVを作成します。


Add-Type -AssemblyName System.Windows.Forms

# フォーム作成
$form = New-Object System.Windows.Forms.Form
$form.Text = "日付選択フォーム"
$form.Width = 300
$form.Height = 220
$form.StartPosition = "CenterScreen"

# 開始日ラベル
$labelStart = New-Object System.Windows.Forms.Label
$labelStart.Text = "開始日:"
$labelStart.Top = 20
$labelStart.Left = 10
$form.Controls.Add($labelStart)

# 開始日ピッカー
$startPicker = New-Object System.Windows.Forms.DateTimePicker
$startPicker.Format = 'Short'
$startPicker.Top = 40
$startPicker.Left = 10
$form.Controls.Add($startPicker)

# 終了日ラベル
$labelEnd = New-Object System.Windows.Forms.Label
$labelEnd.Text = "終了日:"
$labelEnd.Top = 80
$labelEnd.Left = 10
$form.Controls.Add($labelEnd)

# 終了日ピッカー
$endPicker = New-Object System.Windows.Forms.DateTimePicker
$endPicker.Format = 'Short'
$endPicker.Top = 100
$endPicker.Left = 10
$form.Controls.Add($endPicker)

# OKボタン
$okButton = New-Object System.Windows.Forms.Button
$okButton.Text = "OK"
$okButton.Top = 140
$okButton.Left = 100
$okButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.AcceptButton = $okButton
$form.Controls.Add($okButton)

# フォーム表示と戻り値確認
if ($form.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
    # 値の取得
    $start = $startPicker.Value.Date
    $end = $endPicker.Value.Date

    # 出力ファイルパス
    $outputPath = "C:\TEST\data.csv"

    # CSVヘッダー
    "予約日,予約時間,予約者1,予約者2,予約者3,予約者4,予約者5" | Out-File -FilePath $outputPath -Encoding UTF8

    # 日付ループ
    while ($start -le $end) {
        for ($hour = 9; $hour -le 21; $hour++) {
            foreach ($min in @(0, 30)) {
                $time = "{0}:{1:00}" -f $hour, $min
                "$($start.ToString('yyyy/M/d')),$time,,,,," | Out-File -Append -FilePath $outputPath -Encoding UTF8
            }
        }
        $start = $start.AddDays(1)
    }

    Write-Host "CSVファイルを作成しました: $outputPath"
} else {
    Write-Host "キャンセルされました。"
}

}

このPowerShellスクリプトを実行してみると、いつからいつまでの予約表を作るか聞かれます!

今回は開始日を2025/4/1、終了日を2025/4/30とします。

なお日付右側のボタンでカレンダーから日付を選ぶことができます。

プログラムを実行すると以下のように空のCSVファイルができます。

💻ステップ2:予約をブラウザ上で管理できるHTMLを作成

先ほどのCSVを読み込み、Web上で予約を閲覧・入力・削除できるページをPowerShellで自動生成します。
下記のコードを実行してください。

$folderPath = "C:\TEST" は便宜上の設定です。Webページで更新した後のファイルは download フォルダに保存されるため、そちらを指定しても構いません。

createhtml.ps1


# PowerShellスクリプト: reserve*.csvの最新ファイルからHTMLを生成
# ファイルパス
$folderPath = "C:\TEST"
$htmlPath = "C:\TEST\reservation.html"

# デバッグ: スクリプト開始
Write-Host "スクリプトを開始します..."

# reserve*.csvファイルの検索(最新ファイルを優先)
try {
    $csvFile = Get-ChildItem -Path "$folderPath\reserve*.csv" -File |
               Sort-Object -Property LastWriteTime -Descending |
               Select-Object -First 1
    if (-not $csvFile) {
        Write-Host "エラー: $folderPath に reserve*.csv が見つかりません。CSVファイルを準備してください。"
        Write-Host "サンプルCSVフォーマット:"
        Write-Host "予約日,予約時間,予約者1,予約者2,予約者3,予約者4,予約者5"
        Write-Host "4/1,9:00,山田,田中,,鈴木,,"
        exit
    }
    $csvPath = $csvFile.FullName
    Write-Host "最新CSVファイルを選択しました: $csvPath (更新日時: $($csvFile.LastWriteTime))"
} catch {
    Write-Host "エラー: reserve*.csv の検索に失敗しました。詳細: $_"
    exit
}

# CSV読み込み(UTF-8対応)
try {
    $csvContent = Import-Csv -Path $csvPath -Encoding UTF8
    Write-Host "CSVファイルを正常に読み込みました。レコード数: $($csvContent.Count)"
} catch {
    Write-Host "エラー: CSV読み込みに失敗しました。詳細: $_"
    Write-Host "確認事項:"
    Write-Host "- CSVファイルがUTF-8で保存されているか"
    Write-Host "- ヘッダーが正しいか(予約日,予約時間,予約者1,予約者2,予約者3,予約者4,予約者5)"
    exit
}

# CSVデータをJSONに変換
try {
    $jsonData = $csvContent | ConvertTo-Json -Compress
    Write-Host "CSVデータをJSONに変換しました。"
} catch {
    Write-Host "エラー: JSON変換に失敗しました。詳細: $_"
    exit
}

# HTMLテンプレート(変更時にreserve.csvをダウンロード)
$htmlTemplate = @"
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>オフライン予約表(CSV管理)</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        table { border-collapse: collapse; width: 100%; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        th { background-color: #f2f2f2; }
        button { margin: 5px; }
        .input-group { margin-bottom: 10px; }
        label { margin-right: 10px; }
        .delete-btn { font-size: 12px; padding: 2px 5px; }
        .editable { cursor: pointer; background-color: #f9f9f9; }
        .editable:hover { background-color: #e0e0e0; }
        .alert { color: #d32f2f; font-weight: bold; margin-bottom: 10px; }
    </style>
</head>
<body>
    <h1>予約表(CSV管理)</h1>
    <div class="alert">
        予約の追加、削除、入力時にreserve.csvが自動ダウンロードされます。<br>
        ダウンロード後、C:\TEST\reserve.csvに上書き保存してください。<br>
        最新データを反映するには、PowerShellで .\generate_reservation.ps1 を再実行してください。
    </div>

    <table id="reservationTable">
        <thead>
            <tr>
                <th>予約日</th>
                <th>予約時間</th>
                <th>予約者1</th>
                <th>予約者2</th>
                <th>予約者3</th>
                <th>予約者4</th>
                <th>予約者5</th>
            </tr>
        </thead>
        <tbody id="reservationBody"></tbody>
    </table>

    <script>
        // CSVデータ(PowerShellから埋め込み)
        let reservations = $jsonData;
        console.log("初期データ:", reservations);

        // ページロード時に表を描画
        document.addEventListener("DOMContentLoaded", renderTable);

        function renderTable() {
            console.log("renderTable: 表を描画します");
            const tableBody = document.getElementById("reservationBody");
            tableBody.innerHTML = "";
            reservations.forEach((res, index) => {
                const row = document.createElement("tr");
                const formattedDate = res.予約日.includes("-")
                    ? new Date(res.予約日).toLocaleDateString("ja-JP", { month: "numeric", day: "numeric" })
                    : res.予約日;
                row.innerHTML =
                    "<td>" + formattedDate + "</td>" +
                    "<td>" + res.予約時間 + "</td>" +
                    "<td>" + (res.予約者1 && res.予約者1.trim() ? res.予約者1 + ' <button class="delete-btn" onclick="deletePerson(' + index + ', \'予約者1\')">削除</button>' : '<span class="editable" onclick="editPerson(' + index + ', \'予約者1\')">[入力]</span>') + "</td>" +
                    "<td>" + (res.予約者2 && res.予約者2.trim() ? res.予約者2 + ' <button class="delete-btn" onclick="deletePerson(' + index + ', \'予約者2\')">削除</button>' : '<span class="editable" onclick="editPerson(' + index + ', \'予約者2\')">[入力]</span>') + "</td>" +
                    "<td>" + (res.予約者3 && res.予約者3.trim() ? res.予約者3 + ' <button class="delete-btn" onclick="deletePerson(' + index + ', \'予約者3\')">削除</button>' : '<span class="editable" onclick="editPerson(' + index + ', \'予約者3\')">[入力]</span>') + "</td>" +
                    "<td>" + (res.予約者4 && res.予約者4.trim() ? res.予約者4 + ' <button class="delete-btn" onclick="deletePerson(' + index + ', \'予約者4\')">削除</button>' : '<span class="editable" onclick="editPerson(' + index + ', \'予約者4\')">[入力]</span>') + "</td>" +
                    "<td>" + (res.予約者5 && res.予約者5.trim() ? res.予約者5 + ' <button class="delete-btn" onclick="deletePerson(' + index + ', \'予約者5\')">削除</button>' : '<span class="editable" onclick="editPerson(' + index + ', \'予約者5\')">[入力]</span>') + "</td>";
                tableBody.appendChild(row);
            });
        }

        function addReservation() {
            console.log("addReservation: 予約追加を開始");
            const date = document.getElementById("date").value;
            const time = document.getElementById("time").value;
            const person1 = document.getElementById("person1").value;
            const person2 = document.getElementById("person2").value;
            const person3 = document.getElementById("person3").value;
            const person4 = document.getElementById("person4").value;
            const person5 = document.getElementById("person5").value;

            if (date && time) {
                const formattedDate = new Date(date).toLocaleDateString("ja-JP", { month: "numeric", day: "numeric" });
                reservations.push({
                    予約日: formattedDate,
                    予約時間: time,
                    予約者1: person1,
                    予約者2: person2,
                    予約者3: person3,
                    予約者4: person4,
                    予約者5: person5
                });
                console.log("addReservation: 新しい予約を追加", reservations);
                renderTable();
                saveCSV();
                document.getElementById("date").value = "";
                document.getElementById("time").value = "";
                document.getElementById("person1").value = "";
                document.getElementById("person2").value = "";
                document.getElementById("person3").value = "";
                document.getElementById("person4").value = "";
                document.getElementById("person5").value = "";
            } else {
                alert("予約日と予約時間は必須です");
            }
        }

        function deletePerson(index, personKey) {
            console.log("deletePerson: 削除開始", { index, personKey });
            reservations[index][personKey] = "";
            renderTable();
            saveCSV();
        }

        function editPerson(index, personKey) {
            console.log("editPerson: 入力開始", { index, personKey });
            const name = prompt("予約者名を入力してください");
            if (name && name.trim()) {
                reservations[index][personKey] = name.trim();
                console.log("editPerson: 名前を更新", reservations);
                renderTable();
                saveCSV();
            }
        }

        function saveCSV() {
            console.log("saveCSV: CSVダウンロードを開始");
            try {
                const header = "予約日,予約時間,予約者1,予約者2,予約者3,予約者4,予約者5\n";
                const csvContent = header + reservations.map(res =>
                    res.予約日 + "," + res.予約時間 + "," +
                    (res.予約者1 || "") + "," + (res.予約者2 || "") + "," +
                    (res.予約者3 || "") + "," + (res.予約者4 || "") + "," +
                    (res.予約者5 || "")
                ).join("\n");
                const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
                const link = document.createElement("a");
                const url = URL.createObjectURL(blob);
                link.setAttribute("href", url);
                link.setAttribute("download", "reserve.csv");
                document.body.appendChild(link);
                link.click();
                document.body.removeChild(link);
                console.log("reserve.csvをダウンロードしました。C:\\TEST\\reserve.csvに上書き保存してください。");
                console.log("saveCSV: ダウンロード完了");
            } catch (error) {
                console.error("saveCSV: エラー発生", error);
                alert("CSVダウンロードに失敗しました。コンソールを確認してください。");
            }
        }
    </script>
</body>
</html>
"@

# HTMLファイルに保存(UTF-8 BOMなし)
try {
    $htmlTemplate | Out-File -FilePath $htmlPath -Encoding UTF8
    Write-Host "HTMLファイルが正常に生成されました: $htmlPath"
    Write-Host "Microsoft Edgeで $htmlPath を開いて予約表を操作してください。"
} catch {
    Write-Host "エラー: HTMLファイルの生成に失敗しました。詳細: $_"
    exit
}

上記のコードをPowerShellで実行すると、HTMLファイルが生成されます。
ブラウザで開くと以下のような予約表が表示されます(CSVの内容を表形式で表示)。

では、予約を入れてみましょう!

  • 例: 9:00 の予約者1 に 清盛さん を登録

入力をクリックすると予約者の名前を入力するポップが出ますので皐清盛と入力します。

入力が完了すると画面上とCSVに清盛の予約が反映されます。

名前の右にある削除ボタンを押すと予約が削除され画面とCSVに反映されます。

💾ステップ3:CSVを保存・再利用して更新

予約内容を入力・削除したら、ブラウザからCSV形式で保存される仕組みになっています。保存されたCSVをPowerShellスクリプトで読み込めば、常に最新の予約表が再生成されます。

✅まとめ:Excel不要!「自分の現場向け予約管理」を自作しよう

「難しいシステムは要らないけど、紙やExcelの予約管理は限界…」

そんなあなたにぴったりなのが、PowerShellとHTMLで作るシンプルな予約管理システムです。

📌 ここまでの流れをおさらい

  • ① PowerShellでCSV予約表を自動生成
    → カレンダーで期間を選ぶだけ。30分刻みの予約枠が入ったCSVを作成。
  • ② CSVをHTMLに変換し、ブラウザで予約操作が可能に
    → 名前の入力・削除ができ、CSVとして保存されます。
  • ③ 保存したCSVで最新の予約表を再表示
    → データの再利用・更新も簡単!

システム開発の知識がなくても、導入コストゼロ・ネット不要・業種不問で使えるこの仕組み。

小さな現場にこそ、「ちょうどいい」予約管理ツールを、自分の手で作ってみませんか?

0 件のコメント:

コメントを投稿