受付順番待ち登録は、クライアント側~管理者側(サーバー)間で、リアルタイムで情報を共有するシステムです。また、其々のコードは一般公開していますので、機能変更・追加・削除・修正・デザイン変更等のカスタマイズは自由です。また、コピー&ペーストも許可しますが、システム構築後の動作不具合などは自己責任でお願いします。

仕様の概略

1.クライアント側は、各項目(氏名、ID、内容、電話番号、メールアドレス等の各必須項目と、各種の入力条件、全ての入力完了で登録許可などです。

2.管理者フォーム側(サーバー)は、開始終了時間の設定、受付制限の設定、個別削除・全体削除、テーブルの検索機能、設定時間内の全削除不可等を設けています。

3.クライアント側とサーバー間のアクセスコードは最終行近くにコードを記述しています。

デザインや仕様など変更なく公開した場合は、どこか片隅に小さくMOMOPLANと書いて頂くと嬉しいです。

入力フォームの仕様
1.氏名16文字以内
2.ID 10桁制限
3.内容 4項目選択 その他入力
4.電話番号 桁数制限
5.メールアドレス ドメイン基本仕様制限
6.受付までの時間表示(カウントダウン・・受付まで何時何分何秒・・まもなく・・受付中・・受付終了)
7.受付順番のテーブル表示(電話番号・メールアドレスはプライバシーを配慮して非表示)
8.受付時間以外各項目入力不可

各コードのパスは使用環境に応じて変更してください。

長いページなので各コードにジャンプ
クライアント側) JS CSS
サーバー側(管理者) HTML JS delete_all_records.php delete_record.php fetch_admin_data.php save_admin_times.php search_record.php set_max_records.php submit_record.php CSS admin_data.json admin_times.json max_records.json フォルダ外のファイル
クライアント側~管理者サバー側のアクセスコード
クライアント側のコードの紹介
clientフォルダ内 index.html client.js selectmenu.css s-brick008.gif←これはデザインに応じて
HTML
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>クライアントデータ登録と表示</title>
    <link rel="stylesheet" href="selectmenu.css">
</head>
<body>
    <h1>【受付順番登録管理】</h1>
    <div id="statusDisplay">
        <font color="#5511c2"><h3 id="countdownTimer">状態を確認中...</h3></font>
        <p id="countdownTimer" style="font-size: 20px; color: gray;"></p>
    </div>
    <form id="registrationForm">
        <div class="form-row">
            <div class="form-label">
        <label for="name">名前 (16文字以内):</label>
        <span>必須</span>
    </div>
        <input type="text" id="name" maxlength="16" placeholder="例: 山田 太郎 (16文字以内)" required>
    </div>  
        <div class="form-row">
            <div class="form-label">
        <label for="id">ID (10桁):</label>
        <span>必須</span>
    </div>
        <input type="text" id="id" maxlength="10" pattern="\d{10}" title="10桁の数字を入力してください" required>
    </div>
        <div class="form-row">
            <div class="form-label">
        <label for="dropdown">内容を選択:</label>
        <span>必須</span>
    </div>
        <select id="dropdown" name="content" required>
            <option value="A" selected>A</option>
            <option value="B">B</option>
            <option value="C">C</option>
            <option value="その他">その他</option>
        </select>
        </div>
    <div id="otherContentContainer" style="display: none;">
        <div class="form-row">
            <div class="form-label">   
        <label for="otherContent">200文字以内:</label>
        <span>必須</span>
    </div>
    </div>
        <textarea id="otherContent" maxlength="200" cols="78" rows="2" placeholder="文字数は200文字以内です" ></textarea>
    </div>
        <div class="form-row">
            <div class="form-label">   
        <label for="phone">電話番号:</label>
        <span>必須</span>
    </div>
        <input type="tel" id="phone" pattern="^\d{10,15}$" title="電話番号は10~15桁の数字を入力してください" required>
    </div>
    <div class="form-row">
        <div class="form-label">   
        <label for="email">メールアドレス:</label>
        <span>必須</span>
    </div>
        <input type="email" id="email" title="正しいメールアドレスを入力してください" required>
    </div>
    <div class="form-row"></div>
        <button type="submit" id="submitButton" disabled>登録</button>
    </div>
    </form>
    <br>
    <table id="clientTable">
        <thead>
            <tr>
                <th>受付順</th>
                <th>名前</th>
                <th>ID</th>
                <th>内容</th>
                <th>時間</th>
            </tr>
        </thead>
        <tbody>
            <!-- データはJavaScriptで動的に生成 -->
        </tbody>
    </table>
    <script src="client.js"></script>
