基礎から学ぶC#入門 90日コース | アプリを作りながら学ぶ - Day 77:Search

Web APP 90日で身につけるC#
スポンサーリンク
スポンサーリンク

Day 77 のゴールと全体像

Day 77 では、タスク管理アプリに Search(検索)・Filter(絞り込み)機能を追加していきます。 ここまでで、タスクの追加・一覧・編集・削除といった基本操作は一通り揃いました。 しかし、タスクが増えてくると、一覧画面にずらっと並んだタスクの中から、

  • 「今日やるべきタスクだけ見たい」
  • 「未完了のタスクだけ知りたい」
  • 「特定のキーワードを含むタスクを探したい」

といったニーズが自然と出てきます。

そこで登場するのが 検索・フィルター機能です。 今日は、初心者の方でも「なるほど、こうやって絞り込むのか」と腑に落ちるように、 ステップバイステップで実装していきます。

検索・フィルター機能の考え方を整理する

「検索」と「フィルター」の違い

まず、ざっくり言葉の整理をしておきます。

  • 検索(Search) キーワードなどを使って、 「条件に合うものだけを探す」イメージです。 例:タイトルに「レポート」を含むタスクを探す
  • フィルター(Filter) 一覧の中から、 「特定の条件に合うものだけを表示する」イメージです。 例:未完了のタスクだけ表示する、優先度が高いものだけ表示する

実装としてはどちらも「条件に合うタスクだけを取り出す」という点で同じですが、 ユーザーにとっての使い方のイメージが少し違います。

今日は、

  • キーワード検索(タイトル・詳細に含まれる文字列で探す)
  • 状態フィルター(完了/未完了)
  • 優先度フィルター(高・中・低)

あたりを中心に作っていきます。

TaskRepository に検索・フィルターメソッドを追加する

キーワード検索メソッド

まずは、タイトルや詳細にキーワードを含むタスクを探すメソッドを作ります。

using System;
using System.Collections.Generic;
using System.Linq;

// タスク一覧を管理するクラス
class TaskRepository
{
    private readonly List<TaskItem> _tasks = new List<TaskItem>();
    private int _nextId = 1;

    public TaskItem Add(string title, string description, int priority, DateTime? dueDate)
    {
        var task = new TaskItem
        {
            Id = _nextId++,
            Title = title,
            Description = description,
            Priority = priority,
            DueDate = dueDate,
            IsCompleted = false,
            CreatedAt = DateTime.Now
        };

        _tasks.Add(task);
        return task;
    }

    public IEnumerable<TaskItem> GetAll()
    {
        return _tasks;
    }

    public TaskItem FindById(int id)
    {
        return _tasks.FirstOrDefault(t => t.Id == id);
    }

    // キーワード検索:タイトルまたは詳細にキーワードを含むタスクを返す
    public IEnumerable<TaskItem> SearchByKeyword(string keyword)
    {
        if (string.IsNullOrWhiteSpace(keyword))
        {
            // キーワードが空なら、全件返すことにしてもよい
            return _tasks;
        }

        keyword = keyword.ToLower(); // 大文字・小文字を区別しないようにする

        return _tasks.Where(t =>
            (!string.IsNullOrEmpty(t.Title) && t.Title.ToLower().Contains(keyword)) ||
            (!string.IsNullOrEmpty(t.Description) && t.Description.ToLower().Contains(keyword))
        );
    }

    // 完了状態でフィルターする
    public IEnumerable<TaskItem> FilterByCompletion(bool isCompleted)
    {
        return _tasks.Where(t => t.IsCompleted == isCompleted);
    }

    // 優先度でフィルターする
    public IEnumerable<TaskItem> FilterByPriority(int priority)
    {
        return _tasks.Where(t => t.Priority == priority);
    }
}
C#

重要ポイントの解説

  • SearchByKeyword のキーワードチェック 空文字やスペースだけの入力の場合は、 「全件返す」という挙動にしておくと扱いやすいです。
  • 大文字・小文字を区別しない検索 ToLower() を使って、 タスク側・キーワード側の両方を小文字に揃えてから Contains しています。 これにより、「Report」と「report」の違いを気にせず検索できます。
  • タイトルと詳細の両方を対象にしていること タイトルだけでなく、詳細(Description)も検索対象にすることで、 「メモに書いたキーワード」でもヒットするようになります。
  • フィルターメソッドのシンプルさ FilterByCompletionFilterByPriority は、 それぞれ Where で条件に合うものだけを返す、 とてもシンプルなメソッドです。

