【初めてのAWS】チームの勤怠管理システムを作ってみた（その３）

# 1. 初めに
勤怠管理システムの全貌としては以下の3つである。3本に分けて残していく。今回はその３、とうとう最終回！
[その１：名前を選択して「出勤」or「退勤」ボタンを押し、記録する](https://qiita.com/waiiioss/items/6c2ca747a55d3dca9224)
[その２：勤怠記録のデータを可視化する](https://qiita.com/waiiioss/items/f35c1729bb3d17cfbc9e)
その３：過去の勤怠を編集できるようにする <-MOUKOKO

**本Qiitaの内容**
* LambdaでDynamoDBに過去の勤怠を記録
* LambdaでDynamoDBの直近の記録を削除
* 入力のUI試行錯誤

# 2. LambdaでDynamoDBに記録
その１で書いたLambdaは開始時にその時間のTimestampを記録して、終了時にその時のTimestampを記録するものだった。今回のコードは、名前と過去働いた日時を入力し、記録するというものなので、大きく異なる。Lambda関数はそこまで変わらないけど。eventの中のjsonは以下のようなものを想定している。
``` json event.json
{"body": {
    "Name": "Namae",
    "Date": "2024-05-02",
    "StartTime": "09:12",
    "EndTime": "10:00"}
}
```
このイベントJSONが与えられた時に動くようにLambda関数を書く必要がある。まずはイベントJSONの中にあるものをそれぞれ受け取り、以前のDynamoDBへの記録関数と同じ形で記録できるようにデータを加工する。そこの辺は自由にできると思う。そして、DynamoDBへ書き込む。ロールの付与を忘れずに。

``` Python lambda_handler.py
import boto3
import json
from datetime import datetime, timedelta
from decimal import Decimal

def lambda_handler(event, context):
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table('TableName')
    
    # APIからJSON形式でデータを受け取る
    body = json.loads(event['body'])
    name = body['Name']
    date = body['Date']
    start_time = body['StartTime']
    end_time = body['EndTime']

    # 日付と時刻をISO 8601形式に変換する
    start_datetime = datetime.strptime(date + ' ' + start_time, '%Y-%m-%d %H:%M')
    end_datetime = datetime.strptime(date + ' ' + end_time, '%Y-%m-%d %H:%M')
    if end_datetime < start_datetime:
        end_datetime += timedelta(days=1)
    work_hours = Decimal(str((end_datetime - start_datetime).total_seconds() / 3600.00))

    # DynamoDBに書き込むデータを設定
    response = table.put_item(
        Item={
            'Name': name,
            'Timestamp': start_datetime.isoformat(),
            'EndTime': end_datetime.isoformat(),
            'WorkHours': work_hours  # Decimal 型で保存
        }
    )
    
    return {
        'statusCode': 200,
        'body': json.dumps('Item successfully written to DynamoDB.')
    }
```

# 3. Lambdaで直近の記録を消す
最後のLambda関数は直近の記録を消すというものである。使う場面は間違えて入力した場合や、退勤し忘れた場合とかに使えると思う。イベントJSONには名前だけがあり、名前を受け取ったLambda関数がその人のDynamoDBにある直近の記録を消す。
responseがその人の最新の項目を探すもので、いろんな制限ができる。
ちなみにDynamoDBのreadとかは結構早く数ミリ秒とかでできるらしい。（Chat GPTによる）全然関係ないけど。
`Table.delete _item`で消せる。

``` Python lambda_handler.py
import boto3

def lambda_handler(event, context):
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table('TableName')
    target_name = event["name"]

    # 最新の項目を検索
    response = table.query(
        KeyConditionExpression=boto3.dynamodb.conditions.Key('Name').eq(target_name),
        ScanIndexForward=False,  # これにより最新の項目が最初に来る
        Limit=1  # 最新の1項目のみを取得
    )
    
    if response['Items']:
        latest_item = response['Items'][0]
        # 項目を削除
        delete_response = table.delete_item(
            Key={
                'Name': latest_item['Name'],
                'Timestamp': latest_item['Timestamp']
            }
        )
        return delete_response
    else:
        return "記録がありません"
```


# 4. UI
日付選択とかアラートとかのデフォルトのUIが嫌いなので、その部分も凝りたいと思って色々を探した。結局使うことにしたのが
* 日付選択UI: Flatpickr (`flatpickr.min.css` と `flatpickr.js`)
* 時間選択UI: ClockPicker (`bootstrap-clockpicker.min.css` と `bootstrap-clockpicker.min.js`)
* アラートUI: SweetAlert2 (`sweetalert2@11` と `sweetalert.min.js`)

たちである。それぞれちょっとだけ説明していく。
全てのUIをimportするために以下のコードをhtmlに書く。
``` html index.html
<!-- for FlatPickr -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<!-- for ClockPicker -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/clockpicker/0.0.7/bootstrap-clockpicker.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/clockpicker/0.0.7/bootstrap-clockpicker.min.js"></script>
<!-- for SweetAlert2 -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
```
これでインポートできる。あとは、それぞれ使うためのJSを書かねばならない。

### 日付選択UI
いいですねえ。こうゆうUIいい！ただ好きなものを入れてく。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3788759/79bdff71-fc26-3e7e-3e5a-cb2206fc7789.png">

``` Javascript flatpickr.js
flatpickr("#datePicker", {
    altInput: true,
    altFormat: "F j, Y",
    dateFormat: "Y-m-d",
});
```

### 時間選択UI
おーーいい！これはすごいいい！使ってみたらわかるけど、ヌルって動く感じがいい！
<div style="display: flex;">
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3788759/25d0c925-2713-360a-4e52-356940fbc2dd.png">
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3788759/de9cb237-05dc-23d8-ec12-981a4d4d7013.png">
</div>

``` Javascript clockpicker.js
$('.clockpicker').clockpicker({
        donetext: '完了',
        twelvehour: false,
        autoclose: true,
        vibrate: true,
        fromnow: 0,
        placement: 'bottom',
        align: 'left'
    });
});

```

### アラートUI
[Sweetalert2](https://sweetalert2.github.io/)ってのを使ってるんだけど、すごい面白いアラート。
詳しくはこのHPを見てほしんだけど、注意しないといけないのが、Sweeralert1とSweetalert2があるらしく、若干ネットにことがってる情報では混在しているので、エラーが出たらちゃんとHPで確認しよう。これに関してはChat GPTに聞いてもうまく直らなかった。
使い方としては、JSの中でアラートを出したいタイミングで、`showToast(message, type)`としてアラートを実行させる。mesageにはアラートに表示したいメッセージをtypeにはどのアラートの種類で出したいをかく。

結構いけてるアラートだから全HPのアラートこれにするべきだと思う。

```Javascript alert.js
function showToast(message, type) {
    let icon;
    let title;
    switch (type) {
        case 'success':
            icon = 'success';
            title = 'Success';
            break;
        case 'error':
            icon = 'error';
            title = 'Error';
            break;
        case 'info':
            icon = 'info';
            title = 'Information';
            break;
        default:
              icon = 'error';
              title = 'error';
              break;
    }
    swal({
        title: title,
        text: message,
        icon: icon,
        buttons: {
            confirm: {
                text: 'OK',
                className: 'btn btn-success'
            }
        },
        buttonsStyling: true,
        timer: 5000,
        timerProgressBar: true
    });
  }
  ```

# 5. 最後に
流石にこれでできたでしょ！っと思って、メンバーに見せたところ導入された！やったー！
またここで、一つ問題が、実はチームの半分が日本にいない。Timestampとかの管理が若干めんどくさくなるかもと思った。特にその２で実装したカレンダー形式の表示の時差をどこに合わせるかとか問題が出てきた。
ただ、あんまり機能として重要じゃないかつ流石に勤怠管理作るのに飽きてきたので、また気が向いたらこの部分も対処していこうと思ふ。

次回：何しようかな