</body>
</html>
JS
document.addEventListener("DOMContentLoaded", () => {
    const form = document.getElementById("registrationForm");
    const inputs = form.querySelectorAll("input, textarea");
    const submitButton = document.getElementById("submitButton");
    const dropdown = document.getElementById("dropdown"); // プルダウンメニューを取得
    const otherContentContainer = document.getElementById("otherContentContainer");
    const otherContentInput = document.getElementById("otherContent"); // その他入力欄を取得
    const countdownElement = document.getElementById("countdownTimer");
    const formElements = document.querySelectorAll("#registrationForm input, #registrationForm textarea, #registrationForm button");
    console.log("DOMが完全にロードされました");
    // 必要なスクリプトをここに記述
// 要素が存在するかチェック
if (!countdownElement) {
    console.error("countdownTimer 要素が見つかりません。HTML構造を確認してください。");
    return;
}

//===========時間設定===========================================
    // 今日の日付に基づいた受付時間設定
    const today = new Date();
    // フォームの状態を保存する関数
    function saveFormState(isDisabled) {
        localStorage.setItem("formDisabled", isDisabled);
    }

    // ページ読み込み時にフォームの状態を復元する関数
    function restoreFormState() {
        const formDisabled = localStorage.getItem("formDisabled");
        if (formDisabled === "true") {
            formElements.forEach(element => (element.disabled = true));
        } else {
            formElements.forEach(element => (element.disabled = false));
        }
    }
    const startTime = new Date(); // 例: 現在時刻を開始時間として設定
    startTime.setHours(8, 15, 0); // AM 8:00

    const endTime = new Date();
    endTime.setHours(23, 30, 0); // PM 5:30


    function updateCountdown() {
        const now = new Date();
        const clearTime = new Date(startTime.getTime() - 5 * 60 * 1000); // 受付開始5分前
    
        if (now < clearTime) {
            // 通常のカウントダウン
            const timeLeft = startTime - now;
            const hours = Math.floor(timeLeft / (1000 * 60 * 60));
            const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60));
            const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000);
    
            countdownElement.textContent = `受付開始まで ${hours}時間 ${minutes}${seconds}秒`;
            formElements.forEach(element => (element.disabled = true)); // 無効化
            saveFormState(true); // フォーム状態保存
        } else if (now >= clearTime && now < startTime) {
            // 受付開始5分前
            const timeLeft = startTime - now;
            const minutes = Math.floor(timeLeft / (1000 * 60));
            const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000);
    
            countdownElement.textContent = `間もなく受付です ${minutes}${seconds}秒`;
            formElements.forEach(element => (element.disabled = true)); // 無効化
            saveFormState(true); // フォーム状態保存
        } else if (now >= startTime && now <= endTime) {
            // 受付中
            countdownElement.textContent = "受付中です!入力可能です";
            formElements.forEach(element => (element.disabled = false)); // 有効化
            saveFormState(false); // フォーム状態保存
        } else {
            // 受付終了
            countdownElement.textContent = "受付終了しました";
            formElements.forEach(element => (element.disabled = true)); // 無効化
            saveFormState(true); // フォーム状態保存
        }
    }


    // ページ読み込み時にフォーム状態を復元
    restoreFormState();

    // 1秒ごとにカウントダウンを更新
    setInterval(updateCountdown, 1000);

    // 初回呼び出し
    updateCountdown();

//==================================================================

    // メールアドレスの独自バリデーション処理
    const emailInput = document.getElementById("email");
    emailInput.addEventListener("input", () => {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        if (!emailRegex.test(emailInput.value)) {
            emailInput.setCustomValidity("正しいメールアドレス形式を入力してください");
        } else {
            emailInput.setCustomValidity("");
        }
        validateInputs();
    });

    // プルダウンの変更監視
    dropdown.addEventListener("change", () => {
        if (dropdown.value === "その他") {
            // その他選択時、内容欄を表示
            otherContentContainer.style.display = "block";
            otherContentInput.required = true;
        } else {
            // その他以外の場合、内容欄を非表示
            otherContentContainer.style.display = "none";
            otherContentInput.required = false;
            otherContentInput.value = ""; // 入力をクリア
        }
    });

    // フォーム送信時の処理
    document.getElementById("registrationForm").addEventListener("submit", (event) => {
        event.preventDefault();

        const selectedContent = dropdown.value;
        const finalContent = selectedContent === "その他" ? otherContentInput.value : selectedContent;

        console.log("選択された内容:", finalContent);
        alert(`選択された内容: ${finalContent}`);
    });

    // 入力バリデーションのチェック
    const validateInputs = () => {
        const allValid = [...inputs].every(input => input.checkValidity());
        submitButton.disabled = !allValid;
    };

    validateInputs(); // 初期状態のチェック

    // フォーム送信時の処理
    // フォーム送信時の処理
form.addEventListener("submit", async (event) => {
        event.preventDefault();
    
        const name = document.getElementById("name").value.trim();
        const id = document.getElementById("id").value.trim();
        const dropdown = document.getElementById("dropdown");
        const otherContent = document.getElementById("otherContent");
        const phone = document.getElementById("phone").value.trim();
        const email = document.getElementById("email").value.trim();
    
        const content = dropdown.value === "その他" ? otherContent.value.trim() : dropdown.value;
    
        try {
            // サーバーにデータ送信
            const response = await fetch("/jyunbanmati/save_data.php", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({ name, id, content, phone, email })
            });
    
            const text = await response.text();
            console.log("レスポンスの内容:", text);
    
            const result = JSON.parse(text); // JSONとして解析
            if (result.success) {
                alert("登録に成功しました!");
    
                // **即時反映部分:送信されたデータをテーブルに追加**
                const tableBody = document.querySelector("#clientTable tbody");
                const tr = document.createElement("tr");
    
                const orderTd = document.createElement("td");
                orderTd.textContent = tableBody.children.length + 1; // 新しい行番号
                tr.appendChild(orderTd);
    
                const nameTd = document.createElement("td");
                nameTd.textContent = name; // 入力された名前
                tr.appendChild(nameTd);
    
                const idTd = document.createElement("td");
                idTd.textContent = id; // 入力されたID
                tr.appendChild(idTd);
    
                const contentTd = document.createElement("td");
                contentTd.textContent = content; // 選択または入力された内容
                tr.appendChild(contentTd);
    
                const timeTd = document.createElement("td");
                timeTd.textContent = new Date().toLocaleString(); // 現在時刻
                tr.appendChild(timeTd);
    
                tableBody.appendChild(tr); // テーブルに新しい行を追加
    
                form.reset(); // フォームをリセット
                validateInputs(); // ボタン状態をリセット
            } else {
                alert("登録に失敗しました: " + result.message);
            }
        } catch (error) {
            console.error("通信エラー:", error);
            alert("通信エラーが発生しました。もう一度お試しください。");
        }
    });

    // テーブル初期ロード
    loadClientTable();
});