検索結果を表示するための共通メソッドを作る

検索やフィルターの結果を表示するとき、 一覧表示とほぼ同じロジックを使います。

そこで、「タスクのコレクションを表示する共通メソッド」を作っておくと便利です。

void ShowTaskList(IEnumerable<TaskItem> tasks, string header)
{
    Console.WriteLine(header);

    var list = tasks.ToList();

    if (!list.Any())
    {
        Console.WriteLine("条件に合うタスクがありませんでした。");
        return;
    }

    foreach (var t in list)
    {
        string status = t.IsCompleted ? "[完了]" : "[未完了]";
        string priorityText = t.Priority switch
        {
            1 => "高",
            2 => "中",
            3 => "低",
            _ => "不明"
        };
        string dueText = t.DueDate.HasValue
            ? t.DueDate.Value.ToString("yyyy-MM-dd")
            : "期限なし";

        Console.WriteLine($"ID: {t.Id}");
        Console.WriteLine($"  状態   : {status}");
        Console.WriteLine($"  優先度 : {priorityText}");
        Console.WriteLine($"  期限   : {dueText}");
        Console.WriteLine($"  タイトル: {t.Title}");
        Console.WriteLine($"  詳細   : {t.Description}");
        Console.WriteLine();
    }
}
C#

共通メソッドにするメリット

  • 一覧表示・検索結果表示・フィルター結果表示で 同じ見た目を簡単に再利用できる
  • 表示形式を変えたいときに、 このメソッドだけ直せば全部に反映される

こういう「共通化」は、 アプリが少しずつ大きくなってきたときに、 コードの見通しを良くしてくれる大事な考え方です。

キーワード検索機能を作る

キーワード検索の流れ

  1. キーワードを入力してもらう
  2. SearchByKeyword で該当タスクを取得する
  3. 共通表示メソッドで結果を表示する

この流れをコードにしてみます。

void SearchTaskByKeyword()
{
    Console.WriteLine("=== キーワード検索 ===");
    Console.Write("検索したいキーワードを入力してください: ");

    string keyword = Console.ReadLine();

    // リポジトリに検索を依頼
    var result = repository.SearchByKeyword(keyword);

    // 結果を表示
    ShowTaskList(result, "=== 検索結果 ===");
}
C#

ここで意識していること

  • キーワードが空でも SearchByKeyword 側で全件返すようにしているので、 「とりあえず Enter を押したら全部出てくる」という挙動も自然です。
  • 表示部分は ShowTaskList に任せることで、 検索機能のコードがとてもスッキリしています。

完了状態フィルター機能を作る

未完了/完了だけを見たいとき

「終わっていないタスクだけ見たい」 「完了したタスクだけ確認したい」

というニーズは、タスク管理ではよくあります。

これをコードにすると、次のようになります。

void FilterTasksByCompletion()
{
    Console.WriteLine("=== 完了状態で絞り込み ===");
    Console.WriteLine("1: 未完了のタスクのみ表示");
    Console.WriteLine("2: 完了したタスクのみ表示");
    Console.Write("選択してください: ");

    string input = Console.ReadLine();

    bool isCompleted;

    if (input == "1")
    {
        isCompleted = false; // 未完了
    }
    else if (input == "2")
    {
        isCompleted = true;  // 完了
    }
    else
    {
        Console.WriteLine("不正な入力です。");
        return;
    }

    var result = repository.FilterByCompletion(isCompleted);

    string header = isCompleted
        ? "=== 完了したタスク一覧 ==="
        : "=== 未完了のタスク一覧 ===";

    ShowTaskList(result, header);
}
C#

ポイント

  • ユーザーには「1/2」で選んでもらい、 内部では bool に変換して処理しています。
  • 見出し(header)を状態に応じて変えることで、 「今何を見ているのか」が分かりやすくなっています。

優先度フィルター機能を作る

高・中・低で絞り込む

「優先度が高いタスクだけ見たい」というのも、 タスク管理ではよくあるパターンです。

