見出し画像

【アプリ開発日記49週目】タグ付けと履歴反映と片付けと

 おつかれさまです! 今回は今まで見た目だけになっていたUIに中身を詰めていきます。データを反映させていきながら、必要なところ・不必要なところを調整していければなと。

 ということでさっそく始めていきます!

1,ブックページの作成/編集

 一応初期に触っていた部分ですが、node-fetchからaxiosへの切り替えやコンポーネントの変更などが重なり、今ではかなり使いづらい状態に。…というよりエラーで動かない!

 そもそもUIがぐちゃぐちゃになってしまっているので、いったん修正したいところをまとめました。

赤:処理や場所に問題あり  青:データを反映させたい

 …ほとんど問題だらけ! 最初はうまく動作させることが目的だったので、予定通りといえば予定通りですが。さっそく直していきましょう。

 まず青の部分から。下の「最後の演習から○日経過~」はまだコンセプトが固まりきっていないので、いずれ通知処理と合わせて実装していきます。のちの核の一つとなる部分?です。

 一方、もう一つの「○がどれくらい」などは前回とも通じる内容ですね! 最新履歴をもとに、それぞれの割合を出してあげれば良さそうです。ちなみに、カラフルなプログレスバーは下記のように書いて数字を入れてあげれば実装できます。(もっとシンプルな方法あったらごめんなさい)

ProgresBar.js

let bars = document.querySelectorAll('.progress-bar');
let bar_count = 0;
bars.forEach(function(bar) {
  bar.style = 'width:' + per[bar_count] + '%';
  bar.setAttribute('aria-valuenow', per[bar_count]);
  bar_count++;
});
jsx

<div className='progress-bar-wrapper w-full flex overflow-hidden rounded h-4 transition' style={{ backgroundColor:'#e9ecef' }}>
  <div className="progress-bar bg-progress-blue h-full"   role="progressbar" style={{ width: '0%' }} aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
  <div className="progress-bar bg-progress-sky h-full"    role="progressbar" style={{ width: '0%' }} aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
  <div className="progress-bar bg-progress-yellow h-full" role="progressbar" style={{ width: '0%' }} aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
  <div className="progress-bar bg-progress-red h-full"    role="progressbar" style={{ width: '0%' }} aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
  <div className="progress-bar bg-progress-gray h-full"   role="progressbar" style={{ width: '0%' }} aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
</div>

 データも無事反映されました!

達成数や正答率も表示できました

続いて赤の部分です!

 Before After がこちら。

Before

赤:処理や場所に問題あり  青:データを反映させたい

After

本番を想定したデザインへ
フォーム・問題セット(仮)を開いた時

 派手な変化ではありませんが、快適度は少し上がったはず。他のページもありますが、まずこのページを片付けました。保存や編集フォームのイメージは柔軟さに驚いているNotion先生です!(ほとんど原型ないですが)

 noteもそうですが、例えば編集画面でタイトルを変更する時。クリックしただけで書き換えできますよね。当たり前すぎて気づかなかったこのフォーム形式ですが、それくらい快適なのでこれも実装します!

 …といってもinputタグの枠を消して周りになじませながら、「inputの中身が変わった上で他の部分をクリックすると非同期処理で新しいタイトルが保存される」という処理を実装するというシンプルな作業。

 外見はこんな感じでしょうでしょうか? inputタグで表示しています!

 …うーん、、逆に分かりにくくなったかなぁ、直接タイトルをクリックするんじゃなくて「編集」ボタンを押すと2つ目の画像みたいになる、で「保存」ボタンを押したタイミングで更新される、の方がいいのかもしれない。間違えてタイトルクリックして変えちゃった、なんて事故も防げるし。ということでリベンジ。

▼ 編集ボタンを押すと……
フォーム表示!

 これでだいぶ見やすくなったような気がします!

 いつかもっとNotionみたいにヌルっと並び替えとか実現したいですが、それはまたの機会に。処理もaxiosに書き換えて無事動作したので、赤の処理もOKそうです!

2,セクションページの片付け

 これはブックページをベースにしているだけあって、修正の内容もほとんど同じです! というより、共通のコンポーネントを多用しているため、わざわざhtmlをコピーしなくても勝手に修正されています。変更の度にコピペして「コピペ忘れた部分あったらどうしよう」なんて怯える心配もないのはとても大きいです。reactは仮想DOMなので、書き換え時に一部だけ変更される、というのも有り難いですね。

 ということで、最終版だけ載せて次行きます!

コンポーネントの配置だけ書き換えたけど、それでも10分かからず修正終了!
さきほど最後に作成した、セクションの編集も無事機能しています!

3,プロフィール画面

 公開するにあたっては非ログイン時の動作や頻繁には使わないプロフィール画面なども最低限整えておく必要があります。ということでプロフィール画面から始めていきます!

 …と言っても。いずれこのサイト用の事項紹介文などの編集フォームも作りたいのですが、正直まだコンテンツが決まっていないため、ほとんど空白になっています。。。

 現時点でのログイン手段はグーグル認証のみなので、今回はとりあえずこれらを表示するページにしました。またコンテンツが増えてきたら、必要に応じて追加していきます!

Before

超絶シンプル

After

コンテンツに応じて増やしていきます、、、まだまだこれから

 続いて、非ログイン時。この場合は

  • 問題は全部見れるけど、演習履歴は残せない

  • ブックなどのお気に入り登録ができない

  • 問題作成ができない

というようにしておく必要があります。そもそもユーザー別を認識できず、データベースへの保存時にエラーとなってしまうためです。

ということで、今回はユーザーが認識されない場合

  • 問題ページの○×ボタンを押すことができない

  • POST処理を、ユーザーが認識されていないと送られないようにする