// クライアント側のテーブルをロード
async function loadClientTable() {
    try {
        const response = await fetch("/jyunbanmati/fetch_client_data.php");
        if (!response.ok) throw new Error("データ取得に失敗しました");

        const data = await response.json();
        console.log("テーブルデータ:", data);

        const tableBody = document.querySelector("#clientTable tbody");
        tableBody.innerHTML = "";

        data.forEach((row, index) => {
            const tr = document.createElement("tr");

            const orderTd = document.createElement("td");
            orderTd.textContent = index + 1;
            tr.appendChild(orderTd);

            const nameTd = document.createElement("td");
            nameTd.textContent = row.name;
            tr.appendChild(nameTd);

            const idTd = document.createElement("td");
            idTd.textContent = row.id;
            tr.appendChild(idTd);

            const contentTd = document.createElement("td");
            contentTd.textContent = row.content;
            tr.appendChild(contentTd);

            const timeTd = document.createElement("td");
            timeTd.textContent = row.time;
            tr.appendChild(timeTd);

            tableBody.appendChild(tr);
        });
    } catch (error) {
        console.error("クライアントテーブルエラー:", error);
    }
}
CSS
body {/*背景画像*/
  position: relative;
  width: 980px;
  margin: auto;
  text-align: center;
  padding: 0;
  background-image: url("s-brick008.gif");
}
/* レイアウト */
form {
  width: 70%;
  margin: 0 auto;
}
.form-row {
  display: flex;
  align-items: center;
  padding: 4px;
  border-bottom: 1px solid #f2f4f5;
}
.form-row:last-child {
  border-bottom: none;
}
.form-label {
  display: flex;
  align-items: center;
  width: 250px;
}
.form-label label {
  font-weight: bold;
}
.form-label span {
  margin-left: 10px;
  padding: 2px 4px;
  border-radius: 3px;
  font-size: 12px;
  font-weight: bold;
  color: #fff;
  background-color: #166ab5;
}
input, textarea {
  background-color: #f2f4f5;
  border: none;
  border-radius: 3px;
  padding: 12px 20px;
  font-size: 16px;
  color: #333;
  flex-grow: 1;
}
input::placeholder,
textarea::placeholder {
  color: #999;
  font-size: 14px;
}

button {
      display: flex;
      justify-content: center;
      align-items: center;
      width: 120px;
      margin: 0 auto;
      padding: .1em 2em;
      border: 2px solid #333333;
      background-color: #6c6ef3;
      border-radius: 24px;
      box-shadow: 0 7px 15px -5px rgb(0 0 0 / 50%);
      color: #ffffff;
      font-weight: 600;
      font-size: 16px;
    }    
button:hover {
        box-shadow: 0 12px 60px -5px rgba(224, 214, 248, 0.2);
    }

label {
    position: center;
    border: 0;
    line-height: 2.5;
    width : 200px;
    padding: 0 20px;
    font-size: 1rem;
    text-align: center;
    color: #fff;
    text-shadow: 1px 1px 1px #000;
    border-radius: 10px;
    z-index: 2;
    background-color: rgb(85, 17, 194);
    background-image: linear-gradient(
     to top left,
     rgba(0, 0, 0, 0.2),
     rgba(0, 0, 0, 0.2) 30%,
     rgba(0, 0, 0, 0)  
   );
  }
label:hover {/*ボタンのホバー*/
    background-color: rgb(150, 184, 247);
    }  
label:active {/*ボタンのアクティブ*/
    box-shadow:
      inset -2px -2px 3px rgba(255, 255, 255, 0.6),
      inset 2px 2px 3px rgba(0, 0, 0, 0.6);
    }
    table {
      width: 100%;
      border: 2px solid #484b53;
    }
    th {
      background-color: #c0a7fc;
      border: 2px solid #484b53;

      text-align: center;
    }
    td {
      border: 1px solid #6d3be0;

      text-align: left;
    }
    
    thead th {
      background-color: #c6ffdc;
    }

  select{
    appearance: none;
    width: 200px;/*セレクトボックスの横幅*/
    padding: 10px;
    top: -3px;/*矢印の上からの位置*/
    right: -20px;/*矢印の右からの位置*/
    color: #333;/*テキストの色*/
    border: 1px solid #5cc0b5;/*枠線の太さ・形状・色*/
    border-radius: 3px;/*枠線の角丸*/
    background-color: #fff;/*背景色*/
    cursor: pointer;/*マウスカーソルをポインターに*/
  }

  /* 行のホバー時の効果 */
tbody tr:hover {
  background-color: #f1f1f1; /* 薄い灰色 */
}

/* キャンセルボタンのスタイル */
.cancel-btn {
  width: 20px;/*セレクトボックスの横幅*/
  padding: 2px 2px; /* ボタン内の余白 */
  background-color: #5d40ff; /* 赤みがかった色 */
  color: white; /* テキストを白色に */
  border: none; /* 枠線なし */
  border-radius: 4px; /* 角を少し丸める */
  cursor: pointer; /* ポインターを表示 */
  font-weight: 120;
  font-size: 12px;
}

/* ボタンのホバー時の効果 */
.cancel-btn:hover {
  background-color: #ff3333; /* ホバー時に濃い赤 */
}