void FilterTasksByPriority()
{
    Console.WriteLine("=== 優先度で絞り込み ===");
    Console.WriteLine("1: 優先度 高 のタスク");
    Console.WriteLine("2: 優先度 中 のタスク");
    Console.WriteLine("3: 優先度 低 のタスク");
    Console.Write("選択してください: ");

    string input = Console.ReadLine();

    if (!int.TryParse(input, out int priority))
    {
        Console.WriteLine("数値で入力してください。");
        return;
    }

    if (priority != 1 && priority != 2 && priority != 3)
    {
        Console.WriteLine("優先度は 1, 2, 3 のいずれかを選択してください。");
        return;
    }

    var result = repository.FilterByPriority(priority);

    string priorityText = priority switch
    {
        1 => "高",
        2 => "中",
        3 => "低",
        _ => "不明"
    };

    string header = $"=== 優先度 {priorityText} のタスク一覧 ===";

    ShowTaskList(result, header);
}
C#

バリデーションの大切さ

  • int.TryParse で数値チェック
  • 1/2/3 以外はエラー扱い

こうした「入力チェック」は、 アプリを安全に、そして気持ちよく使ってもらうための大事な要素です。

メニューに Search / Filter 機能を組み込む

コンソールメニューの拡張

Day 73〜76 のメニューに、検索・フィルター機能を追加していきます。

void Run()
{
    while (true)
    {
        Console.WriteLine("=== タスク管理アプリ ===");
        Console.WriteLine("1: タスク追加");
        Console.WriteLine("2: タスク一覧");
        Console.WriteLine("3: タスク編集");
        Console.WriteLine("4: 完了状態の変更");
        Console.WriteLine("5: タスク削除");
        Console.WriteLine("6: キーワード検索");
        Console.WriteLine("7: 完了状態で絞り込み");
        Console.WriteLine("8: 優先度で絞り込み");
        Console.WriteLine("0: 終了");
        Console.Write("選択してください: ");

        string input = Console.ReadLine();

        switch (input)
        {
            case "1":
                CreateTask();
                break;
            case "2":
                ShowTasks();
                break;
            case "3":
                EditTask();
                break;
            case "4":
                ToggleComplete();
                break;
            case "5":
                DeleteTask();
                break;
            case "6":
                SearchTaskByKeyword();
                break;
            case "7":
                FilterTasksByCompletion();
                break;
            case "8":
                FilterTasksByPriority();
                break;
            case "0":
                Console.WriteLine("終了します。");
                return;
            default:
                Console.WriteLine("不正な入力です。");
                break;
        }

        Console.WriteLine();
    }
}
C#

これで、タスク管理アプリは、

  • 追加(Create)
  • 一覧(Read)
  • 編集・完了変更(Update)
  • 削除(Delete)
  • 検索・フィルター(Search / Filter)

という、かなり本格的な機能セットを持つようになります。

Day 77 用ミニテンプレート(まとめて貼れるサンプル)

長くなりますが、「Day 73〜76 のコードに検索・フィルターを足した形」のサンプルです。 必要なところだけ切り出して使っていただいても大丈夫です。

using System;
using System.Collections.Generic;
using System.Linq;

// タスクを表すクラス
class TaskItem
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public int Priority { get; set; }
    public DateTime? DueDate { get; set; }
    public bool IsCompleted { get; set; }
    public DateTime CreatedAt { get; set; }
}

// タスク一覧を管理するクラス
class TaskRepository
{
    private readonly List<TaskItem> _tasks = new List<TaskItem>();
    private int _nextId = 1;

    public TaskItem Add(string title, string description, int priority, DateTime? dueDate)
    {
        var task = new TaskItem
        {
            Id = _nextId++,
            Title = title,
            Description = description,
            Priority = priority,
            DueDate = dueDate,
            IsCompleted = false,
            CreatedAt = DateTime.Now
        };

        _tasks.Add(task);
        return task;
    }

    public IEnumerable<TaskItem> GetAll()
    {
        return _tasks;
    }

    public TaskItem FindById(int id)
    {
        return _tasks.FirstOrDefault(t => t.Id == id);
    }

    public bool Remove(int id)
    {
        var task = FindById(id);
        if (task == null)
        {
            return false;
        }

        _tasks.Remove(task);
        return true;
    }

    // キーワード検索
    public IEnumerable<TaskItem> SearchByKeyword(string keyword)
    {
        if (string.IsNullOrWhiteSpace(keyword))
        {
            return _tasks;
        }

        keyword = keyword.ToLower();

        return _tasks.Where(t =>
            (!string.IsNullOrEmpty(t.Title) && t.Title.ToLower().Contains(keyword)) ||
            (!string.IsNullOrEmpty(t.Description) && t.Description.ToLower().Contains(keyword))
        );
    }

