Compare commits

..

2 Commits

Author SHA1 Message Date
21b87f0756 phase4-2まで 2025-07-29 07:15:11 +09:00
e29e4ff796 編集機能追加 2025-07-28 03:48:47 +09:00

View File

@@ -9,6 +9,8 @@ type Todo = {
completed: boolean; // 完了したかどうかのフラグ。チェックボックスに対応。 completed: boolean; // 完了したかどうかのフラグ。チェックボックスに対応。
}; };
type FilterType = 'all' | 'active' | 'completed'; // フィルターの種類。全て、未完了、完了の3種類。
export default function Home() { export default function Home() {
// Todoリスト本体の状態管理 // Todoリスト本体の状態管理
const [todos, setTodos] = useState<Todo[]>([]); // Todo配列を保持 const [todos, setTodos] = useState<Todo[]>([]); // Todo配列を保持
@@ -25,6 +27,55 @@ export default function Home() {
// 空欄入力時の警告ダイアログ表示状態 // 空欄入力時の警告ダイアログ表示状態
const [showInputAlert, setShowInputAlert] = useState(false); // 空欄警告フラグ const [showInputAlert, setShowInputAlert] = useState(false); // 空欄警告フラグ
// 編集機能のための状態管理
const [editingId, setEditingId] = useState<number | null>(null);
// 編集中のテキスト
const [editText, setEditText] = useState('');
// フィルター状態の管理(全て、未完了、完了)
const [filter, setFilter] = useState<FilterType>('all');
// フィルターに応じたTodoの数をカウント
const activeCount = todos.filter(todo => !todo.completed).length;
const completedCount = todos.filter(todo => todo.completed).length;
// フィルターを変更する関数
const getFilteredTodos = () => { // フィルターに応じてTodoリストを返す関数
switch (filter) { // フィルターの種類に応じて処理を分岐
case 'active': // 未完了のTodoのみを返す
return todos.filter(todo => !todo.completed);
case 'completed': // 完了したTodoのみを返す
return todos.filter(todo => todo.completed);
default: // 'all'の場合は全てのTodoを返す
return todos; // 'all'の場合は全てのTodoを返す
}
};
const startEdit = (todo: Todo) => { // 編集開始関数
setEditingId(todo.id); // 編集対象のIDをセット
setEditText(todo.text); // 編集用のテキストをセット
};
const saveEdit = () => { // 編集内容を保存する関数
if (editText.trim() === '') { // 編集内容が空欄だったら警告ダイアログ表示
alert('TODOが空です'); // 簡易的なアラート表示
return; // 空欄の場合は保存しない
}
const updatedTodos = todos.map(todo => { // Todoリストを更新
if (todo.id === editingId) { // 編集対象のTodoを見つけたら
return { ...todo, text: editText }; // 編集内容で更新
}
return todo; // 他のTodoはそのまま返す
});
setTodos(updatedTodos); // 更新されたTodoリストをセット
setEditingId(null); // 編集モードを終了
setEditText(''); // 編集用テキストをクリア
};
// 新しいTODOを追加する関数 // 新しいTODOを追加する関数
const addTodo = () => { const addTodo = () => {
// 入力が空欄だったら警告ダイアログ表示 // 入力が空欄だったら警告ダイアログ表示
@@ -42,6 +93,11 @@ export default function Home() {
setInputValue(''); // 入力欄をクリア setInputValue(''); // 入力欄をクリア
}; };
const cancelEdit = () => { // 編集キャンセル関数
setEditingId(null); // 編集モードを終了
setEditText(''); // 編集用テキストをクリア
};
// フォーム送信時の処理 // フォーム送信時の処理
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { // フォーム送信イベント const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { // フォーム送信イベント
e.preventDefault(); // ページリロード防止 e.preventDefault(); // ページリロード防止
@@ -107,28 +163,127 @@ export default function Home() {
</button> </button>
</form> </form>
<div className="flex justify-between items-center mb-4 p-4 bg-gray-100 rounded-lg"> {/* フィルターと削除ボタンのコンテナ */}
<div className="flex gap-2"> {/* フィルター用のボタン */}
<button
onClick={() => setFilter('all')}
className={`px-4 py-2 rounded transition-colors ${
filter === 'all'
? 'bg-blue-500 text-white'
: 'bg-white text-gray-700 hover:bg-gray-200'
}`}
>
({todos.length})
</button>
<button
onClick={() => setFilter('active')}
className={`px-4 py-2 rounded transition-colors ${
filter === 'active'
? 'bg-blue-500 text-white'
: 'bg-white text-gray-700 hover:bg-gray-200'
}`}
>
({activeCount})
</button>
<button
onClick={() => setFilter('completed')}
className={`px-4 py-2 rounded transition-colors ${
filter === 'completed'
? 'bg-blue-500 text-white'
: 'bg-white text-gray-700 hover:bg-gray-200'
}`}
>
({completedCount})
</button>
</div>
{/* 完了済みTODOをすべて削除 */}
{completedCount > 0 && (
<button
onClick={() => setTodos(todos.filter(todo => !todo.completed))}
className="text-red-500 hover:text-red-700 px-3 py-1 rounded transition-colors"
>
</button>
)}
</div>
{/* TODOリスト */}
<div className="space-y-2"> <div className="space-y-2">
{todos.map((todo) => ( {getFilteredTodos().length === 0 ? (
<p className="text-gray-500 text-center py-8">
{filter === 'active' && 'すべて完了しました!'}
{filter === 'completed' && '完了したTODOがありません'}
{filter === 'all' && 'TODOがありません'}
</p>
) : (
getFilteredTodos().map((todo) => (
<div key={todo.id} className="flex items-center p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors"> <div key={todo.id} className="flex items-center p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors">
<input <input
type="checkbox" type="checkbox"
checked={todo.completed} // 完了状態を反映 checked={todo.completed}
onChange={() => toggleTodo(todo.id)} // チェックボックス変更時に完了状態をトグル onChange={() => toggleTodo(todo.id)}
className="mr-3 h-5 w-5 cursor-pointer" className="mr-3 h-5 w-5 cursor-pointer"
/> />
<span className={`flex-1 ${todo.completed ? 'line-through text-gray-500' : 'text-gray-800'}`}> {editingId === todo.id ? (
// 編集モード
<input
type="text"
value={editText}
onChange={(e) => setEditText(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') saveEdit();
if (e.key === 'Escape') cancelEdit();
}}
className="flex-1 border border-blue-500 p-2 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
autoFocus
/>
) : (
// 通常モード
<span
onClick={() => startEdit(todo)}
className={`flex-1 cursor-pointer p-2 rounded hover:bg-gray-200 transition-colors ${
todo.completed ? 'line-through text-gray-500' : 'text-gray-800'
}`}
>
{todo.text} {todo.text}
</span> </span>
)}
{editingId === todo.id ? (
// 編集モードのボタン
<div className="flex gap-2 ml-2">
<button <button
onClick={() => handleDeleteClick(todo.id)} // 削除ボタン押下時に確認ダイアログ表示 onClick={saveEdit}
className="text-red-500 hover:text-red-700 ml-2 px-2 py-1 rounded transition-colors" className="text-green-600 hover:text-green-800 px-2 py-1 rounded transition-colors"
>
</button>
<button
onClick={cancelEdit}
className="text-gray-600 hover:text-gray-800 px-2 py-1 rounded transition-colors"
>
</button>
</div>
) : (
// 通常モードのボタン
<div className="flex gap-2 ml-2">
<button
onClick={() => startEdit(todo)}
className="text-blue-500 hover:text-blue-700 px-2 py-1 rounded transition-colors"
>
</button>
<button
onClick={() => handleDeleteClick(todo.id)}
className="text-red-500 hover:text-red-700 px-2 py-1 rounded transition-colors"
> >
</button> </button>
</div> </div>
))} )}
</div>
))
)}
</div> </div>
{/* 確認ダイアログ */} {/* 確認ダイアログ */}