という修正を加えました。非ログイン時のメイン画面はこんな感じ。

 また、さきほど削除した問題作成ボタンをどこに配置しようか悩んでいたのですが、ヘッダーに入れることにしました!

 最後にこのメイン画面を編集していきます!

4,メイン画面

 ずっと避けてきたメイン画面行きます。

問題作成数以外を更新していきます!

 正直まだどんな感じにしようか決まりきっていないページではありますが、現在表示させている機能はプロフィール画面かどこかに表示したいと考えています。

 ただ、今までこういった細々とした処理の実装は「面倒だからまとめていつかやろう!」というわけで今日までほったらかしにされてきたのです。。ということでしばらく面倒なことにならないよう、今回まとめて実装していきます!

 まずブックの詳細表示。これはセクションで実装済みなので、コピペすればよさそうです。完成がこちら!

ボタンを押すと説明文が表示

 続いてプログレスバー。これもさきほどと同じようにブックIDで最新履歴を取得すれば反映できます。…が、演習履歴が増えたら処理重くなるし、メイン画面開くのに時間かけるのは何としても避けたいので、ここはいずれ解決するときが来そうです。レンダリング方法(ISR、SSGなど)が定まってからでしょうか?

 とりあえず、ブックの数forを回し、ブックごとに履歴を取得してきました。

ブックごとの履歴反映成功!

 続いて上部の「総演習数」「ブック制覇数」「問題作成数」。演習数と作成数はクエリからの取得でなんとかなりそうですが、ブック制覇数は「そのブックのidを持った最新履歴を全取得」し、「最新履歴がすべてgood/great、且つそのブックのidを持った問題数と最新履歴数が一致」していれば+1される…という感じでいいのでしょうか。

 というのも、一度全問正解しても、時間を置いて解いた時にバツになったら完全制覇ではなくなってしまう。或いは、もう簡単だからとブックの登録を解除しても、一度制覇したとしてもカウントされなくなってしまう。こうなれば、一度完全制覇したブックは解き直したくなくなるし、不必要なブックも削除しにくくなり、気軽にアウトプットの幅を広げられなくなってしまいます。

 そのため、今回新たに「ブック制覇」のテーブルをデータベースに追加します。テーブルは一つ増えますが、上記の処理時間が一気に短縮される上、上記の”制覇なくなる問題”を解決できるからです。ということで作成!

 本当はまだ全問正解していませんが、「昔制覇したけど解き直したらめっちゃ間違えた」ということにして、ブック1つ制覇したと保存します。このデータを取得すれば…

ブック制覇数が「2」に。

 無事反映されました!

 そして、再びブック一覧に戻ります。本当は先に「一覧から使うブックを登録する」「お気に入りをする」という処理(もし全部上部に並んでいたら、法律とか応用情報とか経済学が全部ごっちゃになる)を実装したいのですが、これは次回に回したいと思います。

 …となると残るは「タグ」ですね。見た目はタイトルや説明文と同じですが、違うのは「タグで検索できるようにする」ということ。つまり、ブック-セクションのようにブック-タグの外部キーを実装していきます。

 ところが、よくよく考えると今までの外部キーとも異なるものになりそうです。今までは「1対多」だったのに対し、今回はブックに複数のタグを付けられる、けど1つのタグにも複数のブックを登録できるという「多対多」なのです。

 djangoでのタグ実装には「django-taggit」や「django-tagging」があるそうなのですが、最後の更新がdjango3.6であること(バグが起きた時に対処できない)や、多対多 外部キーの経験も積んでおきたい、わざわざライブラリが必要というほど複雑な処理でもないということから、今回は素の外部キーで実装していきます。

 実装にあたっては、以下の記事を参考にさせていただきました! 多対多初心者にとって非常にわかりやすかったです、ありがとうございます。

 多対多外部キーってこんな感じで操作するんですね! ユーザーの時も見かけたけど、あれも多対多だったのか。serializers.pyにも'tags'を追加。

 やったことは上記の記事とほとんど変わらないので、さっそく実装結果です!

book['tags']だと登録したタグのid一覧が取得されるみたいです!

 …ん? id取得できました、が、idしか取得できていません! これは不便…そこで、どうにか名前なども取得できないか探していたところ

 これです!ほしかった情報。

 そしてコードに追記すると…

class BookTagSerializer(serializers.ModelSerializer):
    class Meta:
        model = BookTag
        fields = ('id', 'user', 'name')

class BookSerializer(serializers.ModelSerializer):
    tags = BookTagSerializer(many=True)

    class Meta:
        model = Book
        fields = ('id', 'user', 'title', 'desc', 'status', 'tags', 'updated_at', 'created_at')

バッチリ名前も取得できました! これをメイン画面に反映させれば…

タグ反映!

 人生初の多対多クリア! バグが出てきたらまた修正していきます。

 これでメイン画面も今気になっている点は解消しました!

おわりに

 これでずっと気になっていたことがだいぶすっきりしてきました。いったん整ったシンプルな演習ウェブアプリになってきた、という感じでしょうか。

 お気に入り登録はじめ他にも色々実装したい機能は残っていますが、スマホでの操作感テストも兼ねて、そろそろ一度公開してみようと思います。今一番怖いのは、ロード時間が長くなるかもしれないこと。

 一度公開すれば、否が応でもレンダリング方法について考えなくちゃいけなくなるので、案外ちょうどいい時期なのかもしれません。それに年内にスマホからアクセスできるようになる!

 ということでこれからも実装を進めていきます。ではでは!

この記事が気に入ったらサポートをしてみませんか?