    // 完了状態フィルター
    public IEnumerable<TaskItem> FilterByCompletion(bool isCompleted)
    {
        return _tasks.Where(t => t.IsCompleted == isCompleted);
    }

    // 優先度フィルター
    public IEnumerable<TaskItem> FilterByPriority(int priority)
    {
        return _tasks.Where(t => t.Priority == priority);
    }
}

class Program
{
    static TaskRepository repository = new TaskRepository();

    static void Main()
    {
        Run();
    }

    static void Run()
    {
        while (true)
        {
            Console.WriteLine("=== タスク管理アプリ ===");
            Console.WriteLine("1: タスク追加");
            Console.WriteLine("2: タスク一覧");
            Console.WriteLine("3: タスク編集");
            Console.WriteLine("4: 完了状態の変更");
            Console.WriteLine("5: タスク削除");
            Console.WriteLine("6: キーワード検索");
            Console.WriteLine("7: 完了状態で絞り込み");
            Console.WriteLine("8: 優先度で絞り込み");
            Console.WriteLine("0: 終了");
            Console.Write("選択してください: ");

            string input = Console.ReadLine();

            switch (input)
            {
                case "1":
                    CreateTask();
                    break;
                case "2":
                    ShowTasks();
                    break;
                case "3":
                    EditTask();
                    break;
                case "4":
                    ToggleComplete();
                    break;
                case "5":
                    DeleteTask();
                    break;
                case "6":
                    SearchTaskByKeyword();
                    break;
                case "7":
                    FilterTasksByCompletion();
                    break;
                case "8":
                    FilterTasksByPriority();
                    break;
                case "0":
                    Console.WriteLine("終了します。");
                    return;
                default:
                    Console.WriteLine("不正な入力です。");
                    break;
            }

            Console.WriteLine();
        }
    }

    // 共通表示メソッド
    static void ShowTaskList(IEnumerable<TaskItem> tasks, string header)
    {
        Console.WriteLine(header);

        var list = tasks.ToList();

        if (!list.Any())
        {
            Console.WriteLine("条件に合うタスクがありませんでした。");
            return;
        }

        foreach (var t in list)
        {
            string status = t.IsCompleted ? "[完了]" : "[未完了]";
            string priorityText = t.Priority switch
            {
                1 => "高",
                2 => "中",
                3 => "低",
                _ => "不明"
            };
            string dueText = t.DueDate.HasValue
                ? t.DueDate.Value.ToString("yyyy-MM-dd")
                : "期限なし";

            Console.WriteLine($"ID: {t.Id}");
            Console.WriteLine($"  状態   : {status}");
            Console.WriteLine($"  優先度 : {priorityText}");
            Console.WriteLine($"  期限   : {dueText}");
            Console.WriteLine($"  タイトル: {t.Title}");
            Console.WriteLine($"  詳細   : {t.Description}");
            Console.WriteLine();
        }
    }

    static void CreateTask()
    {
        Console.WriteLine("=== タスク追加 ===");

        string title = ReadTitle();
        string description = ReadDescription();
        int priority = ReadPriority();
        DateTime? dueDate = ReadDueDate();

        TaskItem task = repository.Add(title, description, priority, dueDate);

        Console.WriteLine($"タスクを追加しました。ID: {task.Id}");
    }

    static string ReadTitle()
    {
        while (true)
        {
            Console.Write("タスク名を入力してください: ");
            string title = Console.ReadLine();

            if (!string.IsNullOrWhiteSpace(title))
            {
                return title;
            }

            Console.WriteLine("タスク名は必須です。空では登録できません。");
        }
    }

    static string ReadDescription()
    {
        Console.Write("詳細(メモ)を入力してください(空でも構いません): ");
        string description = Console.ReadLine();
        return description ?? string.Empty;
    }

    static int ReadPriority()
    {
        while (true)
        {
            Console.Write("優先度を入力してください (1: 高, 2: 中, 3: 低): ");
            string text = Console.ReadLine();

            if (int.TryParse(text, out int value))
            {
                if (value == 1 || value == 2 || value == 3)
                {
                    return value;
                }
            }

            Console.WriteLine("優先度は 1, 2, 3 のいずれかで入力してください。");
        }
    }

