ローカルストレージでCRUD. ID/ ストレージ・モーダルウィンドウ

## 重要ポイント 

```js:
 ローカルストレージ使う時、IDとワンセット

 IDは、htmlでは隠しとくのが基本
  (編集画面のみ、IDつける)
  <input type="hidden" id="editId">

 保存の仕組み : テーブルにdata 表示してから保存

 テーブル表示と保存は、処理を同時にはしない
 
 保存とテーブル表示は、いつも、処理別々
```

### ローカルストレージ　作成手順

```php:
 1.ローカルストレージ使うための、初期設定
 2.data読み込み関数　作成
 3.data保存関数　作成
 4.data取得関数 作成
 5.新規追加　変数 仕込む
 6.data編集　変数仕込む
 7.削除　変数 仕込む

 8.テーブル読み込み　変数 仕込む
   
 9.編集モーダル開く　変数 仕込む

 10.入力formでdata送信ボタン押したら、ボタンの色を変える
 　　に、変数仕込む

           
    code化すると
        ↓
  
  1.ローカルストレージ使うための、初期設定 場所 : 最初と最後
    → 名前"studentData"でローカルストレージ使うよ
  const LOCAL_STORAGE_KEY = 'studentData';
  
  → ローカルストレージにはたくさんdataを入れる。
  たくさんdataを入れるには配列がいるから、配列を用意し、
  配列を初期化
    let studentData = [];

    ↓
    
  2.data読み込み関数　作成
  作った配列を、ローカルストレージにセット
  const storedData = localStorage.getItem(LOCAL_STORAGE_KEY);

  dataはそのままだとJSで扱えないから、JSで扱える形にする
  studentData = JSON.parse(storedData);

  ローカルストレージの長さが増えてたらデータが新しく追加された、
  ってことだから、ローカルストレージの長さを数える
   if (studentData.length > 0) {

  data追加時、最新IDでdata追加できるよう、処理
  nextId = Math.max(...studentData.map(item => item.id)) + 1;

  テーブルの中身を毎回からにし、入力欄でdataを新規作成にするには
  テーブル初期化しないといけないから、テーブル初期化
  renderTable(); 
    ↓

 3.data保存関数　作成
 JSで扱えるようにしたdataを、画面に表示できるよう 設定
    function saveDataToLocalStorage() {
    localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(studentData));
  }
    ↓

  5.新規追加　変数 仕込む
  idの初期値はローカルストレージに保存されてて、
  最初のidはローカルストレージから引っ張って来ないと
  使えないから、 push使う
    studentData.push({ ...data, id: nextId++ }); // IDを更新

    data保存とテーブル初期化関数 呼び出し
      saveDataToLocalStorage();
      renderTable();

    data を作り終えたら、入力formはからにしないと
    data を新規作成できないから、入力formをからに
      clearFormInputs();
    ↓
  6.data編集　変数仕込む
  index でIDを指定し、指定したID 探す
  findIndex : dataがローカルストレージの何番目に入ってるか
   探す(何番目に入ってるか、要素番号調べないとdataを正しく
   編集できないから)
  const index = studentData.findIndex(item => item.id === id);

  探したIDがあったら、dataを更新するから index !== -1
      if (index !== -1) {

  探したIDで、ローカルストレージにdataを代入し、更新
        studentData[index] = { ...studentData[index], ...updatedData };

  data保存、テーブル初期化
    saveDataToLocalStorage();
        renderTable();
    ↓
  7.削除　変数 仕込む
  削除するID指定 ; itemし、ID削除されてるか確認
  studentData = studentData.filter(item => item.id !== id);

  data 保存、テーブル初期化
  saveDataToLocalStorage();
      renderTable();
    
          ↓
  8.テーブル空欄に　変数 仕込む
  テーブルにdata新規追加を新しくやる時、
  テーブルの全項目を空にしないといけなくて、
  テーブルの全項目を空にするにはforeach使わないとできないから
  やってる
    function renderTable() {
    tableBody.innerHTML = ''; // テーブルの中身をクリア
    studentData.forEach(data => addTableRow(data)); // 全てのデータを再描画
  }
    ↓
   9.編集モーダル開く　変数 仕込む
   すでにあるidを探し、探したidをもとにdata編集
    const dataToEdit = studentData.find(item => item.id === id);
    ↓

  10.入力formでdata送信ボタン押したら、ボタンの色を変える
    　に、変数仕込む
     ボタンの色を変える時、ローカルストレージのdataを
     読み込んでからボタンの色を変えないと、data 読み込みなしで
     ボタンの色を変えたら エラーになるらしい
      oadDataFromLocalStorage();
      addButton.addEventListener('click', () => {
      addButton.classList.add('clicked-button'); 

```

