受付順番待ち登録は、クライアント側~管理者側(サーバー)間で、リアルタイムで情報を共有するシステムです。また、其々のコードは一般公開していますので、機能変更・追加・削除・修正・デザイン変更等のカスタマイズは自由です。また、コピー&ペーストも許可しますが、システム構築後の動作不具合などは自己責任でお願いします。
仕様の概略
1.クライアント側は、各項目(氏名、ID、内容、電話番号、メールアドレス等の各必須項目と、各種の入力条件、全ての入力完了で登録許可などです。
2.管理者フォーム側(サーバー)は、開始終了時間の設定、受付制限の設定、個別削除・全体削除、テーブルの検索機能、設定時間内の全削除不可等を設けています。
3.クライアント側とサーバー間のアクセスコードは最終行近くにコードを記述しています。
デザインや仕様など変更なく公開した場合は、どこか片隅に小さくMOMOPLANと書いて頂くと嬉しいです。


入力フォームの仕様 |
1.氏名16文字以内 |
2.ID 10桁制限 |
3.内容 4項目選択 その他入力 |
4.電話番号 桁数制限 |
5.メールアドレス ドメイン基本仕様制限 |
6.受付までの時間表示(カウントダウン・・受付まで何時何分何秒・・まもなく・・受付中・・受付終了) |
7.受付順番のテーブル表示(電話番号・メールアドレスはプライバシーを配慮して非表示) |
8.受付時間以外各項目入力不可 |
各コードのパスは使用環境に応じて変更してください。
クライアント側のコードの紹介 |
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"
}
以上です。この環境でほぼ動作すると思います。自己責任でお願いします。