Reactで作る勤怠状況管理アプリ

## こんなの作りました

![無題の動画-‐-Clipchampで作成.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480394/6e9057c6-52f5-7327-a235-f83c59bacd2a.gif)

## はじめに

Reactで何か作てっみたいなと考えた際に、「どうせなら業務で役に立つものを作ってみよう！」ということで今回の「勤怠状況管理アプリ」を作成しました。

本記事は、プログラミング初学者が、学習を進めていて疑問に思った点について調べた結果を備忘録も兼ねてまとめたものです。

そのため、記事の内容に誤りが含まれている可能性があります。ご容赦ください。間違いを見つけた方は、お手数ですが、ご指摘いただけますと幸いです。

## スキルレベル（筆者自身）

- HTML,CSS,JavaScript（実務経験2年）
- React

## 設計

必要な機能をざっとまとめてみました。

> - 当日出勤・退勤したらボタンを押してステータスを変更する
> - メンバーの全員分の勤怠状況を一覧で確認できる
> - 初期表示で最低でも1週間分の出勤予定情報は表示させたい
> - 不特定多数のユーザーが同時にアクセスすることがあるが、ユーザー認証機能はつけたくない
> - 読み込みの速度は速くする
> - 情報を保存ボタンなどは実装せず、更新時に都度DB更新を行う


今回のプロジェクトでは以下のようなディレクトリ構成になっています。

-src
--|-asssets(画像やCSSファイル)
--|-common
----|-AttendPlace.jsx（編集画面での「場所」プルダウン機能を実装）
----|-AttendSelect.jsx（HOME画面での従業員選択プルダウン機能を実装）
----|-AttendTime.jsx（編集画面での「時間」プルダウン機能を実装）
----|-Info.jsx（編集画面でプルダウンから選択したものを下記に表示する機能を実装）
----|-StoreInfo.jsx（DBに登録されているデータをHOME画面に表示する）
--|-components
----|-AttendButton.jsx（出勤退勤ボタンの表示と登録情報をDB更新を実装）
----|-Edit.jsx（編集画面全体の機能を実装）
----|-Header.jsx（ヘッダー部分を実装）
----|-Home.jsx（HOME画面全体の機能を実装）
--|-pages
----|-index.jsx（各コンポーネントの呼び出しとルーティング機能を実装）
----|-firebase.js（configの情報を記載）
--|-index.js（index.jsxを表示）

他の方の出勤状況を確認するページと、出勤予定を編集するページに分けました。

ユーザー認証は導入せず、プルダウンリストから自身の名前を選択することでデータの取得・更新を実装しています。


## 実装

初回レンダリング時にfirestoreからdocument idを取得。この際にユーザー情報を配列usersに格納している。ユーザー登録機能を実装していないので、事前にfirestoreにユーザーネームを登録する必要がある。

```react
const [users, setUsers] = useState([]);
useEffect(() => {
    const usersCollectionRef = collection(db, "users");
    getDocs(usersCollectionRef).then((querySnapshot) => {
      setUsers(
        querySnapshot.docs.map((doc) => ({ ...doc.data(), id: doc.id }))
      );
    });
  }, []);
```



7日分の日付データを配列dateListに格納

```react
const dateList = [];
  const weeksEn = [];

  //「＿」は単純に引数として書く必要があるため、記述はされたが特に使われていない」から気にしないでOK
  [...Array(7)].map((_, id) => {
    dateList.push(
      //現在時刻に
      moment()
        //0~7を足す＝現在の日付に０～７を足して1週間の日付を格納
        .add(dateId + id, "days")
        //formatで日付の形式を指定
        .format("MMDD(ddd)")
    );
    weeksEn.push(
      moment()
        .add(dateId + id, "days")
        //曜日を日本語表記で取得する
        .format("ddd")
    );
  });
```



出勤・退勤ボタンは<AttendButton />コンポーネントで実装し、ユーザーごとの出勤情報を表示する表はMUIのtableを使用