## ID使い、 ローカルストレージで保存 仕組み

```js:
  1.データ初期設定
   → IDの初期値　設定

   2.ID最大値を探し、今のid読み込み
   → IDの最大値を探し、IDを常に＋１しておく
   → dataが新規作成されたら、IDは常に最大値になるから
   → IDは数字だから、数字を扱う時、maoでいつも処理

   3.入力data取得 
   → idは呼び出すのみ

   4.新規追加
   id増やす
   
   5.data編集　変数仕込む
   同じID探す
   ID探してからじゃないと、編集dataをストレージから
   常に引っ張って来れないから、使うのがfind

   7.削除
   IDは消したら無くなるから、なくなったID探す
   IDがなく存在しなくなったことを確認してから
   出ないと、dataを安全に消せないから、調べてる"filter"

   8.テーブル表示 : idセット
   
   編集ボタン押した / 削除ボタン押した / 編集modal開いた
   / 編集モーダル開き、保存
   → id渡す "id渡してないと、data反映できない”から


          code化すると
             ↓
    
    1.データ初期設定
    let nextId = 1;

         ↓
    2.ID最大値を探し、今のid読み込み
    nextId = Math.max(...studentData.map(item => item.id)) + 1;
    
             ↓
   3.入力data取得 ：id 読み込み
    id: nextId,
    
    ↓
   4.新規追加: id増やす
   studentData.push({ ...data, id: nextId++ });
    ↓

   5.data編集 : 同じID探す
   findIndex : dataがローカルストレージの何番目に入ってるか
   探す(何番目に入ってるか、要素番号調べないとdataを正しく
   編集できないから)
   const index = studentData.findIndex(item => item.id === id);
    ↓

  7.削除 : 消したID探す
  studentData = studentData.filter(item => item.id !== id);
    ↓
  8.テーブル表示 : idセット
  row.setAttribute('data-id', data.id);

    ↓
  編集ボタン押したら、idセット
  editButton.addEventListener('click', () => {
      openEditModal(data.id); // 編集モーダルを開く
    });
    ↓

  削除ボタン押したら、idセット
  deleteButton.addEventListener('click', () => {
      deleteData(data.id); // データ削除関数を呼び出す
    });

    ↓
  編集ボタン押してモーダルウィンドウ開く時、idセットし、
  id探す
   function openEditModal(id) {
   const dataToEdit = studentData.find(item => item.id === id);
    ↓
  
 編集画面で、id表示
 editIdInput.value = dataToEdit.id;
    ↓

 保存先を指定し、保存(保存先 : 編集画面のid)
 const id = parseInt(editIdInput.value);
    ↓

 バリデーションさせた後、dataを保存するから、
 バリデーション後は、もう一度idを呼び出して保存
 updateData(id, updatedData);
```


### ボタンの色を変える　仕組み