    static DateTime? ReadDueDate()
    {
        while (true)
        {
            Console.Write("期限を入力してください (yyyy-MM-dd、未入力なら期限なし): ");
            string text = Console.ReadLine();

            if (string.IsNullOrWhiteSpace(text))
            {
                return null;
            }

            if (DateTime.TryParse(text, out DateTime date))
            {
                return date;
            }

            Console.WriteLine("日付の形式が正しくありません。例: 2026-09-10 のように入力してください。");
        }
    }

    static void ShowTasks()
    {
        var tasks = repository.GetAll();
        ShowTaskList(tasks, "=== タスク一覧 ===");
    }

    static void EditTask()
    {
        Console.WriteLine("=== タスク編集 ===");

        Console.Write("編集したいタスクのIDを入力してください: ");
        string idText = Console.ReadLine();

        if (!int.TryParse(idText, out int id))
        {
            Console.WriteLine("IDは数値で入力してください。");
            return;
        }

        TaskItem task = repository.FindById(id);
        if (task == null)
        {
            Console.WriteLine("指定されたIDのタスクが見つかりませんでした。");
            return;
        }

        Console.WriteLine($"現在のタイトル: {task.Title}");
        Console.Write("新しいタイトル(変更しない場合は空のまま): ");
        string newTitle = Console.ReadLine();
        if (!string.IsNullOrWhiteSpace(newTitle))
        {
            task.Title = newTitle;
        }

        Console.WriteLine($"現在の詳細: {task.Description}");
        Console.Write("新しい詳細(変更しない場合は空のまま): ");
        string newDescription = Console.ReadLine();
        if (!string.IsNullOrWhiteSpace(newDescription))
        {
            task.Description = newDescription;
        }

        Console.WriteLine($"現在の優先度: {task.Priority} (1: 高, 2: 中, 3: 低)");
        Console.Write("新しい優先度 (1/2/3、変更しない場合は空のまま): ");
        string newPriorityText = Console.ReadLine();
        if (!string.IsNullOrWhiteSpace(newPriorityText))
        {
            if (int.TryParse(newPriorityText, out int newPriority))
            {
                if (newPriority == 1 || newPriority == 2 || newPriority == 3)
                {
                    task.Priority = newPriority;
                }
                else
                {
                    Console.WriteLine("優先度は 1, 2, 3 のいずれかである必要があります。変更は反映されませんでした。");
                }
            }
            else
            {
                Console.WriteLine("優先度は数値で入力してください。変更は反映されませんでした。");
            }
        }

        Console.WriteLine($"現在の期限: {(task.DueDate.HasValue ? task.DueDate.Value.ToString("yyyy-MM-dd") : "期限なし")}");
        Console.Write("新しい期限 (yyyy-MM-dd、空なら期限なしに変更): ");
        string newDueText = Console.ReadLine();
        if (string.IsNullOrWhiteSpace(newDueText))
        {
            task.DueDate = null;
        }
        else
        {
            if (DateTime.TryParse(newDueText, out DateTime newDue))
            {
                task.DueDate = newDue;
            }
            else
            {
                Console.WriteLine("日付の形式が正しくありません。期限は変更されませんでした。");
            }
        }

        Console.WriteLine("タスクを更新しました。");
    }

    static void ToggleComplete()
    {
        Console.WriteLine("=== 完了状態の変更 ===");

        Console.Write("完了状態を切り替えたいタスクのIDを入力してください: ");
        string idText = Console.ReadLine();

        if (!int.TryParse(idText, out int id))
        {
            Console.WriteLine("IDは数値で入力してください。");
            return;
        }

        TaskItem task = repository.FindById(id);
        if (task == null)
        {
            Console.WriteLine("指定されたIDのタスクが見つかりませんでした。");
            return;
        }

        Console.WriteLine($"現在の状態: {(task.IsCompleted ? "完了" : "未完了")}");
        Console.Write("状態を切り替えますか? (y/n): ");
        string answer = Console.ReadLine();

        if (answer?.ToLower() != "y")
        {
            Console.WriteLine("完了状態の変更をキャンセルしました。");
            return;
        }

        task.IsCompleted = !task.IsCompleted;

        Console.WriteLine($"完了状態を変更しました。現在の状態: {(task.IsCompleted ? "完了" : "未完了")}");
    }