```react
return (
    <main className="main layoutHome">
      <div className="container">
        <div className="section-container">
          <AttendButton users={users} />
          <TableContainer component={Paper}>
            <Table className="section-table" aria-label="simple table">
              <TableHead>
                <TableRow>
                  <TableCell></TableCell>
                  {dateList.map((date, id) => (
                    <TableCell key={id} align="center">
                      {date}
                    </TableCell>
                  ))}
                </TableRow>
              </TableHead>
              <TableBody>
                {users.length > 0 &&
                  users.map((user) => (
                    <TableRow>
                      <TableCell key={user.id} component="th" scope="row">
                        {user.name}
                      </TableCell>
                      {dateList.map((id) => (
                        <TableCell key={id} align="center">
                          {user.time}
                          {id === today && user.now}
                        </TableCell>
                      ))}
                    </TableRow>
                  ))}
              </TableBody>
            </Table>
          </TableContainer>
          <div className="arrowLeft">
            <IconButton onClick={() => setDateId(dateId - 7)}>
              <img src={arrow} alt="arrow" />
            </IconButton>
          </div>
          <div className="arrowRight">
            <IconButton onClick={() => setDateId(dateId + 7)}>
              <img src={arrow} alt="arrow" />
            </IconButton>
          </div>
        </div>
      </div>
    </main>
  );
```



<AttendButton />コンポーネントでは、プルダウンリストからユーザーを選択し、選択したユーザーネームに対応するdocument idに対して、dbを更新する。

更新する際には、updateDocを使用。

> updateDocは、フィールドに要素があれば更新、なければ作成するメソッド

```react
const handleInChange = async (event) => {
    event.preventDefault();
    const value = people;
    if (value === "○○") {
      const docRef = doc(db, "users", "doc id");
      await updateDoc(docRef, {
        now: "△",
      });
    }
    if (value === "○○") {
      const docRef = doc(db, "users", "doc id");
      await updateDoc(docRef, {
        now: "△",
      });
    }
  };
  const handleOutChange = async (event) => {
    event.preventDefault();
    const value = people;
    if (value === "○○") {
      const docRef = doc(db, "users", "doc id");
      await updateDoc(docRef, {
        now: "-",
      });
    }
    if (value === "○○") {
      const docRef = doc(db, "users", "doc id");
      await updateDoc(docRef, {
        now: "-",
      });
    }
  };

  return (
    <div>
      <AttendSelect options={member} handleValueChange={handleValueChange} />
      <h1>{people}</h1>
      <Button variant="contained" onClick={handleInChange}>
        出勤
      </Button>
      <Button variant="outlined" onClick={handleOutChange}>
        退勤
      </Button>
    </div>
  );
};
```



出勤場所、時間についてはも、プルダウンリストから選択したものを日付単位で管理する方法でdbに書き込む。

```react
 const saveDB = async (event) => {
    event.preventDefault();
    const type1 = event.target.value;
    const docRef = doc(db, "users", props.user, props.date, "aaa");
    await setDoc(docRef, {
      place: type1,
    });
  };
  return (
    <FormControl>
      <InputLabel></InputLabel>
      <Select onChange={saveDB}>
        {props.options.map((option) => (
          <MenuItem key={option.id} value={option.id}>
            {option.name}
          </MenuItem>
        ))}
      </Select>
    </FormControl>
  );
};
```



## 問題点

- 一定期間が過ぎた後、過去のデータを削除する処理ができていない
- セキュリティ関係全く考慮していない
- プルダウンからユーザーを選択し、対象のユーザーの登録情報を認証する部分の実装が冗長


## 展望

上記問題点を解決しつつ、既存の勤怠管理アプリの情報を継承して表示する仕組みがあればより便利。


## 参考文献

・モダンJavaScriptの基本から始める React実践の教科書

・トラゼミ
→youtubeでReact開発講座を見ながら実際に手を動かす