Webアプリ

【Webアプリ】出退勤管理アプリ⑥ 管理者画面の強化

次に管理者画面を強化していきます。

変更内容

内容説明
① 社員一覧を管理者画面で表示誰が登録されているかを一覧で見たい
② スタッフごとの過去の履歴も見たい日付ごとの出退勤履歴を社員ごとに確認

進め方のイメージ

順番に作っていきます。

  1. Firestoreに「社員リスト」を作成
  2. 管理者ページで社員一覧表示
  3. 各社員ごとに「今の出勤状況」を表示
  4. 社員をクリックすると「その人の履歴一覧ページ」にジャンプ

①最初のステップ:「社員リスト」を作ろう

Firestoreに「usersコレクション」を作成する

  • コレクション名:users
  • ドキュメント:社員ごとのデータ
  • データ例:
    • name: “田中 太郎”
    • email: “tanaka@example.com
    • role: “employee”(普通の社員ならemployee、管理者ならadmin)

🛠 Firestoreに手動で追加する方法

  1. Firebaseコンソールに行く
  2. Firestore Databaseを開く
  3. 「コレクションを開始」をクリック
  4. コレクションIDに users と入力
  5. ドキュメントIDは自動生成でもOK
  6. フィールドを追加:
    • name : “山田 太郎”(←社員名)
    • email : “yamada@example.com“(←社員のメールアドレス)
    • role : “employee”(←社員ならemployee)

これでOKです!

次にやること

管理者ページ(admin.html)で、
この users コレクションを読み込んで、社員一覧を表示します!

イメージ👇

名前メールアドレス出勤状況
山田 太郎yamada@example.com出勤中
佐藤 花子sato@example.com退勤済み

今あるadmin.htmlの <body> 部分をちょっと変えます👇

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>出退勤履歴一覧</title>
    <script src="https://www.gstatic.com/firebasejs/10.11.0/firebase-app-compat.js"></script>
    <script src="https://www.gstatic.com/firebasejs/10.11.0/firebase-firestore-compat.js"></script>
</head>
<body>
    <h1>管理者ページ</h1>

    <table border="1">
        <thead>
            <tr>
                <th>名前</th>
                <th>メールアドレス</th>
                <th>出勤状況</th>
            </tr>
        </thead>
        <tbody id="userList">
            <!-- ここに社員リストが表示される -->
        </tbody>
    </table>

    <script>
        // ここにFirebase設定と初期化を追加!!👇
        const firebaseConfig = {
            apiKey: "あなたのAPIキー",
            authDomain: "あなたのプロジェクトID.firebaseapp.com",
            projectId: "あなたのプロジェクトID",
            storageBucket: "あなたのプロジェクトID.appspot.com",
            messagingSenderId: "送信者ID",
            appId: "アプリID"
        };

        // Firebase初期化
        firebase.initializeApp(firebaseConfig);

        // Firestore使うよ
        const db = firebase.firestore();

        function loadUsers() {
            db.collection("users").get()
                .then((querySnapshot) => {
                    const userList = document.getElementById("userList");
                    userList.innerHTML = ""; // 一回クリア

                    querySnapshot.forEach((doc) => {
                        const user = doc.data();

                        // 出勤状況は今は仮で「不明」
                        const status = "不明";

                        const row = `
                            <tr>
                                <td>${user.name}</td>
                                <td>${user.email}</td>
                                <td>${status}</td>
                            </tr>
                        `;
                        userList.innerHTML += row;
                    });
                })
                .catch((error) => {
                    console.error("エラー:", error);
                });
        }

        // ページが開いたら実行
        window.onload = loadUsers;
    </script>
</body>
</html>

② 社員ごとの過去の出退勤履歴を一覧で見れるようにする

やりたいこと

  • Firestoreのattendanceコレクションから
  • 特定のUIDと一致するデータだけ取得して
  • リスト表示する
  • 日付や出勤・退勤の情報も見やすく出す

イメージとしては、

日付出勤/退勤時間
4月28日出勤23:42
4月28日退勤23:50

みたいな感じ。

これから作る機能

  • Firestoreから指定のユーザーのattendanceデータだけを取得
  • 名前にボタン機能をつける
  • ボタンを押すと履歴がリスト表示される
  • 日付・出勤or退勤・時間をわかりやすく表示する

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>出退勤履歴一覧</title>
    <script src="https://www.gstatic.com/firebasejs/10.11.0/firebase-app-compat.js"></script>
    <script src="https://www.gstatic.com/firebasejs/10.11.0/firebase-firestore-compat.js"></script>