```js:
 ボタンの色を変えるクラスをcssに設定
 ボタンの初期値セット
 dataを読み込んで
 追加ボタン押したら、ボタンの色変える
 入力formのdataを取得し、表示
 バリデーション無効に


        code化すると
            ↓

  ボタンの色を変えるクラスをcssに設定
   #addButon.clicked-button {
        background-color: #28a745; /* クリック後は別の色（例: 緑） */
    }
           ↓
　ボタンの初期値セット
document.addEventListener('DOMContentLoaded', () => {
const addButton = document.getElementById('addButon');

                ↓
   dataを読み込んで
 loadDataFromLocalStorage();

                ↓
  追加ボタン押したら、ボタンの色変える
  addButton.addEventListener('click', () => {
    addButton.classList.add('clicked-button'); 

               ↓
    入力formのdataを取得し、表示
    → ボタンの色を変えた後も、入力formのdataを表示しとか
      なきゃいけないから
    const formData = getFormData();
    
             ↓
   ・バリデーション無効に
    バリデーションを効かせないために、バリデーション時
    表示するdivを、表示させないようにする
    clearErrors();

                  ↓
    バリデーションさせてなかったら、処理続ける
    if (!validateForm(formData)) return;

    data新規追加（テーブルにdata表示の、前の処理)
    createData(formData); 
  });
```


## 編集・削除ボタン　追加 仕組み

```js:
  やり方: テーブル表示のすぐ後

  # 仕込む場所 : テーブル表示する変数
  function addTableRow(data) {
  html テーブル
  html 削除ボタン
  html 編集ボタン
  tableBody.appendChild(row);
  
      ↓
 これの、すぐ後に編集と削除ボタン追加
 ボタンが押されたらイベントリスナー設定


       code化すると
          ↓
  編集と削除ボタン追加(querySelector)
  const editButton = row.querySelector('.edit-button');
    const deleteButton = row.querySelector('.delete-button');

         ↓
  ボタンが押されたら、イベントリスナー設定
  editButton.addEventListener('click', () => {
      openEditModal(data.id); // 編集モーダルを開く
    });
  deleteButton.addEventListener('click', () => {
      deleteData(data.id); // データ削除関数を呼び出す
    });
  } 
```


## モーダルウィンドウ　作成仕組み