    static void DeleteTask()
    {
        Console.WriteLine("=== タスク削除 ===");

        Console.Write("削除したいタスクのIDを入力してください: ");
        string idText = Console.ReadLine();

        if (!int.TryParse(idText, out int id))
        {
            Console.WriteLine("IDは数値で入力してください。");
            return;
        }

        TaskItem task = repository.FindById(id);
        if (task == null)
        {
            Console.WriteLine("指定されたIDのタスクが見つかりませんでした。");
            return;
        }

        Console.WriteLine("削除対象のタスク:");
        string status = task.IsCompleted ? "[完了]" : "[未完了]";
        string priorityText = task.Priority switch
        {
            1 => "高",
            2 => "中",
            3 => "低",
            _ => "不明"
        };
        string dueText = task.DueDate.HasValue
            ? task.DueDate.Value.ToString("yyyy-MM-dd")
            : "期限なし";

        Console.WriteLine($"ID: {task.Id}");
        Console.WriteLine($"  状態   : {status}");
        Console.WriteLine($"  優先度 : {priorityText}");
        Console.WriteLine($"  期限   : {dueText}");
        Console.WriteLine($"  タイトル: {task.Title}");
        Console.WriteLine($"  詳細   : {task.Description}");
        Console.WriteLine();

        Console.Write("本当に削除しますか? (y/n): ");
        string answer = Console.ReadLine();

        if (answer?.ToLower() != "y")
        {
            Console.WriteLine("削除をキャンセルしました。");
            return;
        }

        bool removed = repository.Remove(id);

        if (removed)
        {
            Console.WriteLine("タスクを削除しました。");
        }
        else
        {
            Console.WriteLine("削除に失敗しました。");
        }
    }

    static void SearchTaskByKeyword()
    {
        Console.WriteLine("=== キーワード検索 ===");
        Console.Write("検索したいキーワードを入力してください: ");

        string keyword = Console.ReadLine();

        var result = repository.SearchByKeyword(keyword);

        ShowTaskList(result, "=== 検索結果 ===");
    }

    static void FilterTasksByCompletion()
    {
        Console.WriteLine("=== 完了状態で絞り込み ===");
        Console.WriteLine("1: 未完了のタスクのみ表示");
        Console.WriteLine("2: 完了したタスクのみ表示");
        Console.Write("選択してください: ");

        string input = Console.ReadLine();

        bool isCompleted;

        if (input == "1")
        {
            isCompleted = false;
        }
        else if (input == "2")
        {
            isCompleted = true;
        }
        else
        {
            Console.WriteLine("不正な入力です。");
            return;
        }

        var result = repository.FilterByCompletion(isCompleted);

        string header = isCompleted
            ? "=== 完了したタスク一覧 ==="
            : "=== 未完了のタスク一覧 ===";

        ShowTaskList(result, header);
    }

    static void FilterTasksByPriority()
    {
        Console.WriteLine("=== 優先度で絞り込み ===");
        Console.WriteLine("1: 優先度 高 のタスク");
        Console.WriteLine("2: 優先度 中 のタスク");
        Console.WriteLine("3: 優先度 低 のタスク");
        Console.Write("選択してください: ");

        string input = Console.ReadLine();

        if (!int.TryParse(input, out int priority))
        {
            Console.WriteLine("数値で入力してください。");
            return;
        }

        if (priority != 1 && priority != 2 && priority != 3)
        {
            Console.WriteLine("優先度は 1, 2, 3 のいずれかを選択してください。");
            return;
        }

        var result = repository.FilterByPriority(priority);

        string priorityText = priority switch
        {
            1 => "高",
            2 => "中",
            3 => "低",
            _ => "不明"
        };

        string header = $"=== 優先度 {priorityText} のタスク一覧 ===";

        ShowTaskList(result, header);
    }
}
C#

Day 77 のまとめ

Day 77 では、タスク管理アプリに 検索・フィルター機能を追加しました。

  • タイトル・詳細にキーワードを含むタスクを探す SearchByKeyword
  • 完了状態で絞り込む FilterByCompletion
  • 優先度で絞り込む FilterByPriority
  • 結果表示を共通化する ShowTaskList
  • メニューから「検索」「絞り込み」を選べるように拡張

これで、タスクが増えてきても、 「今見たいタスクだけ」に視点を絞ることができるようになります。

検索・フィルターは、 ただの“おまけ機能”ではなく、 「情報の海から、自分にとって大事なものだけをすくい上げるための道具」です。

ぜひ、いくつかタスクを登録してから、 キーワード検索や状態フィルターを試してみてください。 「あ、ちゃんと探せるって気持ちいいな」と感じてもらえたら、 それはもう立派な“自分の手で作ったアプリ”です。

タイトルとURLをコピーしました