やる気サポーター

https://github.com/kota-0827/yaruki-supporter

GitHub

CSS

JavaScript

Node.js

HTML

今のやる気に合わせて、やるべきタスクを1件だけ提案するアプリ

志賀広拓

推しアイデア

一覧を見せずに1件だけ大きく出すこと。やる気が出ない日ほどタスク一覧は開きたくないので、選択肢を隠すこと自体を機能にしました。24時間で消える「供養」も、残り時間を予告してから消すことで、責められる感じにならないよう工夫しています。

作った背景

やる気が出ない日ほどタスク管理アプリを開きたくなくなる、という自分の経験から作りました。全部並ぶ一覧を見ると量に圧倒されて閉じてしまう。漏れなく管理するより、今の自分でも着手できる1件を差し出すほうが助かると思ったのがきっかけです。

推し技術

24時間で消す処理を定期実行(cron)に頼らず、一覧を読む直前に必ず一括更新する方式にしたこと。Renderの無料プランはサーバーが止まるので、常駐処理を前提にできません。読むときに最新化すれば、何度止まっても結果が変わりません。

プロジェクト詳細

■ 何をつくったか 「やる気サポーター」は、今の自分のやる気レベル(1〜5)を選ぶと、その日の自分でも着手できるタスクを1件だけ提案してくれるタスク管理アプリです。

タスクには「ヤバ度(緊急度)1〜5」と「必要やる気1〜5」を設定します。今のやる気以上の気力を要求するタスクは候補から外れ、残った中でヤバ度が一番高いものを1件だけ大きく表示します。気が乗らなければ「別のを出す」で次の候補に切り替えられます。

もうひとつの軸が「24時間供養」です。登録から24時間経った未完了タスクは自動的に一覧から消え、「供養リスト」に移ります。タスクが無限に積み上がってアプリを開くのが嫌になる状態を、仕組みで防いでいます。完了するとヤバ度×10ポイントと、ランダムなご褒美メッセージがもらえます。

■ どんなデータを保持しているか PostgreSQLに3つのテーブルを持っています。

・users … 名前、メール、パスワードのハッシュ、合計ポイント ・categories … 講義名などのカテゴリ(ユーザーごと) ・tasks … タイトル、ヤバ度、必要やる気、状態(active/completed/expired)、作成日時、期限

ポイントは3つです。

  1. status を3値に固定した(文字列だと表記ゆれで「登録したのに出てこない」不具合になる)
  2. 供養の基準を deadline ではなく created_at にした(期限は任意入力なので空のことがあるが、作成日時は必ず入るので処理が必ず動く)
  3. パスワードは平文で持たず、bcryptでハッシュ化して保存している

■ どうやって作ったか(構成) 講義で扱った3層構造にそっています。

・フロントエンド: React + Vite + Tailwind CSS + Framer Motion ・バックエンド: Node.js + Express + TypeScript + Prisma ・データベース: PostgreSQL ・認証: express-session(Cookieセッション) ・デプロイ: Render(render.yamlのBlueprintでDB・API・静的サイトを一括構成)

フロントは表示と操作だけを担当し、判断のロジックは持たせていません。「やる気で絞る」「ヤバ度順に並べる」「24時間で供養する」といったルールはすべてバックエンドのservices層に置き、controllersはHTTPの入口(入力検証とステータスコードの決定)に徹しています。この分け方のおかげで、ルールを変えたいときに触る場所が1か所で済みました。

■ 最もこだわったところ 一覧ではなく「1件だけ」を主役にしたことです。最初は一覧画面をメインにするつもりでしたが、それだと既存のアプリと同じで、「やる気が出ない日に開きたくない」という最初の問題が解決しません。トップではおすすめの1件だけを大きく出し、一覧はボタンを押さないと開かないようにしました。

もうひとつは、やる気レベルと画面の色を連動させたことです。やる気1(青)から5(赤)へ色相を等間隔に動かしていて、選んだレベルに応じてカードの枠線・ボタン・進捗バーの色が一斉に変わります。数値だけだと「3を選んだ」という事実しか残りませんが、画面全体の色が変わると「今日はこういう日だ」という感覚が残ります。

■ 学び 一番苦労したのはロジックではなくデプロイまわりでした。ローカルではフロントもAPIも localhost なのでCookieは何も考えなくても送られますが、本番はドメインが分かれるため SameSite=None と Secure を指定しないとログイン状態が保てません。CORSの許可オリジンもURLが1文字違うと弾かれます。「ローカルで動く」と「デプロイして動く」は別物だと実感しました。

また、無料プランのサーバーは止まるという制約から設計が決まった部分があります。供養処理をcronに頼らず「読む直前に必ず最新化する」方式にしたのも、セッションをメモリではなくDBに置いたのもそのためです。制約を知って初めて設計が決まる、というのが今回一番の学びでした。

志賀広拓

@b6444b19f0854525