管理者側フォーム(サーバー)の紹介
1,受付開始時間~終了時間の設定
2,テーブル内の全レコード削除(受付時間中は不可)
3,最大受付数の設定
4,ID番号による検索機能
5,テーブルのリアルタイム表示
6,各レコードの個別削除
adminフォルダ内 admin.html admin.js delete_record.php delete_all_records.php fetch_admin_data.php save_admin_times.php search_record.php set_max_records.php submit_record.php admin_data.json admin_times.json max_records.json admin.css
HTML
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>管理者専用ページ</title>
<body>
    <h1>【管理者用受付設定】</h1>
    <form id="adminTimeForm">
        <label for="startTime">受付開始時刻:</label>
        <input type="time" id="startTime" required>
        
        <label for="endTime">受付終了時刻:</label>
        <input type="time" id="endTime" required>
        
        <button type="button" id="saveTimeButton">受付時間を保存</button>
    </form>
    <p id="timeSaveMessage" style="color: green; font-weight: bold;"></p>
    <h3>現在の受付時間</h3>
    <p id="displayTimeMessage" style="font-weight: bold; color: blue;"></p>
    <div><font color="#ff0099"><strong>本日分の全データを削除します※受付時間外操作</strong></font></div>
    <button id="deleteAllButton" style="background-color: red; color: white; padding: 10px; border-radius: 5px; cursor: pointer;">全削除</button>

    <h3>受付制限数の設定</h3>
    <label for="maxRecords">最大受付件数:</label>
    <input type="number" id="maxRecords" min="1" required>
    <button id="setLimitButton">設定</button>
    <p id="limitMessage" style="font-weight: bold; color: green;"></p>

    <h1>管理者専用データ一覧</h1>
    <div>
    <label for="searchId">ID番号検索:</label>
    <input type="text" id="searchId" placeholder="ID番号を入力">
    <button id="searchButton">検索</button>
    <p id="searchMessage" style="font-weight: bold; color: blue;"></p>
    </div>
    <button id="resetButton">全データを表示</button>
    <table id="adminTable" border="1">
    <thead>
        <tr>
            <th>受付順</th>
            <th>名前</th>
            <th>ID</th>
            <th>内容</th>
            <th>電話番号</th>
            <th>メールアドレス</th>
            <th>時間</th>
            <th>削除</th>
        </tr>
    </thead>
    <tbody>
        <!-- データが動的に表示される -->
    </tbody>
</table>
    <script src="admin.js"></script>