```js:
 ばつボタン/ モーダルウィンドウ,
 キャンセルボタン / 保存ボタンのスタイル設定
 
 編集画面　idはテーブル表示では、隠しておく
  
 モーダルウィンドウ全体のdiv id 読み込み
 
 ばつボタン 表示させるため、❌ボタンのクラス表示させる
 
 [編集画面]保存ボタン　ID読み込み
 
 [編集画面]キャンセルボタン　ID読み込み
 
 [編集画面] id / 電話番号/ 年齢 / 名前 / Eメール　ID読み込み
 
 編集関数作成し、ローカルストレージにdata保存
 モーダルを閉じる関数だけ　仕込む
  → 編集画面開いた時、編集せず
    モーダル閉じたい場合もあるから

 編集ボタンが押されたら、モーダルを開く関数　仕込む

 編集モーダル開く関数　作成
 
 編集モーダル閉じる関数　作成
  → 画面を消すから、display none

 ボタンの色を変える関数の後に、ばつボタンと
 キャンセルボタンでモーダルを閉じる処理　作成

 モーダルの保存ボタンで、data保存する関数　作成

 data編集関数呼び出し、data更新

      code化
        ↓
 モーダルウィンドウのスタイル設定
        ↓    
変種画面　idはテーブル表示では、隠しておく
  <input type="hidden" id="editId">

        ↓
  モーダルウィンドウ全体のdiv id 読み込み
  document.addEventListener('DOMContentLoaded', () => {
   const editModal = document.getElementById('editModal');
        ↓
  ばつボタン 表示させるため、❌ボタンのクラス表示させる
  const closeButton = document.querySelector('.close-button');
        ↓
  [編集画面]保存ボタン　ID読み込み
  const saveEditButton = document.getElementById('saveEditButton');
        ↓
        
  [編集画面]キャンセルボタン　ID読み込み
  const cancelEditButton = document.getElementById('cancelEditButton');
  
        ↓
  [編集画面] id / 電話番号/ 年齢 / 名前 / Eメール　ID読み込み
  const editIdInput = document.getElementById('editId');
  const editNameInput = document.getElementById('editName');
  const editAgeInput = document.getElementById('editAge');
  const editEmailInput = document.getElementById('editEmail');
  const editPhoneInput = document.getElementById('editPhone');

        ↓
 編集関数作成
 async function updateData(id, updatedData) {
    try {
      // ダミーAPIはPUT/PATCHしても実際のデータは更新しないため、擬似的な成功とします
      const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, { // ダミーAPI
        method: 'PUT', // または 'PATCH'
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(updatedData)
      });
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      const result = await response.json();
      console.log('APIでデータが更新されました:', result);

      // 成功したらローカルデータとLocalStorageを更新
      const index = studentData.findIndex(item => item.id === id);
      if (index !== -1) {
        studentData[index] = { ...studentData[index], ...updatedData };
        saveDataToLocalStorage();
        renderTable();
      }

 編集関数に、モーダル閉じる関数　仕込む
  → モーダル開いても、何も処理しないで画面閉じたい時もあるから
  }
      closeEditModal(); // 編集成功後にモーダルを閉じる
    } catch (error) {
      console.error('データ更新に失敗しました:', error);
      alert('データ更新に失敗しました。');
    }
    
        ↓
  編集ボタンが押されたら、モーダルを開く関数　仕込む
  editButton.addEventListener('click', () => {
      openEditModal(data.id); // 編集モーダルを開く
    });
    
        ↓
  編集モーダル開く関数　作成
  function openEditModal(id) {
    const dataToEdit = studentData.find(item => item.id === id);
    if (dataToEdit) {
      editIdInput.value = dataToEdit.id;
      editNameInput.value = dataToEdit.name;
      editAgeInput.value = dataToEdit.age;
      editEmailInput.value = dataToEdit.email;
      editPhoneInput.value = dataToEdit.phone;
      editModal.style.display = 'flex'; // flexにして中央寄せを有効に
    }
        ↓
  編集モーダル閉じる関数　作成
  function closeEditModal() {
    editModal.style.display = 'none';
  }
        ↓
  ボタンの色を変える関数の後に、ばつボタンと
 キャンセルボタンでモーダルを閉じる処理　作成
  closeButton.addEventListener('click', closeEditModal); → ばつボタン
  cancelEditButton.addEventListener('click', closeEditModal);  →  キャンセルボタン

        ↓
 モーダルの保存ボタンが押されたら、イベントリスナーで
 dataを画面に表示関数、作成
  saveEditButton.addEventListener('click', () => {
    const id = parseInt(editIdInput.value);
    const updatedData = {
      name: editNameInput.value.trim(),
      age: parseInt(editAgeInput.value.trim()),
      email: editEmailInput.value.trim(),
      phone: editPhoneInput.value.trim()
    };
        ↓
    data保存関数[編集のみの]呼び出し、data更新
      updateData(id, updatedData); 
```

### code全文