</head>
<body>
    <h1>管理者ページ</h1>

    <table border="1">
        <thead>
            <tr>
                <th>名前</th>
                <th>メールアドレス</th>
                <th>出勤状況</th>
            </tr>
        </thead>
        <tbody id="userList">
            <!-- ここに社員リストが表示される -->
        </tbody>
    </table>

    <h2>出退勤履歴</h2>
    <div id="attendanceList">
        <!-- ここに選択した社員の出退勤記録が表示される -->
    </div>

    <script>
        const firebaseConfig = {
            apiKey: "あなたのAPIキー",
            authDomain: "あなたのプロジェクトID.firebaseapp.com",
            projectId: "あなたのプロジェクトID",
            storageBucket: "あなたのプロジェクトID.appspot.com",
            messagingSenderId: "送信者ID",
            appId: "アプリID"
        };

        firebase.initializeApp(firebaseConfig);
        const db = firebase.firestore();

        function loadUsers() {
            db.collection("users").get()
                .then((querySnapshot) => {
                    const userList = document.getElementById("userList");
                    userList.innerHTML = "";

                    querySnapshot.forEach((doc) => {
                        const user = doc.data();
                        const userId = doc.id; // ドキュメントIDがUIDの場合

                        const row = `
                            <tr>
                                <td><a href="#" onclick="loadAttendance('${userId}', '${user.name}')">${user.name}</a></td>
                                <td>${user.email}</td>
                                <td>不明</td>
                            </tr>
                        `;
                        userList.innerHTML += row;
                    });
                })
                .catch((error) => {
                    console.error("エラー:", error);
                });
        }

        function loadAttendance(userId, userName) {
            const attendanceList = document.getElementById("attendanceList");
            attendanceList.innerHTML = `<h3>${userName}さんの出退勤履歴</h3>`;

            db.collection("attendance")
                .where("uid", "==", userId)
                .orderBy("time", "desc")
                .get()
                .then((querySnapshot) => {
                    if (querySnapshot.empty) {
                        attendanceList.innerHTML += "<p>出退勤記録がありません。</p>";
                        return;
                    }

                    let listHtml = "<ul>";
                    querySnapshot.forEach((doc) => {
                        const data = doc.data();
                        const type = data.type;
                        const time = data.time.toDate().toLocaleString('ja-JP');

                        listHtml += `<li>${time} - ${type}</li>`;
                    });
                    listHtml += "</ul>";
                    attendanceList.innerHTML += listHtml;
                })
                .catch((error) => {
                    console.error("出退勤記録の取得エラー:", error);
                });
        }

        window.onload = loadUsers;
    </script>
</body>
</html>

🔥変更ポイントまとめ

  • 名前をリンクにして、クリックしたらloadAttendance(userId, userName)を呼ぶ
  • attendanceコレクションから、uidが一致する出退勤データを取得
  • 時系列(新しい順)に並べてリスト表示
  • 出勤 or 退勤が時刻と一緒に表示される

さらに良くするために

  • 「次の月へ」「前の月へ」ボタン式にする
  • 今月以降の未来の月は押せないようにする
  • 出勤・退勤でアイコンや色をつける
  • 合計勤務日数、合計勤務時間を出す

改良版コード

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>出退勤履歴一覧</title>
    <script src="https://www.gstatic.com/firebasejs/10.11.0/firebase-app-compat.js"></script>
    <script src="https://www.gstatic.com/firebasejs/10.11.0/firebase-firestore-compat.js"></script>
    <style>
        .clock-in {
            color: green;
            font-weight: bold;
        }
        .clock-out {
            color: blue;
            font-weight: bold;
        }
    </style>