</body>
</html>
JS
document.addEventListener("DOMContentLoaded", () => {
    // DOM要素の取得
    const saveTimeButton = document.getElementById("saveTimeButton");
    const startTimeInput = document.getElementById("startTime");
    const endTimeInput = document.getElementById("endTime");
    const timeSaveMessage = document.getElementById("timeSaveMessage");
    const displayTimeMessage = document.getElementById("displayTimeMessage");
    const deleteAllButton = document.getElementById("deleteAllButton");
    const maxRecordsInput = document.getElementById("maxRecords");
    const setLimitButton = document.getElementById("setLimitButton");
    const limitMessage = document.getElementById("limitMessage");
    
    // サーバーから受付時間を取得する関数
    const fetchTimes = async () => {
        try {
            const response = await fetch(`/jyunbanmati/admin/admin_times.json?timestamp=${new Date().getTime()}`);
            if (!response.ok) throw new Error("受付時間の取得に失敗しました");
            const times = await response.json();
            console.log("最新の受付時間:", times); // デバッグ用
            return times;
        } catch (error) {
            console.error("受付時間取得エラー:", error);
            return null;
        }
    };
//--------------------------------------------------------------
const updateDeleteAllButtonState = async () => {
    const times = await fetchTimes();
    if (!times) {
        console.error("受付時間の取得に失敗しました");
        return;
    }

    const { startTime, endTime } = times;

    // 現在時刻と受付時間の比較用オブジェクトを設定
    const now = new Date();
    const [startHours, startMinutes] = startTime.split(':').map(Number);
    const [endHours, endMinutes] = endTime.split(':').map(Number);

    const start = new Date();
    start.setHours(startHours, startMinutes, 0, 0);

    const end = new Date();
    end.setHours(endHours, endMinutes, 0, 0);

    // 時間内(開始時刻~終了時刻)ではボタンを無効化
    if (now >= start && now <= end) {
        deleteAllButton.disabled = true; // ボタンを無効化
        deleteAllButton.style.backgroundColor = "gray"; // 無効時の色変更
        deleteAllButton.style.cursor = "not-allowed"; // 無効時のカーソル
        console.log("受付時間内: 全削除ボタンが無効です");
    } else {
        deleteAllButton.disabled = false; // ボタンを有効化
        deleteAllButton.style.backgroundColor = "red"; // 有効時の色
        deleteAllButton.style.cursor = "pointer"; // 有効時のカーソル
        console.log("受付時間外: 全削除ボタンが有効です");
    }
};

// 初回の状態更新を実行
updateDeleteAllButtonState();

// 定期的に現在時刻をチェックしボタンの状態を更新(例: 1分ごと)
setInterval(updateDeleteAllButtonState, 60000); // 60000ミリ秒 = 1分
//--------------------------------------------------------------
    // 受付時間を画面に表示する関数
    const displayTimes = async () => {
        const times = await fetchTimes();
        if (!times) {
            displayTimeMessage.textContent = "受付時間を取得できませんでした";
            displayTimeMessage.style.color = "red";
            return;
        }

        const { startTime, endTime } = times;
        displayTimeMessage.textContent = `受付時間は ${startTime}${endTime} です。`;
        displayTimeMessage.style.color = "blue";
    };

    // 初回に受付時間を表示
    displayTimes();

    // 保存ボタンのクリックイベントリスナー
    saveTimeButton.addEventListener("click", async () => {
        const startTime = startTimeInput.value;
        const endTime = endTimeInput.value;
    
        if (!startTime || !endTime || startTime >= endTime) {
            timeSaveMessage.textContent = "正しい時間を設定してください!";
            timeSaveMessage.style.color = "red";
            return;
        }
    
        try {
            const response = await fetch("/jyunbanmati/admin/save_admin_times.php", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({ startTime, endTime })
            });
    
            const result = await response.json();
            if (result.success) {
                timeSaveMessage.textContent = "受付時間が保存されました!";
                timeSaveMessage.style.color = "green";
    
                // 時間の再取得と表示更新
                await updateDeleteAllButtonState();
                await displayTimes();
            } else {
                timeSaveMessage.textContent = "保存に失敗しました!";
                timeSaveMessage.style.color = "red";
            }
        } catch (error) {
            console.error("保存エラー:", error);
            timeSaveMessage.textContent = "エラーが発生しました";
            timeSaveMessage.style.color = "red";
        }
    });

    const loadAdminTable = async () => {
        const searchIdInput = document.getElementById("searchId");
        const searchButton = document.getElementById("searchButton");
        const searchMessage = document.getElementById("searchMessage");
        const tableBody = document.querySelector("#adminTable tbody");
    
        // **初期データロード**: 全てのデータを表示
        const fetchAllData = async () => {
            try {
                const response = await fetch("/jyunbanmati/admin/fetch_admin_data.php"); // 全データ取得用
                const data = await response.json();
    
                if (Array.isArray(data) && data.length > 0) {
                    searchMessage.textContent = ""; // 初期状態はメッセージなし
                    tableBody.innerHTML = ""; // テーブルクリア
                    data.forEach(row => {
                        const tr = document.createElement("tr");
    
                        // 各データセルを作成
                        ["order", "name", "id", "content", "phone", "email", "time"].forEach(key => {
                            const td = document.createElement("td");
                            td.textContent = row[key] || "";
                            tr.appendChild(td);
                        });
    
                        // 削除ボタンを追加
                        const deleteTd = document.createElement("td"); // 削除ボタンのセルを作成
                        const deleteButton = document.createElement("button"); // 削除ボタンを作成
                        deleteButton.textContent = "削除";
                        deleteButton.style.backgroundColor = "red";
                        deleteButton.style.color = "white";

                        // 削除ボタンのクリックイベントを定義
                    deleteButton.addEventListener("click", async () => {
                     if (confirm(`「${row.name}」を削除しますか?`)) {
                    await handleDelete(row); // 削除処理
                    await fetchAllData(); // 削除後に全データを再表示
        }
});

deleteTd.appendChild(deleteButton); // セルにボタンを追加
tr.appendChild(deleteTd); // 行に削除ボタンのセルを追加
    
                        tableBody.appendChild(tr);
                    });
                } else {
                    tableBody.innerHTML = "<tr><td colspan='8'>データがありません</td></tr>";
                }
            } catch (error) {
                console.error("データ取得エラー:", error);
                searchMessage.textContent = "サーバーエラーによりデータを取得できませんでした";
                searchMessage.style.color = "red";
                tableBody.innerHTML = "<tr><td colspan='8'>サーバーエラーが発生しました</td></tr>";
            }
        };
    
        // **検索ボタンのイベントリスナー**
        searchButton.addEventListener("click", async () => {
            const searchId = searchIdInput.value.trim();
    
            // 入力チェック
            if (!searchId) {
                searchMessage.textContent = "ID番号を入力してください";
                searchMessage.style.color = "red";
                return;
            }
    
            try {
                const response = await fetch("/jyunbanmati/admin/search_record.php", {
                    method: "POST",
                    headers: { "Content-Type": "application/json" },
                    body: JSON.stringify({ id: searchId })
                });
    
                const result = await response.json();
                if (result.success) {
                    searchMessage.textContent = "検索結果が表示されました";
                    searchMessage.style.color = "blue";
                
                    tableBody.innerHTML = ""; // テーブルをクリア
                    result.data.forEach(row => {
                        const tr = document.createElement("tr");
                
                        ["order", "name", "id", "content", "phone", "email", "time"].forEach(key => {
                            const td = document.createElement("td");
                            td.textContent = row[key];
                            tr.appendChild(td);
                        });
                
                        const deleteTd = document.createElement("td");
                        const deleteButton = document.createElement("button");
                        deleteButton.textContent = "削除";
                        deleteButton.style.backgroundColor = "red";
                        deleteButton.style.color = "white";
                        deleteButton.addEventListener("click", async () => {
                            if (confirm(`「${row.name}」を削除しますか?`)) {
                                await handleDelete(row); 
                                await loadAdminTable(); 
                            }
                        });
                        deleteTd.appendChild(deleteButton);
                        tr.appendChild(deleteTd);
                
                        tableBody.appendChild(tr);
                    });
                } else {
                    searchMessage.textContent = "該当するIDが見つかりません";
                    searchMessage.style.color = "red";
                    tableBody.innerHTML = "<tr><td colspan='8'>該当するデータはありません</td></tr>"; // テーブル内にメッセージ表示
                }
            } catch (error) {
                console.error("検索エラー:", error);
                searchMessage.textContent = "サーバーエラーにより検索できませんでした";
                searchMessage.style.color = "red";
            }
        });
    
        // 初回全データをロード
        await fetchAllData();
    };

    document.getElementById("resetButton").addEventListener("click", async () => {
        await loadAdminTable(); // 全データを再表示
        searchMessage.textContent = "全データを表示しました";
        searchMessage.style.color = "green";
    });

// データ削除処理
const handleDelete = async (row) => {
    if (confirm(`「${row.name}」を削除しますか?`)) {
        try {
            const response = await fetch("/jyunbanmati/admin/delete_record.php", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({ id: row.id })
            });

            const result = await response.json();
            if (result.success) {
                alert(result.message);
                loadAdminTable(); // テーブルを再読み込み
            } else {
                alert("削除に失敗しました: " + result.message);
            }
        } catch (error) {
            console.error("削除エラー:", error);
            alert("サーバーエラーにより削除できませんでした");
        }
    }
};

