Webアプリ「出退勤管理アプリ」を作ってみた

【Webアプリ】出勤管理アプリ(11)ページを整理

admin_attendance.html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <title>出勤状況一覧</title>
</head>
<body>
  <h1>出勤状況一覧</h1>
  <a href="admin_requests.html">▶ 申請管理ページへ</a>

  <table border="1">
    <thead>
      <tr>
        <th>名前</th>
        <th>メールアドレス</th>
        <th>出勤状況</th>
        <th>詳細</th>
      </tr>
    </thead>
    <tbody id="userList"></tbody>
  </table>

  <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: "あなたのプロジェクトID.firebaseapp.com",
            projectId: "あなたのプロジェクトID",
            storageBucket: "あなたのプロジェクトID.appspot.com",
            messagingSenderId: "送信者ID",
            appId: "アプリID"
    };

    const app = initializeApp(firebaseConfig);
    const db = getFirestore(app);

    async function loadUsers() {
      const userList = document.getElementById("userList");
      userList.innerHTML = "";
      const usersSnapshot = await getDocs(collection(db, "users"));

      for (const docSnap of usersSnapshot.docs) {
        const user = docSnap.data();
        const uid = docSnap.id;
        let status = "不明";

        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();
          const lastTimestamp = last.timestamp.toDate();
          const now = new Date();
          const isSameDay =
            lastTimestamp.getFullYear() === now.getFullYear() &&
            lastTimestamp.getMonth() === now.getMonth() &&
            lastTimestamp.getDate() === now.getDate();
          status = (last.type === "出勤" && isSameDay) ? "出勤中" : "退勤済み";
        }

        const detailLink = `<a href="user_detail.html?uid=${uid}&name=${encodeURIComponent(user.name)}" target="_blank">詳細</a>`;

        const row = `
          <tr>
            <td>${user.name}</td>
            <td>${user.email}</td>
            <td>${status}</td>
            <td>${detailLink}</td>
          </tr>
        `;
        userList.innerHTML += row;
      }
    }

    window.onload = () => {
      loadUsers();
    };
  </script>
</body>
</html>

✅ 現在の機能まとめ

このHTMLは、Firebase Firestoreを使ってユーザーの出勤状況一覧を表示する管理用ページです。

機能内容
Firebase連携users と attendance コレクションを読み込む
出勤状況判定本日中の最新の「出勤」記録があれば「出勤中」、なければ「退勤済み」表示
ユーザー一覧表示名前・メール・出勤状況・詳細リンクをテーブルに表示
詳細リンクuser_detail.html に uid と name をクエリパラメータで送信

🧠 解説ポイント

1. users コレクションの読み込み

const usersSnapshot = await getDocs(collection(db, "users"));

全ユーザーの一覧を取得。

2. 各ユーザーの最新出退勤情報の取得

const attendanceQuery = query(
  collection(db, "attendance"),
  where("uid", "==", uid),
  orderBy("timestamp", "desc"),
  limit(1)
);
  • 該当ユーザーの最新の出退勤記録(1件)を取得。
  • timestamp による降順ソート + limit(1) により「最新」を取得。

3. 出勤中か退勤済みかの判定

const isSameDay =
  lastTimestamp.getFullYear() === now.getFullYear() &&
  lastTimestamp.getMonth() === now.getMonth() &&
  lastTimestamp.getDate() === now.getDate();
status = (last.type === "出勤" && isSameDay) ? "出勤中" : "退勤済み";
  • 本日中の「出勤」記録なら「出勤中」
  • それ以外(古い or 「退勤」記録)は「退勤済み」

4. 詳細ページリンク

<a href="user_detail.html?uid=${uid}&name=${encodeURIComponent(user.name)}" target="_blank">詳細</a>
  • ユーザーIDと名前をパラメータとして送信
  • target="_blank" で別タブ表示

admin_requests.html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <title>申請管理</title>
</head>
<body>
  <h1>申請管理ページ</h1>
  <a href="admin_attendance.html">▶ 出勤状況一覧ページへ</a>

  <h2>申請中のもののみ表示</h2>
  <table border="1">
    <thead>
      <tr>
        <th>氏名</th>
        <th>申請日</th>
        <th>種類</th>
        <th>理由</th>
        <th>操作</th>
      </tr>
    </thead>
    <tbody id="adminRequestBody"></tbody>
  </table>

  <script type="module">
    import { initializeApp } from "https://www.gstatic.com/firebasejs/10.11.0/firebase-app.js";
    import {
      getFirestore, collection, getDocs, query, where, orderBy, updateDoc, doc
    } from "https://www.gstatic.com/firebasejs/10.11.0/firebase-firestore.js";

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

    const app = initializeApp(firebaseConfig);
    const db = getFirestore(app);

    async function getUserMap() {
      const map = {};
      const usersSnapshot = await getDocs(collection(db, "users"));
      usersSnapshot.forEach(doc => {
        map[doc.id] = doc.data().name;
      });
      return map;
    }

    async function loadAdminRequests() {
      const tbody = document.getElementById("adminRequestBody");
      tbody.innerHTML = "";

      const userMap = await getUserMap();

      const q = query(
        collection(db, "applications"),
        where("status", "==", "申請中"),
        orderBy("timestamp", "desc")
      );
      const snapshot = await getDocs(q);

      for (const docSnap of snapshot.docs) {
        const data = docSnap.data();
        const id = docSnap.id;
        const date = data.timestamp?.toDate?.() || new Date();
        const userName = userMap[data.uid] || "不明";
        const formattedDate = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${String(date.getMinutes()).padStart(2, '0')}`;

        const row = `
          <tr id="admin-request-${id}">
            <td>${userName}</td>
            <td>${formattedDate}</td>
            <td>${data.type}</td>
            <td>${data.reason || "なし"}</td>
            <td>
              <button onclick="adminUpdateStatus('${id}', '許可')">許可</button>
              <button onclick="adminUpdateStatus('${id}', '却下')">却下</button>
            </td>
          </tr>
        `;
        tbody.innerHTML += row;
      }
    }

    window.adminUpdateStatus = async function(id, status) {
      const ref = doc(db, "applications", id);
      await updateDoc(ref, { status });
      const row = document.getElementById(`admin-request-${id}`);
      if (row) row.remove();
      alert(`ステータスを「${status}」に更新しました`);
    };

    window.onload = () => {
      loadAdminRequests();
    };
  </script>
</body>
</html>

✅ 現在の機能まとめ

このHTMLは、Firebase Firestore の applications コレクションから申請中の申請だけを表示し、管理者が許可・却下の操作を行える「申請管理ページ」です。

項目内容
表示対象status が「申請中」の申請のみ
表示情報氏名・申請日・申請種別・理由・操作(許可/却下)
操作ボタンを押すと status を Firestore 上で更新し、画面から該当行を削除
ユーザー名取得users コレクションから全ユーザー名を取得し、UIDで照合して名前を表示

🧠 解説ポイント

1. ユーザーID→名前のマッピング(getUserMap

const map = {};
usersSnapshot.forEach(doc => {
  map[doc.id] = doc.data().name;
});
  • Firestore の users コレクションを全件取得して、
  • uid => 名前 のオブジェクトを作成。

2. 申請データの読み込み(loadAdminRequests

const q = query(
  collection(db, "applications"),
  where("status", "==", "申請中"),
  orderBy("timestamp", "desc")
);
  • 申請中のものだけを抽出し、申請日降順で表示。

3. ステータス変更(adminUpdateStatus

await updateDoc(ref, { status });
  • Firestoreの該当申請ドキュメントを更新。
  • DOMから該当行を削除。
  • アラートで通知。