From 205126ec0a702caf868c0b54ae7febcc736d4ba9 Mon Sep 17 00:00:00 2001 From: tmakkrhr-ctrl Date: Fri, 27 Feb 2026 13:32:23 +0900 Subject: [PATCH] =?UTF-8?q?=E7=B7=A8=E9=9B=86=E6=A9=9F=E8=83=BD=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0=E3=81=97=E3=81=BE=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- team_product/src/public/app.js | 99 ++++++++++++++++++++++++------ team_product/src/public/index.html | 4 +- team_product/src/public/style.css | 44 +++++++++++++ team_product/src/server.js | 39 ++++++++++-- 4 files changed, 160 insertions(+), 26 deletions(-) diff --git a/team_product/src/public/app.js b/team_product/src/public/app.js index 91e0dfb..528c567 100644 --- a/team_product/src/public/app.js +++ b/team_product/src/public/app.js @@ -4,7 +4,7 @@ * このコードはブラウザ上で動作します。 * ログはブラウザのF12(開発者ツール)に表示されます。 * - * 現在の機能: Create(追加)、Read(一覧表示) + * 現在の機能: Create(追加)、Read(一覧表示)、Update(編集) */ // ===================================================== @@ -52,27 +52,27 @@ async function loadItems() { * アイテムを追加 * * IPO: - * - Input: テキストボックスのタイトル + * - Input: テキストボックスのname * - Process: サーバーにPOSTリクエスト → DB保存 * - Output: 一覧を再読み込み */ async function addItem() { - const title = titleInput.value.trim() + const name = titleInput.value.trim() // 入力チェック(Client側のバリデーション) - if (!title) { - alert('タイトルを入力してください') + if (!name) { + alert('nameを入力してください') return } - console.log('[CLIENT] アイテムを追加:', title) + console.log('[CLIENT] アイテムを追加:', name) try { // サーバーにPOSTリクエスト const response = await fetch('/api/items', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ title }) + body: JSON.stringify({ title: name }) }) if (!response.ok) { @@ -94,9 +94,47 @@ async function addItem() { } // ===================================================== -// ここに新しい機能(Delete, Updateなど)を追加していこう! +// Update機能 // ===================================================== +/** + * アイテム名(name)を更新 + * + * IPO: + * - Input: 編集ボタン押下後に入力されたname + * - Process: PUTリクエストでServerへ編集要求 → DB更新 + * - Output: 更新後一覧を再描画 + */ +async function updateItem(id, nameInput) { + const name = nameInput.value.trim() + + if (!name) { + alert('nameを入力してください') + return + } + + console.log('[CLIENT] アイテムを更新:', id, name) + + try { + const response = await fetch(`/api/items/${id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name }) + }) + + if (!response.ok) { + const error = await response.json() + throw new Error(error.error) + } + + await loadItems() + console.log('[CLIENT] 更新完了') + } catch (error) { + console.error('[CLIENT] エラー:', error) + alert('更新に失敗しました: ' + error.message) + } +} + // ===================================================== // 画面描画関数 // ===================================================== @@ -115,22 +153,43 @@ function renderItems(items) { items.forEach(item => { const li = document.createElement('li') li.className = 'item' - li.innerHTML = ` - ${escapeHtml(item.title)} - ` + + const nameText = document.createElement('span') + nameText.className = 'item-title' + nameText.textContent = item.title + + const actions = document.createElement('div') + actions.className = 'item-actions' + + const editButton = document.createElement('button') + editButton.className = 'item-button edit-button' + editButton.textContent = '編集' + editButton.addEventListener('click', () => { + const nameInput = document.createElement('input') + nameInput.type = 'text' + nameInput.className = 'edit-input' + nameInput.value = item.title + + const doneButton = document.createElement('button') + doneButton.className = 'item-button done-button' + doneButton.textContent = '完了' + doneButton.addEventListener('click', () => { + updateItem(item.id, nameInput) + }) + + actions.innerHTML = '' + li.replaceChild(nameInput, nameText) + actions.appendChild(doneButton) + nameInput.focus() + }) + + actions.appendChild(editButton) + li.appendChild(nameText) + li.appendChild(actions) itemList.appendChild(li) }) } -/** - * HTMLエスケープ(XSS対策) - */ -function escapeHtml(text) { - const div = document.createElement('div') - div.textContent = text - return div.innerHTML -} - // ===================================================== // イベントリスナー // ===================================================== diff --git a/team_product/src/public/index.html b/team_product/src/public/index.html index 5b52dc6..358c487 100644 --- a/team_product/src/public/index.html +++ b/team_product/src/public/index.html @@ -19,7 +19,7 @@

Base App

@@ -32,7 +32,7 @@

アイテム一覧

- アイテムがありません。上のフォームから追加してください。 + データがありません。上のフォームから追加してください。

diff --git a/team_product/src/public/style.css b/team_product/src/public/style.css index 203a4e8..a6bab48 100644 --- a/team_product/src/public/style.css +++ b/team_product/src/public/style.css @@ -110,6 +110,50 @@ header .subtitle { .item-title { font-size: 1rem; color: #2c3e50; + flex: 1; +} + +.item-actions { + display: flex; + gap: 8px; +} + +.item-button { + padding: 8px 12px; + border: none; + border-radius: 6px; + color: white; + cursor: pointer; + font-size: 0.85rem; +} + +.edit-button { + background-color: #7f8c8d; +} + +.edit-button:hover { + background-color: #6c7a7d; +} + +.done-button { + background-color: #27ae60; +} + +.done-button:hover { + background-color: #219150; +} + +.edit-input { + flex: 1; + padding: 8px 10px; + border: 2px solid #ddd; + border-radius: 6px; + font-size: 1rem; +} + +.edit-input:focus { + outline: none; + border-color: #3498db; } /* 空メッセージ */ diff --git a/team_product/src/server.js b/team_product/src/server.js index f9d2a84..e4b4222 100644 --- a/team_product/src/server.js +++ b/team_product/src/server.js @@ -4,7 +4,7 @@ * このファイルはサーバー側の処理を担当します。 * ログはVSCodeのターミナルに表示されます。 * - * 現在の機能: Create(追加)、Read(一覧表示) + * 現在の機能: Create(追加)、Read(一覧表示)、Update(編集) */ const express = require('express') @@ -78,9 +78,40 @@ app.post('/api/items', async (req, res) => { } }) -// ===================================================== -// ここに新しいAPI(Delete, Updateなど)を追加していこう! -// ===================================================== +/** + * PUT /api/items/:id - アイテム編集 + * + * IPO: + * - Input: クライアントからidとnameを受け取る + * - Process: 対象idのtitleをDB上で更新 + * - Output: 更新したアイテムをJSONで返す + */ +app.put('/api/items/:id', async (req, res) => { + try { + const id = Number(req.params.id) + const { name, title } = req.body + const nextName = (name ?? title ?? '').trim() + + if (!Number.isInteger(id) || id <= 0) { + return res.status(400).json({ error: '不正なidです' }) + } + + if (!nextName) { + return res.status(400).json({ error: 'nameを入力してください' }) + } + + const item = await prisma.item.update({ + where: { id }, + data: { title: nextName } + }) + + console.log('[SERVER] アイテムを更新:', item) + res.json(item) + } catch (error) { + console.error('[SERVER] エラー:', error) + res.status(500).json({ error: 'アイテム更新に失敗しました' }) + } +}) // ===================================================== // サーバー起動