deleteAllButton.addEventListener("click", async () => {
    if (confirm("本当にすべてのデータを削除しますか?")) {
        try {
            const response = await fetch("/jyunbanmati/admin/delete_all_records.php", {
                method: "POST",
                headers: { "Content-Type": "application/json" }
            });

            const result = await response.json();
            if (result.success) {
                alert(result.message);
                loadAdminTable(); // テーブルを再読み込み
            } else {
                alert("削除に失敗しました: " + result.message);
            }
        } catch (error) {
            console.error("全削除エラー:", error);
            alert("サーバーエラーにより削除できませんでした");
        }

    }
});

setLimitButton.addEventListener("click", async () => {
    const maxRecords = maxRecordsInput.value;

    // バリデーション
    if (!maxRecords || maxRecords <= 0) {
        limitMessage.textContent = "制限数を正しく入力してください!";
        limitMessage.style.color = "red";
        return;
    }

    try {
        const response = await fetch("/jyunbanmati/admin/set_max_records.php", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ maxRecords })
        });

        const result = await response.json();
        if (result.success) {
            limitMessage.textContent = result.message;
            limitMessage.style.color = "green";
        } else {
            limitMessage.textContent = "設定に失敗しました: " + result.message;
            limitMessage.style.color = "red";
        }
    } catch (error) {
        console.error("制限数設定エラー:", error);
        limitMessage.textContent = "サーバーエラーにより設定できませんでした";
        limitMessage.style.color = "red";
    }
});
// 初回ロード
loadAdminTable();
});
delete_all_records.php
<?php
header("Content-Type: application/json");

// データファイルのパス
$filePath = "admin_data.json";

// ファイルの存在を確認
if (!file_exists($filePath)) {
    echo json_encode(['success' => false, 'message' => 'データファイルが存在しません']);
    exit();
}

// ファイルをクリア(空の配列を書き込む)
if (file_put_contents($filePath, json_encode([], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)) === false) {
    echo json_encode(['success' => false, 'message' => 'データの保存に失敗しました']);
    exit();
}

echo json_encode(['success' => true, 'message' => 'すべてのデータが削除されました']);
?>
delete_record.php
<?php
header("Content-Type: application/json");

// データファイルのパス
$filePath = "admin_data.json";

// リクエストデータを取得
$input = json_decode(file_get_contents("php://input"), true);
$recordId = $input['id'] ?? null;

if (!$recordId) {
    echo json_encode(['success' => false, 'message' => 'IDが送信されていません']);
    exit();
}

// JSONファイルを読み込む
if (!file_exists($filePath)) {
    echo json_encode(['success' => false, 'message' => 'データファイルが存在しません']);
    exit();
}

$data = json_decode(file_get_contents($filePath), true);

if (!is_array($data)) {
    echo json_encode(['success' => false, 'message' => 'データ形式が不正です']);
    exit();
}

// 指定されたIDのデータを削除
$data = array_filter($data, function ($row) use ($recordId) {
    return $row['id'] !== $recordId;
});

// JSONファイルを更新
if (file_put_contents($filePath, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)) === false) {
    echo json_encode(['success' => false, 'message' => 'データの保存に失敗しました']);
    exit();
}

echo json_encode(['success' => true, 'message' => 'レコードが削除されました']);
?>
fetch_admin_data.php
<?php
header('Content-Type: application/json');
$filePath = "admin_data.json";

if (!file_exists($filePath)) {
    echo json_encode([]);
    exit();
}

$data = json_decode(file_get_contents($filePath), true);
echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
?>
save_admin_times.php
<?php
header("Content-Type: application/json");

// 入力データを取得
$data = json_decode(file_get_contents("php://input"), true);

// 必須データが存在するか確認
if (!isset($data['startTime']) || !isset($data['endTime'])) {
    echo json_encode(['success' => false, 'message' => '時刻が送信されていません']);
    exit();
}

$startTime = $data['startTime'];
$endTime = $data['endTime'];

// 時刻データを保存
$filePath = "admin_times.json"; // 保存場所
file_put_contents($filePath, json_encode(['startTime' => $startTime, 'endTime' => $endTime], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));

// 成功レスポンスを返す
echo json_encode(['success' => true, 'message' => '受付時間が保存されました']);
?>
search_record.php
<?php
header("Content-Type: application/json");

// JSONファイルのパス
$filePath = "admin_data.json";

// リクエストデータを取得
$input = json_decode(file_get_contents("php://input"), true);
$searchId = $input['id'] ?? null;

// IDが送信されていない場合のエラーメッセージ
if (!$searchId) {
    echo json_encode(['success' => false, 'message' => 'IDが送信されていません']);
    exit();
}

// データファイルの存在確認
if (!file_exists($filePath)) {
    echo json_encode(['success' => false, 'message' => 'データファイルが存在しません']);
    exit();
}

// JSONファイルの内容を取得
$data = json_decode(file_get_contents($filePath), true);

// IDで検索
$result = array_filter($data, function ($row) use ($searchId) {
    return $row['id'] === $searchId;
});

// 結果が見つからない場合の処理
if (empty($result)) {
    echo json_encode(['success' => false, 'message' => '該当するデータが見つかりません']);
    exit();
}

// 検索結果を返す
echo json_encode(['success' => true, 'data' => array_values($result)]);
?>
set_max_records.php
<?php
header("Content-Type: application/json");