```js:
!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>テーブル表示練習</title>
  <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
  <style>
    body {
        font-family: Arial, sans-serif;
        margin: 20px;
    }
    .form-group { margin-bottom: 10px; }
    .label { display: inline-block; width: 80px; }
    .error-message { display:none; color:red; margin-left:5px; }

    /* テーブル全体の基本スタイル */
    table {
        width: 100%;
        border-collapse: collapse;
        margin-top: 20px;
    }
    table, th, td { border:1px solid #000; border-collapse:collapse; padding:5px; }

    /* テーブルヘッダーの枠線を太くする */
    th {
        border: 3px solid #000; /* ヘッダーの枠線を太く */
    }

    /* ボタンの初期スタイル */
    #addButon {
        padding: 8px 15px;
        font-size: 16px;
        cursor: pointer;
        background-color: #007bff; /* 初期の色（青） */
        color: white;
        border: none;
        border-radius: 4px;
        transition: background-color 0.3s ease; /* 色の変化を滑らかに */
    }

    /* ボタンがクリックされた後のスタイル */
    #addButon.clicked-button {
        background-color: #28a745; /* クリック後は別の色（例: 緑） */
    }

    /* 削除・編集ボタンのスタイル */
    .action-button {
        padding: 5px 10px;
        margin-left: 5px; /* ボタン間のスペース */
        border: none;
        border-radius: 3px;
        cursor: pointer;
        font-size: 13px;
        color: white;
    }

    .delete-button {
        background-color: #dc3545; /* 赤色 */
    }

    .edit-button {
        background-color: #ffc107; /* 黄色 */
        color: black; /* 黄色には黒文字が見やすい */
    }

    /* --- モーダルウィンドウのスタイル --- */
    .modal {
      display: none; /* 初期状態では非表示 */
      position: fixed; /* 画面に固定 */
      z-index: 1; /* 最前面に表示 */
      left: 0;
      top: 0;
      width: 100%; /* 全幅 */
      height: 100%; /* 全高 */
      overflow: auto; /* スクロール可能にする */
      background-color: rgba(0,0,0,0.4); /* 半透明の黒背景 */
      justify-content: center;
      align-items: center;
    }

    .modal-content {
      background-color: #fefefe;
      margin: auto; /* 中央寄せ */
      padding: 20px;
      border: 1px solid #888;
      width: 80%; /* 幅 */
      max-width: 500px; /* 最大幅 */
      border-radius: 8px;
      box-shadow: 0 4px 8px rgba(0,0,0,0.2);
      position: relative;
    }

    .close-button {
      color: #aaa;
      float: right;
      font-size: 28px;
      font-weight: bold;
    }

    .close-button:hover,
    .close-button:focus {
      color: black;
      text-decoration: none;
      cursor: pointer;
    }

    .modal-buttons {
      margin-top: 20px;
      text-align: right;
    }
    .modal-buttons button {
      padding: 8px 15px;
      margin-left: 10px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    .modal-buttons .save-button {
      background-color: #28a745;
      color: white;
    }
    .modal-buttons .cancel-button {
      background-color: #6c757d;
      color: white;
    }
  </style>
</head>
<body>

<form id="userForm">
  <div class="form-group">
    <label class="label" for="studentName">名前</label>
    <input type="text" id="studentName">
    <div id="nameError" class="error-message">名前を入力してください</div>
  </div>
  <div class="form-group">
    <label class="label" for="studentAge">年齢</label>
    <input type="number" id="studentAge">
  </div>
  <div class="form-group">
    <label class="label" for="studentEmail">メール</label>
    <input type="email" id="studentEmail">
  </div>
  <div class="form-group">
    <label class="label" for="studentnumber">電話</label>
    <input type="text" id="studentnumber">
  </div>
  <button type="button" id="addButon">追加</button>
</form>

<hr>

<table id="studentTable">
  <thead>
    <tr><th>ID</th><th>名前</th><th>年齢</th><th>メール</th><th>電話</th><th>操作</th></tr> </thead>
  <tbody></tbody>
</table>

<div id="editModal" class="modal">
  <div class="modal-content">
    <span class="close-button">&times;</span>
    <h2>データ編集</h2>
    <input type="hidden" id="editId">
    <div class="form-group">
      <label class="label" for="editName">名前</label>
      <input type="text" id="editName">
    </div>
    <div class="form-group">
      <label class="label" for="editAge">年齢</label>
      <input type="number" id="editAge">
    </div>
    <div class="form-group">
      <label class="label" for="editEmail">メール</label>
      <input type="email" id="editEmail">
    </div>
    <div class="form-group">
      <label class="label" for="editPhone">電話</label>
      <input type="text" id="editPhone">
    </div>
    <div class="modal-buttons">
      <button class="save-button" id="saveEditButton">保存</button>
      <button class="cancel-button" id="cancelEditButton">キャンセル</button>
    </div>
  </div>
</div>

<script>
document.addEventListener('DOMContentLoaded', () => {
  const LOCAL_STORAGE_KEY = 'studentData'; // ローカルストレージのキー

  const addButton = document.getElementById('addButon');
  const nameInput = document.getElementById('studentName');
  const ageInput = document.getElementById('studentAge');
  const emailInput = document.getElementById('studentEmail');
  const phoneInput = document.getElementById('studentnumber');
  const nameError = document.getElementById('nameError');
  const tableBody = document.querySelector('#studentTable tbody');

  // モーダル関連要素
  const editModal = document.getElementById('editModal');
  const closeButton = document.querySelector('.close-button');
  const saveEditButton = document.getElementById('saveEditButton');
  const cancelEditButton = document.getElementById('cancelEditButton');
  const editIdInput = document.getElementById('editId');
  const editNameInput = document.getElementById('editName');
  const editAgeInput = document.getElementById('editAge');
  const editEmailInput = document.getElementById('editEmail');
  const editPhoneInput = document.getElementById('editPhone');

  let studentData = []; // アプリケーションのデータ配列
  let nextId = 1; // 次に割り当てるID

  // --- ローカルストレージとデータ管理 ---

  // ローカルストレージからデータを読み込む
  function loadDataFromLocalStorage() {
    const storedData = localStorage.getItem(LOCAL_STORAGE_KEY);
    if (storedData) {
      studentData = JSON.parse(storedData);
      // 既存データから最大のIDを見つけてnextIdを設定
      if (studentData.length > 0) {
        nextId = Math.max(...studentData.map(item => item.id)) + 1;
      }
      renderTable(); // テーブルを初期表示
    }
  }

  // ローカルストレージにデータを保存する
  function saveDataToLocalStorage() {
    localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(studentData));
  }

  // --- 関数定義 ---

  // 入力データ取得
  function getFormData() {
    return {
      id: nextId, // 現在のnextIdを割り当てる
      name: nameInput.value.trim(),
      age: parseInt(ageInput.value.trim()) || 0, // 数値に変換、無効な場合は0
      email: emailInput.value.trim(),
      phone: phoneInput.value.trim()
    };
  }

  // バリデーション
  function validateForm(data) {
    if (data.name === '') {
      nameError.style.display = 'inline';
      setTimeout(() => { nameError.style.display = 'none'; }, 2000);
      return false;
    }
    if (isNaN(data.age) || data.age <= 0) {
      alert('正しい年齢を入力してください'); // ここは残しておきます
      return false;
    }
    return true;
  }

  // バリデーションエラー初期化
  function clearErrors() {
    nameError.style.display = 'none';
  }

  // --- CRUD操作（Fetch APIを使用） ---

  // データ新規追加 (Create)
  async function createData(data) {
    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/users', { // ダミーAPI
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data)
      });
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      const result = await response.json();
      console.log('APIから新規データが追加されました:', result);
      
      // 成功したらローカルデータとLocalStorageを更新
      studentData.push({ ...data, id: nextId++ }); // IDを更新
      saveDataToLocalStorage();
      renderTable();
      clearFormInputs();
    } catch (error) {
      console.error('データ追加に失敗しました:', error);
      alert('データ追加に失敗しました。');
    }
  }

  // データ編集 (Update)
  async function updateData(id, updatedData) {
    try {
      // ダミーAPIはPUT/PATCHしても実際のデータは更新しないため、擬似的な成功とします
      const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, { // ダミーAPI
        method: 'PUT', // または 'PATCH'
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(updatedData)
      });
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      const result = await response.json();
      console.log('APIでデータが更新されました:', result);

      // 成功したらローカルデータとLocalStorageを更新
      const index = studentData.findIndex(item => item.id === id);
      if (index !== -1) {
        studentData[index] = { ...studentData[index], ...updatedData };
        saveDataToLocalStorage();
        renderTable();
      }
      closeEditModal(); // 編集成功後にモーダルを閉じる
    } catch (error) {
      console.error('データ更新に失敗しました:', error);
      alert('データ更新に失敗しました。');
    }
  }

  // データ削除 (Delete)
  async function deleteData(id) {
    const confirmDelete = confirm('本当に削除しますか？');
    if (!confirmDelete) return;

    try {
      const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, { // ダミーAPI
        method: 'DELETE'
      });
      if (!response.ok) {
        // DELETEリクエストは200 OKまたは204 No Contentを返すことが一般的
        // ここでは200番台以外のエラーを捕捉
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      console.log(`APIからID ${id} のデータが削除されました。`);

      // 成功したらローカルデータとLocalStorageを更新
      studentData = studentData.filter(item => item.id !== id);
      saveDataToLocalStorage();
      renderTable();
    } catch (error) {
      console.error('データ削除に失敗しました:', error);
      alert('データ削除に失敗しました。');
    }
  }

  // --- UI操作 ---

  // テーブルに行追加（編集・削除ボタン付き）
  function addTableRow(data) {
    const row = document.createElement('tr');
    row.setAttribute('data-id', data.id); // 行にデータのIDを設定
    row.innerHTML = `
      <td>${data.id}</td> <td>${data.name}</td>
      <td>${data.age}</td>
      <td>${data.email}</td>
      <td>${data.phone}</td>
      <td>
        <button class="action-button edit-button">編集</button>
        <button class="action-button delete-button">削除</button>
      </td>
    `;
    tableBody.appendChild(row);

    // 追加したばかりのボタンにイベントリスナーを設定
    const editButton = row.querySelector('.edit-button');
    const deleteButton = row.querySelector('.delete-button');

    editButton.addEventListener('click', () => {
      openEditModal(data.id); // 編集モーダルを開く
    });

    deleteButton.addEventListener('click', () => {
      deleteData(data.id); // データ削除関数を呼び出す
    });
  }

  // 入力欄クリア
  function clearFormInputs() {
    nameInput.value = '';
    ageInput.value = '';
    emailInput.value = '';
    phoneInput.value = '';
  }

  // テーブルを再描画する関数
  function renderTable() {
    tableBody.innerHTML = ''; // テーブルの中身をクリア
    studentData.forEach(data => addTableRow(data)); // 全てのデータを再描画
  }

  // 編集モーダルを開く関数
  function openEditModal(id) {
    const dataToEdit = studentData.find(item => item.id === id);
    if (dataToEdit) {
      editIdInput.value = dataToEdit.id;
      editNameInput.value = dataToEdit.name;
      editAgeInput.value = dataToEdit.age;
      editEmailInput.value = dataToEdit.email;
      editPhoneInput.value = dataToEdit.phone;
      editModal.style.display = 'flex'; // flexにして中央寄せを有効に
    }
  }

  // 編集モーダルを閉じる関数
  function closeEditModal() {
    editModal.style.display = 'none';
  }

  // --- イベントリスナー ---

  // ページロード時にローカルストレージからデータを読み込み、テーブルを表示
  loadDataFromLocalStorage();

  // 「追加」ボタンのクリックイベント
  addButton.addEventListener('click', () => {
    // ボタンの色を青に変更 (今回はクリック後に緑に変える例)
    addButton.classList.add('clicked-button'); 

    const formData = getFormData();
    clearErrors();

    if (!validateForm(formData)) return;

    createData(formData); // データ追加関数を呼び出す
  });

  // モーダルを閉じるボタン
  closeButton.addEventListener('click', closeEditModal);
  cancelEditButton.addEventListener('click', closeEditModal);

  // モーダル内の保存ボタン
  saveEditButton.addEventListener('click', () => {
    const id = parseInt(editIdInput.value);
    const updatedData = {
      name: editNameInput.value.trim(),
      age: parseInt(editAgeInput.value.trim()),
      email: editEmailInput.value.trim(),
      phone: editPhoneInput.value.trim()
    };

    if (!validateForm(updatedData)) return; // 編集フォームのバリデーションも既存関数を利用

    updateData(id, updatedData); // データ更新関数を呼び出す
  });
});
</script>

</body>
</html>
```