スーパーでの「来週なにつくる？」に答えを出すツールをReactで作った

# なぜ作ったか
週1回の買い物時にできるだけ必要なものは買い揃えたい。
そのために何を作るか決める必要があるが、大体スーパーについてから考えてしまうのである程度自動化したい。

# メイン機能
準備したリストの中からランダムな重複なしリストを取得するアプリケーション

入力
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224501/c13827ad-de27-85c7-a37c-adecd21ac35a.jpeg" width=300 /> 

結果取得
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224501/c2ee63f8-8590-4334-a1b9-5a3ca84e6f53.jpeg" width=300 /> 



# 工夫点
- 1アイテムだけチェンジする機能 -> 最近作った、材料がないなどでチェンジするための機能。全部チェンジするとなかなか理想の結果が得られないことがある。
- 結果をLINEに出力するボタン(`SHARE BY LINE` ) -> 結果の永続化、シェア
- 入力したリストはブラウザに保存 -> 入力時間削減

LINEシェア結果
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224501/b1d93418-c081-3911-6697-c1a9a043f2b7.jpeg" width=300 /> 

# 利用技術
- 言語: TypeScript
- フレームワーク: Next.js
- UI: MUI
- CD: github actionsでGithub Pagesにdeploy

# ソースコードと主要部分の実装
リポジトリ: https://github.com/yoshikipom/dinnerslot-web

ページは `index.tsx` のみ。
ページ内で`ListInput.tsx`(リスト入力) と `Slot.tsx`(結果生成) を呼び出し、タブで出し分ける。
```
$ tree src 
src
├── components
│   ├── Footer.tsx
│   ├── LineShareButton.tsx
│   ├── ListInput.tsx
│   └── Slot.tsx
├── model
│   └── Food.ts
└── pages
    ├── _app.tsx
    └── index.tsx
```

結果生成
- 入力されたリストをコピー
- ランダムに並び替え
- 欲しい数だけ前から切り取る
```typescript:Slot.tsx
    const slot = () => {
        const copiedFoodList = foodList.concat();
        for (let i = copiedFoodList.length - 1; i > 0; i--) {
            const r = Math.floor(Math.random() * (i + 1));
            const tmp = copiedFoodList[i];
            copiedFoodList[i] = copiedFoodList[r];
            copiedFoodList[r] = tmp;
        }
        updateResult(copiedFoodList.slice(0, Number(count)));
    }
```

一点チェンジ
- 入力されたリストをコピー
- 選ばれているアイテムを除去
  - 候補がなければ終了
- ランダムで一点選択
- 上書き
```typescript:Slot.tsx
    const slotOneItem = (index: number) => {
        const copiedFoodList: Food[] = foodList.concat();
        const remainingList: Food[] = copiedFoodList.filter((food) => !results.includes(food))
    
        if (remainingList.length == 0) {
            return;
        }

        const r = Math.floor(Math.random() * (remainingList.length));
        results[index] = remainingList[r];
        updateResult(results.slice(0, Number(count)));
    }
```

LINE Shareボタン
- 「LINE URLスキーム」を利用
  - ref https://developers.line.biz/ja/docs/line-login/using-line-url-scheme/
- 値はpropsで渡す
```typescript:LineShareButton.tsx
const LineShareButton = (props: any) => {
    return (
        <Button sx={{ mx: 2, my: 2 }} variant="outlined" href={`https://line.me/R/share?text=${encodeURI(props.message)}`}>
            Share by LINE
        </Button>
    );
};
```

入力リスト保存
- localStorage利用
- 取得は初回呼び出しのみ
- 書き込みは入力(`foodListInputRaw`)が変更が変更するたび
```typescript:ListInput.tsx
    useEffect(() => {
        const foodListInputRaw = window.localStorage.getItem(STORAGE_KEY);
        if (foodListInputRaw !== null) {
            setFoodListInputRaw(foodListInputRaw);
        }
    }, []);

    useEffect(() => {
        const strList = foodListInputRaw.split('\n').filter(val => val);
        let index = 1;
        const newFoodList: Food[] = strList.map(name => ({ id: index++, name }));
        setFoodList(newFoodList);
        window.localStorage.setItem(STORAGE_KEY, foodListInputRaw);
    }, [foodListInputRaw, setFoodList]);
```

# 感想
localStorage、LINEシェアは初めて試したが簡単に使えてよかった。
技術の復習としてバックエンドも作りたかったが、要件的に不要になってしまい残念。
自分のほしいアプリケーションを作ると勉強したい技術が使えるとは限らないので、勉強目的なら勉強用のアプリケーションを作った方が無難そう。