// リクエストから制限数を取得
$input = json_decode(file_get_contents("php://input"), true);
$limit = $input['maxRecords'] ?? null;

if (!$limit || $limit <= 0) {
    echo json_encode(['success' => false, 'message' => '無効な制限数です']);
    exit();
}

// 制限数をJSONファイルに保存
$filePath = "max_records.json";
file_put_contents($filePath, json_encode(['limit' => $limit], JSON_PRETTY_PRINT));

echo json_encode(['success' => true, 'message' => '制限数が設定されました: ' . $limit]);
?>
submit_record.php
<?php
header("Content-Type: application/json");

// 現在のデータ件数を取得
$dataFilePath = "admin_data.json";
$data = json_decode(file_get_contents($dataFilePath), true) ?? [];

// 制限数を取得
$limitFilePath = "max_records.json";
$limitData = json_decode(file_get_contents($limitFilePath), true);
$limit = $limitData['limit'] ?? null;

if ($limit && count($data) >= $limit) {
    echo json_encode(['success' => false, 'message' => '受付件数が上限に達しました']);
    exit();
}

// 新しいデータの登録処理(例)
$newRecord = [...]; // ここに新しいデータを追加するロジック
$data[] = $newRecord;

file_put_contents($dataFilePath, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
echo json_encode(['success' => true, 'message' => 'データが正常に登録されました']);
?>
CSS
/* ベース設定 */
body {
    font-family: Arial, sans-serif;
    margin: 0;
    padding: 0;
    background-color: #f4f4f9;
    color: #333;
}

/* 見出し */
h1 {
    text-align: center;
    color: #0056b3;
    margin: 20px 0;
}

/* フォームスタイル */
form {
    max-width: 600px;
    margin: 20px auto;
    padding: 15px;
    background: #ffffff;
    border: 1px solid #ccc;
    border-radius: 8px;
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}

label {
    font-weight: bold;
    margin-bottom: 5px;
    display: inline-block;
}

input[type="time"], button {
    width: 100%;
    padding: 10px;
    margin-bottom: 10px;
    border: 1px solid #ccc;
    border-radius: 5px;
    box-sizing: border-box;
}

/* ボタン */
button {
    background-color: #007bff;
    color: #fff;
    border: none;
    cursor: pointer;
}

button:hover {
    background-color: #0056b3;
}

/* メッセージ */
p#timeSaveMessage {
    text-align: center;
    margin-top: 10px;
}
admin_data.json
[
    {
        "order": 1,
        "name": "ユーザー1",
        "id": "123",
        "content": "内容A",
        "phone": "09012345678",
        "email": "user1@example.com",
        "time": "2023-04-01 12:00:00"
    },
    {
        "order": 2,
        "name": "ユーザー2",
        "id": "456",
        "content": "内容B",
        "phone": "09087654321",
        "email": "user2@example.com",
        "time": "2023-04-01 13:00:00"
    },
    {   "order": 3,
        "name": "ユーザー3",
        "id": "133",
        "content": "内容A",
        "phone": "09012345678",
        "email": "user1@example.com",
        "time": "2023-04-01 12:00:00"
    },
    {
        "order": 4,
        "name": "ユーザー4",
        "id": "586",
        "content": "内容B",
        "phone": "09087654321",
        "email": "user2@example.com",
        "time": "2023-04-01 13:00:00"
    }
]
admin_times.json
{
    "startTime": "09:00",
    "endTime": "17:30"
}
max_records.json
{
    "limit": "20"
}
フォルダ外のファイル
delete_record.php fetch_client_data.php fetch_public_data.php save_data.php data.csv 
delete_record.php
<?php
header('Content-Type: application/json');

$input = json_decode(file_get_contents('php://input'), true);
$id = $input['id'] ?? null;

if ($id === null) {
    echo json_encode(['success' => false, 'message' => 'レコードIDが指定されていません']);
    exit;
}

$data = json_decode(file_get_contents('data.json'), true);
$data = array_filter($data, function ($row) use ($id) {
    return $row['id'] !== $id;
});

file_put_contents('data.json', json_encode(array_values($data), JSON_PRETTY_PRINT));
echo json_encode(['success' => true]);
?>
fetch_client_data.php
<?php
header("Access-Control-Allow-Origin: *");
header('Content-Type: application/json');

ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

// データ読み込み
$file = __DIR__ . '/data.csv'; // 絶対パスに変更

if (!file_exists($file)) {
    echo json_encode([]);
    exit;
}

error_log("CSVファイル読み込み開始: " . realpath($file));

$rows = array_map('str_getcsv', file($file));
$data = [];

foreach ($rows as $row) {
    $data[] = [
        'name' => $row[0] ?? '',
        'id' => $row[1] ?? '',
        'content' => $row[2] ?? '',
        'time' => $row[5] ?? '' // 時間
    ];
}

error_log("読み込んだ行数: " . count($rows));

// JSONデータとして返す
echo json_encode($data);
?>
fetch_public_data.php
<?php
header('Content-Type: application/json');
$data = json_decode(file_get_contents('data.json'), true);
$publicData = array_map(function ($row) {
    return [
        'name' => $row['name'],
        'content' => $row['content']
    ];
}, $data);
echo json_encode($publicData);
?>
save_data.php
<?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: POST, GET, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
header('Content-Type: application/json');

ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

// POSTデータを取得
$input = json_decode(file_get_contents('php://input'), true);
$name = $input['name'] ?? '';
$id = $input['id'] ?? '';
$content = $input['content'] ?? '';
$phone = $input['phone'] ?? '';
$email = $input['email'] ?? '';

