機能拡張していきます。
Contents
🔧 改善目標(機能追加)
- 名前をクリックするとそのユーザーの出退勤履歴を表示
- 履歴は年・月で絞り込めるようにする(プルダウンなどのUI付き)
- 表示を見やすく整理する(インデックスや表など)
✅ 改善プラン
1. HTML構造の拡張(モーダル or 下部に履歴表示エリア)
<!-- 名前をクリックしたら履歴を表示するためのエリア -->
<div id="historyContainer" style="margin-top: 20px;">
<h2 id="historyTitle"></h2>
<!-- 年月選択 -->
<label for="yearSelect">年:</label>
<select id="yearSelect"></select>
<label for="monthSelect">月:</label>
<select id="monthSelect"></select>
<table border="1" id="historyTable">
<thead>
<tr>
<th>日付</th>
<th>時刻</th>
<th>種類</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
2. JavaScriptの改善(履歴表示処理追加)
以下の機能を追加:
- 名前クリックイベント
- 年月セレクター
- attendanceデータのフィルタ・表示
// クリックで履歴を表示
function createUserRow(user, uid, status) {
return `
<tr>
<td><a href="#" onclick="loadHistory('${uid}', '${user.name}')">${user.name}</a></td>
<td>${user.email}</td>
<td>${status}</td>
</tr>
`;
}
// 履歴表示処理
async function loadHistory(uid, name) {
document.getElementById("historyTitle").innerText = `${name} さんの出退勤履歴`;
// 年月セレクタ生成
await populateYearMonthSelects(uid);
// 初期表示(最新の年月)
const year = document.getElementById("yearSelect").value;
const month = document.getElementById("monthSelect").value;
displayHistory(uid, parseInt(year), parseInt(month));
}
// 年月選択肢の作成
async function populateYearMonthSelects(uid) {
const attendanceRef = collection(db, "attendance");
const q = query(attendanceRef, where("uid", "==", uid), orderBy("timestamp", "desc"));
const snapshot = await getDocs(q);
const dates = snapshot.docs.map(doc => doc.data().timestamp.toDate());
const years = [...new Set(dates.map(d => d.getFullYear()))];
const yearSelect = document.getElementById("yearSelect");
const monthSelect = document.getElementById("monthSelect");
yearSelect.innerHTML = years.map(y => `<option value="${y}">${y}</option>`).join("");
yearSelect.onchange = () => {
const selectedYear = parseInt(yearSelect.value);
const months = [...new Set(dates.filter(d => d.getFullYear() === selectedYear).map(d => d.getMonth() + 1))];
monthSelect.innerHTML = months.map(m => `<option value="${m}">${m}</option>`).join("");
displayHistory(uid, selectedYear, parseInt(monthSelect.value));
};
monthSelect.onchange = () => {
displayHistory(uid, parseInt(yearSelect.value), parseInt(monthSelect.value));
};
// 初期値に対して月も更新
yearSelect.dispatchEvent(new Event('change'));
}
// 履歴テーブルを表示
async function displayHistory(uid, year, month) {
const attendanceRef = collection(db, "attendance");
const q = query(attendanceRef, where("uid", "==", uid), orderBy("timestamp", "desc"));
const snapshot = await getDocs(q);
const tbody = document.querySelector("#historyTable tbody");
tbody.innerHTML = "";
snapshot.docs.forEach(doc => {
const data = doc.data();
const ts = data.timestamp.toDate();
if (ts.getFullYear() === year && (ts.getMonth() + 1) === month) {
const row = `
<tr>
<td>${ts.getFullYear()}-${ts.getMonth() + 1}-${ts.getDate()}</td>
<td>${ts.getHours()}:${ts.getMinutes().toString().padStart(2, "0")}</td>
<td>${data.type}</td>
</tr>
`;
tbody.innerHTML += row;
}
});
}
3. loadUsers()
の修正
行生成部分を次のように差し替えます:
const row = createUserRow(user, uid, status);
userList.innerHTML += row;
🧪 実装後のチェックリスト
- 名前をクリックすると履歴が表示されるか?
- 年・月の選択で履歴が切り替わるか?
- 履歴が存在しない場合の表示は問題ないか?
✅ 完全統合版 HTML
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>出退勤履歴一覧</title>
</head>
<body>
<h1>管理者ページ</h1>
<table border="1">
<thead>
<tr>
<th>名前</th>
<th>メールアドレス</th>
<th>出勤状況</th>
</tr>
</thead>
<tbody id="userList"></tbody>
</table>
<!-- 履歴表示エリア -->
<div id="historyContainer" style="margin-top: 40px; display:none;">
<h2 id="historyTitle"></h2>
<div>
<label for="yearSelect">年:</label>
<select id="yearSelect"></select>
<label for="monthSelect">月:</label>
<select id="monthSelect"></select>
</div>
<table border="1" id="historyTable" style="margin-top: 10px;">
<thead>
<tr>
<th>日付</th>
<th>時刻</th>
<th>種類</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/10.11.0/firebase-app.js";
import {
getFirestore,
collection,
getDocs,
query,
where,
orderBy,
limit
} from "https://www.gstatic.com/firebasejs/10.11.0/firebase-firestore.js";
const firebaseConfig = {
apiKey: "あなたのAPIキー",
authDomain: "あなたのauthDomain",
projectId: "あなたのprojectId",
storageBucket: "あなたのstorageBucket",
messagingSenderId: "あなたのmessagingSenderId",
appId: "あなたのappId"
};
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
async function loadUsers() {
const userList = document.getElementById("userList");
userList.innerHTML = "";
try {
const usersSnapshot = await getDocs(collection(db, "users"));
for (const doc of usersSnapshot.docs) {
const user = doc.data();
const uid = doc.id;
let status = "不明";
try {
const attendanceQuery = query(
collection(db, "attendance"),
where("uid", "==", uid),
orderBy("timestamp", "desc"),
limit(1)
);
const attendanceSnapshot = await getDocs(attendanceQuery);
if (!attendanceSnapshot.empty) {
const last = attendanceSnapshot.docs[0].data();
status = last.type === "出勤" ? "出勤中" : "退勤済み";
}
} catch (e) {
console.warn("出退勤データ取得エラー:", e);
}
const row = createUserRow(user, uid, status);
userList.innerHTML += row;
}
} catch (error) {
console.error("ユーザーデータ取得エラー:", error);
}
}
function createUserRow(user, uid, status) {
return `
<tr>
<td><a href="#" onclick="loadHistory('${uid}', '${user.name}'); return false;">${user.name}</a></td>
<td>${user.email}</td>
<td>${status}</td>
</tr>
`;
}
window.loadHistory = async function(uid, name) {
const historyContainer = document.getElementById("historyContainer");
const historyTitle = document.getElementById("historyTitle");
historyTitle.innerText = `${name} さんの出退勤履歴`;
historyContainer.style.display = "block";
await populateYearMonthSelects(uid);
};
async function populateYearMonthSelects(uid) {
const attendanceRef = collection(db, "attendance");
const q = query(attendanceRef, where("uid", "==", uid), orderBy("timestamp", "desc"));
const snapshot = await getDocs(q);
const dates = snapshot.docs.map(doc => doc.data().timestamp.toDate());
const years = [...new Set(dates.map(d => d.getFullYear()))];
const yearSelect = document.getElementById("yearSelect");
const monthSelect = document.getElementById("monthSelect");
yearSelect.innerHTML = years.map(y => `<option value="${y}">${y}</option>`).join("");
yearSelect.onchange = () => {
const selectedYear = parseInt(yearSelect.value);
const months = [...new Set(dates
.filter(d => d.getFullYear() === selectedYear)
.map(d => d.getMonth() + 1))];
monthSelect.innerHTML = months.map(m => `<option value="${m}">${m}</option>`).join("");
displayHistory(uid, selectedYear, parseInt(monthSelect.value));
};
monthSelect.onchange = () => {
displayHistory(uid, parseInt(yearSelect.value), parseInt(monthSelect.value));
};
yearSelect.dispatchEvent(new Event('change'));
}
async function displayHistory(uid, year, month) {
const attendanceRef = collection(db, "attendance");
const q = query(attendanceRef, where("uid", "==", uid), orderBy("timestamp", "desc"));
const snapshot = await getDocs(q);
const tbody = document.querySelector("#historyTable tbody");
tbody.innerHTML = "";
snapshot.docs.forEach(doc => {
const data = doc.data();
const ts = data.timestamp.toDate();
if (ts.getFullYear() === year && (ts.getMonth() + 1) === month) {
const row = `
<tr>
<td>${ts.getFullYear()}-${ts.getMonth() + 1}-${ts.getDate()}</td>
<td>${ts.getHours()}:${ts.getMinutes().toString().padStart(2, "0")}</td>
<td>${data.type}</td>
</tr>
`;
tbody.innerHTML += row;
}
});
}
window.onload = loadUsers;
</script>
</body>
</html>
🔍 機能まとめ
機能 | 内容 |
---|---|
🔵 出勤状況 | Firestoreのusers とattendance を参照し、出勤中/退勤済みを表示 |
🔵 名前クリック | 個別ユーザーの履歴を下に表示 |
🔵 年月プルダウン | 対象ユーザーの履歴から自動生成(重複除外) |
🔵 履歴表示 | 指定年月の出退勤記録を日別で一覧表示 |