【Webアプリ開発】買い物リストアプリ

# はじめに
勉強も兼ねて、今回は買い物メモのアプリを作成することにしました。
もともと忘れっぽい性格なので、よく何を買うんだっけ、、、？
スーパーに行ってから考えるタイプなので、自作アプリで乗り越えられたらベストかなと。
シンプルに見やすく、継続して利用できそうなアプリにしたいと思います。

# 要件定義
**想定場面**
買い物に行く際、アプリを使用し、購入タスクをつける。

**機能**
リストへの追記・修正・削除
カテゴリ分け（食品・日用品など、自分で追加・編集可能）
検索機能

**技術選定**
フロントエンド：vue.js
バックエンド：python、flask
開発環境：VsCode

## 学習内容（～開発環境作成）
:black_nib:　**git**
```powershell:cmd
git init #gitを始めるおまじない
```
:black_nib:　.gitignoreファイルの作成
```powershell:.gitignore
node_modules/ #node_modules フォルダを無視
*.log #ログファイルを無視
.env #環境設定ファイルを無視
```


:black_nib:　**flask**
・Pythonで書かれたフレームワークのこと
・最小限のコア機能やプラグイン、拡張モジュールを搭載し、必要な機能のみを追加できる
・シンプルで使いやすく、初心者向けのフレームワーク→覚えることが少ない

:black_nib:　**Node.js**
・javascriptのサーバー側実行環境
```powershell:cmd
node -v #インストールされているかのチェック
```

:black_nib:　**vue.js インストール時の選択項目**
・typescript：（よくわかりませんでした）
・JSX　support：JSX(javascript XML)→javascript内にHTMLなど別言語を組み込むかどうか
・vue Router：SPA(single page app)にするかどうかで使用、ページ移動をするかのようにURLを書き換える機能がある
・Pinia：公式状態管理アプリ
・Vitest：（よくわかりませんでした）
・EsLint：誤った構文のチェックをしてくれる
・Prettier：javascriptの自動フォーマッターで、インデント修正などをしてくれる
→今回は**EsLint、Prettier**のみ採用:star:

:black_nib:　**npm**
npm=Node Package Manager
pnpm=performance NPM（javascriptのパッケージ管理ツール）
```powershell:cmd
npm sreate vue@latest #Vueプロジェクトの作成
npm install #パッケージのインストール
npm run dev #開発サーバーを起動
```

## コードの中身
```vue:App.vue
<template>
  <div class="flex flex-col min-h-screen">
    
    <!-- ✅ ヘッダー -->
    <header class="bg-green-200 text-gray-800 p-4 flex justify-between items-center">
      <div class="flex items-center space-x-2">
        <span class="text-xl">🛒</span>
        <h1 class="text-xl font-bold">Shopping-ListApp</h1>
      </div>
      <div>{{ currentDate }}</div>
    </header>

    <!-- ✅ 本体は左右2カラム -->
    <div class="flex flex-1">
      
      <!-- 左サイドバー -->
      <aside class="w-48 bg-gray-800 text-white p-4 space-y-4">
        <button class="w-full bg-gray-700 hover:bg-gray-600 p-2 rounded">Settings</button>
        <button class="w-full bg-gray-700 hover:bg-gray-600 p-2 rounded">Search</button>
        <button class="w-full bg-gray-700 hover:bg-gray-600 p-2 rounded">Add Item</button>
      </aside>

      <!-- 右側：フォームとリスト -->
      <main class="flex-1 p-6 bg-white">
        <!-- フォーム -->
        <div class="mb-4">
          <input
            v-model="newItem.name"
            placeholder="商品名"
            class="border p-2 mr-2"
          />
          <input
            v-model.number="newItem.quantity"
            type="number"
            placeholder="数量"
            class="border p-2 mr-2 w-20"
          />
          <select v-model="newItem.category" class="border p-2 mr-2">
            <option value="Food">Food</option>
            <option value="Household">Household</option>
          </select>
          <button @click="addItem" class="bg-green-500 text-white px-4 py-2 rounded">
            追加
          </button>
        </div>

        <!-- リスト -->
        <h1 class="text-2xl font-bold mb-4">買い物リスト</h1>
        <ul>
          <li
            v-for="item in items"
            :key="item.id"
            class="mb-2 p-2 border rounded flex justify-between items-center"
          >
            <span>🛒 {{ item.name }}（{{ item.quantity }}）</span>
            <button
              @click="deleteItem(item.id)"
              class="bg-red-500 text-white px-2 py-1 rounded ml-4"
            >
              🗑 削除
            </button>
          </li>
        </ul>
      </main>
    </div>
  </div>
</template>

<script>
import api from './api'

export default {
  data() {
    return {
      items: [],
      newItem: {
        name: '',
        quantity: 1,
        category: 'Food'
      },
      currentDate: new Date().toLocaleDateString()
    }
  },
  async created() {
    const response = await api.getItems()
    this.items = response.data
  },
  methods: {
    async addItem() {
      if (!this.newItem.name.trim()) return
      const response = await api.addItem(this.newItem)
      this.items.push(response.data)
      this.newItem = { name: '', quantity: 1, category: 'Food' }
    },
    async deleteItem(id) {
      await api.deleteItem(id)
      this.items = this.items.filter(item => item.id !== id)
    }
  }
}
</script>

<style scoped>
/* くすみグリーンの調整やロゴ用 */
.logo {
  display: block;
  margin: 0 auto 2rem;
}

@media (min-width: 1024px) {
  header {
    display: flex;
    place-items: center;
    padding-right: calc(var(--section-gap) / 2);
  }

  .logo {
    margin: 0 2rem 0 0;
  }

  header .wrapper {
    display: flex;
    place-items: flex-start;
    flex-wrap: wrap;
  }
}
</style>

```