// 必須データの確認
if (empty($name) || empty($id) || empty($content) || empty($phone) || empty($email)) {
    echo json_encode(['success' => false, 'message' => 'すべての入力項目を正しく記入してください']);
    exit;
}

// データ保存
$file = __DIR__ . '/data.csv'; // 保存ファイルパスを絶対パスに変更
$newRecord = [$name, $id, $content, $phone, $email, date('Y-m-d H:i:s')]; // 時間を含む

error_log("データ保存処理開始");

// CSVファイルへの書き込み
if (!file_exists($file)) {
    touch($file); // ファイルがない場合新規作成
    chmod($file, 0666); // 書き込み権限を設定
}

$handle = fopen($file, 'a');
if (!$handle) {
    error_log("CSVファイルを開けません: " . $file);
    echo json_encode(['success' => false, 'message' => 'データ保存に失敗しました']);
    exit;
}

fputcsv($handle, $newRecord);
fclose($handle);

error_log("CSV書き込み成功: " . json_encode($newRecord));

// 成功レスポンスを返す
echo json_encode(['success' => true, 'message' => 'データが保存されました']);
?>
data.csv
山田太郎,0133456987,test1,0123456789,sssss@sss.com,"2025-04-12 18:31:41"
佐藤一郎,9876542510,test2,9876543210,test@test.com,"2025-04-12 18:37:01"
田中一郎,9876543992,test3,9874563210,wwwww@wwww.com,"2025-04-12 18:48:48"
山田次郎,3361654987,test4,0147852369,wwwwww@wwww.com,"2025-04-12 19:58:49"
田中次郎,2205756789,A,3625987410,test@test.com,"2025-04-12 19:59:35"
山田三郎,0123423895,B,4569873210,sssss@sss.com,"2025-04-12 22:15:29"

クライアント側~管理者サバー側とのアクセスコード
フォルダ・ファイル構成
jyunbanmati/admin/
├── admin.html
├── admin.js
├── admin_times.json
├── admin.css
├── fetch_admin_data.php
├── save_admin_times.php
jyunbanmati/client/
├── index.html
├── client.js
├── selectmenu.css
jyunbanmati/
├── delete_record.php
├── fetch_client_data.php
├── fetch_public_data.php
├── save_data.php
├── selectmenu.css
├── data.csv
jyunbanmati/admin/はサーバーアクセス制限があるデレクトリィ
管理者専用ファイルの配置
例: /admin フォルダに管理者専用ファイルを配置
ファイル例: admin.php, fetch_admin_data.php, admin_times.jsonなど。
.htaccessの設定(必要に応じて):
不正なアクセスを防ぐために、アクセス制御ルール
AuthType Basic
AuthName "管理者専用エリア"
AuthUserFile /path/to/.htpasswd
Require valid-user
サーバー側: APIエンドポイント
リアルタイムのデータ共有を可能にするため、APIエンドポイントを作成して必要なデータを提供します。以下はサーバー側の簡易な例です。
fetch_admin_data.php
<?php
header("Content-Type: application/json");
header("Cache-Control: no-cache, no-store, must-revalidate");
header("Pragma: no-cache");
header("Expires: 0");

// 管理者専用データのサンプル
$data = [
    ["order" => 1, "name" => "John Doe", "id" => "123", "content" => "Sample Content", "phone" => "123-456-7890", "email" => "john@example.com", "time" => "2025-04-14"],
    // 必要なデータを追加
];

// JSON形式でデータを返す
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
?>
クライアント側: リアルタイムでデータを取得するコード
クライアント側でAPIからデータを取得し、リアルタイムに表示するコード
client.js:
const fetchAdminData = async () => {
    try {
        const response = await fetch("/admin/fetch_admin_data.php"); // APIのURL
        if (!response.ok) throw new Error("データの取得に失敗しました");

        const data = await response.json();
        console.log("取得したデータ:", data); // デバッグ用
        updateTable(data); // 画面表示の更新
    } catch (error) {
        console.error("データ取得エラー:", error);
    }
};

const updateTable = (data) => {
    const tableBody = document.querySelector("#adminTable tbody");
    tableBody.innerHTML = "";

    data.forEach(row => {
        const tr = document.createElement("tr");
        ["order", "name", "id", "content", "phone", "email", "time"].forEach(key => {
            const td = document.createElement("td");
            td.textContent = row[key];
            tr.appendChild(td);
        });
        tableBody.appendChild(tr);
    });
};

// 初回ロード時にデータを取得
document.addEventListener("DOMContentLoaded", fetchAdminData);

// 定期的な更新(例: 30秒ごとにデータ取得)
setInterval(fetchAdminData, 30000);
クライアント側: HTMLのテーブル
リアルタイムでデータを表示するためのシンプルなHTML構造
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>リアルタイムデータ</title>
    <script src="client.js" defer></script>
    <style>
        table { width: 100%; border-collapse: collapse; }
        th, td { border: 1px solid #ddd; padding: 8px; }
        th { background-color: #f2f2f2; }
    </style>
</head>
<body>
    <h1>管理者データ</h1>
    <table id="adminTable">
        <thead>
            <tr>
                <th>受付順</th>
                <th>名前</th>
                <th>ID</th>
                <th>内容</th>
                <th>電話番号</th>
                <th>メールアドレス</th>
                <th>時間</th>
            </tr>
        </thead>
        <tbody>
            <!-- データが動的に追加されます -->
        </tbody>
    </table>
</body>
</html>
サーバー側 時間管理機能
受付時間を管理し、リアルタイムでボタンの状態を制御するサーバー側のエンドポイント
admin_times.json
{
    "startTime": "09:00",
    "endTime": "17:00"
}

以上です。この環境でほぼ動作すると思います。自己責任でお願いします。