</head>
<body>
    <h1>管理者ページ</h1>

    <table border="1">
        <thead>
            <tr>
                <th>名前</th>
                <th>メールアドレス</th>
                <th>出勤状況</th>
            </tr>
        </thead>
        <tbody id="userList">
            <!-- ここに社員リストが表示される -->
        </tbody>
    </table>

    <h2>出退勤履歴</h2>
    <div id="attendanceList">
        <!-- ここに選択した社員の出退勤記録が表示される -->
    </div>

    <script>
        const firebaseConfig = {
          apiKey: "あなたのAPIキー",
            authDomain: "あなたのプロジェクトID.firebaseapp.com",
            projectId: "あなたのプロジェクトID",
            storageBucket: "あなたのプロジェクトID.appspot.com",
            messagingSenderId: "送信者ID",
            appId: "アプリID"
        };

        firebase.initializeApp(firebaseConfig);
        const db = firebase.firestore();

        let currentUserId = null;
        let currentUserName = null;
        let currentYear = null;
        let currentMonth = null; // 0〜11
        const today = new Date();

        function loadUsers() {
            db.collection("users").get()
                .then((querySnapshot) => {
                    const userList = document.getElementById("userList");
                    userList.innerHTML = "";

                    querySnapshot.forEach((doc) => {
                        const user = doc.data();
                        const userId = doc.id;

                        const row = `
                            <tr>
                                <td><a href="#" onclick="loadAttendance('${userId}', '${user.name}')">${user.name}</a></td>
                                <td>${user.email}</td>
                                <td>不明</td>
                            </tr>
                        `;
                        userList.innerHTML += row;
                    });
                })
                .catch((error) => {
                    console.error("エラー:", error);
                });
        }

        function loadAttendance(userId, userName) {
            currentUserId = userId;
            currentUserName = userName;

            const now = new Date();
            currentYear = now.getFullYear();
            currentMonth = now.getMonth();

            renderAttendancePage();
        }

        function renderAttendancePage() {
            const attendanceList = document.getElementById("attendanceList");
            attendanceList.innerHTML = `
                <h3>${currentUserName}さんの出退勤履歴</h3>
                <div>
                    <button onclick="changeMonth(-1)">前の月へ</button>
                    <span id="currentMonthLabel">${currentYear}年${currentMonth + 1}月</span>
                    <button onclick="changeMonth(1)">次の月へ</button>
                </div>
                <div id="attendanceRecords"></div>
            `;

            fetchAttendance(currentUserId, currentYear, currentMonth);
        }

        function changeMonth(diff) {
            let tempYear = currentYear;
            let tempMonth = currentMonth + diff;

            if (tempMonth < 0) {
                tempMonth = 11;
                tempYear--;
            } else if (tempMonth > 11) {
                tempMonth = 0;
                tempYear++;
            }

            // 未来の月はダメ!
            if (tempYear > today.getFullYear() || (tempYear === today.getFullYear() && tempMonth > today.getMonth())) {
                return;
            }

            currentYear = tempYear;
            currentMonth = tempMonth;

            document.getElementById("currentMonthLabel").innerText = `${currentYear}年${currentMonth + 1}月`;
            fetchAttendance(currentUserId, currentYear, currentMonth);
        }

        function fetchAttendance(userId, year, month) {
            const start = new Date(year, month, 1);
            const end = new Date(year, month + 1, 1);

            const attendanceRecords = document.getElementById("attendanceRecords");
            attendanceRecords.innerHTML = `<p>読み込み中...</p>`;

            db.collection("attendance")
                .where("uid", "==", userId)
                .where("time", ">=", start)
                .where("time", "<", end)
                .orderBy("time", "asc")
                .get()
                .then((querySnapshot) => {
                    if (querySnapshot.empty) {
                        attendanceRecords.innerHTML = "<p>この月の出退勤記録はありません。</p>";
                        return;
                    }

                    let listHtml = "<ul>";
                    let lastClockIn = null;
                    let totalWorkedMs = 0;
                    let workDays = 0;

                    querySnapshot.forEach((doc) => {
                        const data = doc.data();
                        const type = data.type;
                        const time = data.time.toDate();
                        const timeString = time.toLocaleString('ja-JP');

                        if (type === "出勤") {
                            listHtml += `<li class="clock-in">✅ ${timeString} - 出勤</li>`;
                            lastClockIn = time;
                            workDays++;
                        } else if (type === "退勤") {
                            listHtml += `<li class="clock-out">🏁 ${timeString} - 退勤</li>`;
                            if (lastClockIn) {
                                totalWorkedMs += time - lastClockIn;
                                lastClockIn = null;
                            }
                        }
                    });

                    listHtml += "</ul>";

                    const totalHours = (totalWorkedMs / (1000 * 60 * 60)).toFixed(2);

                    listHtml += `<p>✅ 勤務日数: ${workDays}日</p>`;
                    listHtml += `<p>⏰ 総勤務時間: ${totalHours}時間</p>`;

                    attendanceRecords.innerHTML = listHtml;
                })
                .catch((error) => {
                    console.error("出退勤記録の取得エラー:", error);
                });
        }

        window.onload = loadUsers;
    </script>
</body>
</html>

改善ポイントまとめ

機能できること
✅未来制限今月より未来には「次へ」できない!
✅色分け出勤→✅緑色、退勤→🏁青色でわかりやすい
✅合計表示勤務日数と総勤務時間(小数点2桁)を月ごとに表示