```javascript:api.js
// src/api.js
import axios from 'axios'

const apiClient = axios.create({
  baseURL: 'http://localhost:5000', // FlaskのサーバーURL
  headers: {
    'Content-Type': 'application/json'
  }
})

export default {
  getItems: () => apiClient.get('/items'),
  addItem: (item) => apiClient.post('/items', item),
  deleteItem: (id) => apiClient.delete(`/items/${id}`),
  // 他の関数はあとで追加
}

```

```python:app.py
from flask import Flask, jsonify, request
from flask_cors import CORS

app = Flask(__name__)
CORS(app)  # Vueと通信できるようにする！

# 仮のリストデータ
items = [
    {"id": 1, "name": "Apples", "quantity": 5, "category": "Food", "checked": False},
    {"id": 2, "name": "Laundry Detergent", "quantity": 1, "category": "Household", "checked": False},
]

@app.route("/items", methods=["GET"])
def get_items():
    return jsonify(items)

@app.route("/items", methods=["POST"])
def add_item():
    data = request.json
    new_item = {
        "id": len(items) + 1,
        "name": data["name"],
        "quantity": data["quantity"],
        "category": data["category"],
        "checked": False
    }
    items.append(new_item)
    return jsonify(new_item), 201

@app.route("/items/<int:item_id>", methods=["DELETE"])
def delete_item(item_id):
    global items
    items = [item for item in items if item["id"] != item_id]
    return jsonify({"message": "Item deleted"}), 200

if __name__ == "__main__":
    app.run(debug=True)

```

## 現在のWEBアプリ状況
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4101204/8bda36ea-0dd2-433c-8659-bc61090b5ea2.png)
**flaskとの連携・リストへの追加削除は完了**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4101204/bfc92946-1e31-403e-ba2b-c73b5a5a1b98.png)

**今後改良すること**
・ヘッダーをつけて、横にカートをおしてる女性を白抜きで表示
・日付横に時刻まで入れて今の時間だとタイムセールかも？？みたいな風に
・くすみグリーンの色合いで表示をしたいが、まっしろなまま動かず

## 想定イメージ（chatGPT作成）
![syopping-listapp.image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4101204/eaa11800-271f-48f1-a156-c22c93117399.png)



# おわりに
以前、試しに作ってみたアプリで、なんとなくvueやflaskを利用していましたが、
なぜそのような書き方をするのか、どう修正をすればエラーが解消できるのか等、
今回はかなり細かく調べながら行っています。
今後はデザインをより想定イメージに近づけるように改良を進めていきます。