見出し画像

Notion で(自分なりの)タスク・時間管理を行う方法(これまでの記事のまとめ)

はじめに、これまでの九本の記事を少しでも読んでみてくれたり「スキ」までしてくださった皆さんへ、感謝を。いつも、ありがとうございます!

お陰様で、当初、説明しておきたいと思っていた内容の中心的な部分は、いったん、一通り書き終えることができました。

次回以降は、もっと細かいトピックを取り上げながら、いよいよ「自分なりの実践例」に重点を移して、紹介していければと思いますが、
その前に、一つの区切りとして、これまでの記事を振り返り、全体をまとめておこうというのが、今回の目的です。

それでは、始めて参りましょう。


入門編

この記事から読んでくれている方のために補足しておくと、実は、Notion でタスク管理を行う方法を紹介するといっても、
誰もが思い浮かべるようなタスク管理手法と比べると、人によっては、一風変わったものに思われるかもしれません。

そもそも「タスクシュート」のことを知らない人はもちろん、
普通に「タスクシュート」を使っている/使ったことのある人から見ても、結構、トリッキーなアプローチに感じられる可能性もあります。

導入としては、最初の記事を読んでもらうのが一番なのですが、

簡単に言ってしまえば、Notion は、案外、タスクシュートを「スモールスタート」で始めたい人にとっても、最適なツールなのではないか、
そして、だとすれば、すでに Notion を別の用途で活用している人や、試しに使ってみたいと思っている人に対しては、
上の記事に書いたような流れで、タスクシュートをオススメすると良いのではないか、と考えたわけですね。

つまり、Notion ほどのカスタマイズ性がなければ、真に「自分なりの」タスク管理は実践できない、とまでは言いませんが、
少なくとも、自分に合ったタスク管理システムを、既存のアプリの仕様に囚われず、ゼロから構成しやすいし、小さく始められるのは確かで、

その代わり、できることが多すぎて迷ってしまう人が多いのもまた事実なので、本マガジンが、ちょっとした道案内にでもなってくれればと思います。

運用編まとめ

次に、実際の運用方法を説明しながら、ツールが実用的になるよう、徐々に強化していこうというのが、ここからの三回となります。

特に、運用編①では、タスクシュートの基礎というか、基本的な使い方を紹介していて、

その次の記事では、早速「プロジェクト管理」にまで手を出していますね。
Notion の機能で言えば「リレーション」の導入です。

ちなみに、通常、タスクシュートを始める上で、プロジェクト管理の話は後回しになることも多いかと思いますが、
個人的には、このプロジェクトの位置付けなら、単に「今日のタスク」を追加する際のサポート機能と捉えることもできると考えています。

むしろ、最初は「プロジェクトリスト」からだけタスクを追加して実行することこそが「スモールスタート」だと言っても過言ではないでしょう。

そして、運用編の最後では「データベーステンプレート」を利用した「ルーチン」の作り方を説明しているわけですが、

こうしてみると、我ながら、割といい感じの手順で解説を進められている気がしますね。

タスクシュートと Notion の機能の説明を同時にこなすというのは、なかなか難しい面もありつつ、
逆に、自然と順を追って話さざるを得なくなるため、あらかじめ細かい計画を立てなくても、少しずつ書いていきやすかったのかもしれません。

拡張編まとめ

さて、前回までで、私が Notion でタスクシュートを始めた時、最初に作ったシステムについては、概要を紹介できた形となりますが、
もちろん、ここから「自分なりに」拡張していけることこそが、Notion でタスク管理を行う、一番のメリットでもあります。

もはや、これは、Notion を Viewer として利用した、一種の「タスク管理アプリ開発」と表現した方が、分かりやすいくらいでしょう。

とは言え、拡張編の一つ目は、準備運動のようなものです。

この記事については、運用編に含めても良かったかもしれませんが、
一応、単なるタスク管理から「時間管理」に拡張されているという意味で、新たな展開とは言えると思います。

タスクシュートとしても、次の記事で「見積もり」の概念が導入されることにより、一歩先へ進んだ感じですね。

Notion の機能で言うと、ここで「数式」を使い始めているわけですが、
ある程度、複雑なことをしている割には、それなりに綺麗な流れで解説できたのではないかと自負しています。

まぁ、複雑とは言っても、この次の記事からの内容を考えれば、まだまだ、序の口でしょう。

この記事から、API を利用して、Google Apps Script(JavaScript)でデータを取得したり、Notion にタスクを追加したりし始めました。

ここでは、Google カレンダーから今日の予定を取得して、Notion 上のタスクデータベースに今日のタスクとして登録することを題材としていますが、
最も重要なのは、その設定の手順を理解することで、
やり方さえ分かってしまえば、それ以降は、自分なりに様々な機能を実装することができるようになっていくでしょう。

例えば、RSS を活用して、定期的にブログ等の更新情報を受け取り、Notion 上でリスト化しておいたり、
YouTube から動画のタイトルやリンク等を取得して、データベース化したりなどといったことも可能です(具体的には、いずれ紹介します)。

もちろん、逆に、Notion のデータベースからデータを取得して、プログラムの中で利用することもできます。
LINE Notify の API を使えば、LINE に通知を送ったりすることも。

そうした、大まかな方針さえ決まっているのなら、今時、細かい部分は AI に丸投げしてもいいでしょう。

私も、元々は「ルーチンデータベース」からタスクを自動生成したい!というモチベーションがあって、実際、そこを目指していたわけですが、
次の記事では、その準備も兼ねて「セクションデータベース」を作成し、まず、セクションプロパティの運用を更新しました。

あまり詳しくは説明できませんでしたが、ここまでくれば、Notion API の使い方の基本も、なんとなく分かってきたのではないでしょうか?

とりあえず、データベースからのデータの取得と、データベースへのデータの追加について、実例を紹介しただけとも言えますが、
おそらく、大体のシステムの構築においては、それで十分だと思います。

そして、拡張編の最後の記事が、ついに、ルーチンデータベースから取得したデータを元に、今日のタスクを自動生成する方法についてです。

前二回の知識もフルに活用しなければならないため、なかなか難易度は高かったかもしれませんが、
逆に言うと、それまでの記事の内容を理解していれば、最後の方に少しだけ発展的な内容が追加されている程度でしょう。

Notion の「ブロック」の説明は、思い切って割愛しましたが、私もまだ十分に使いこなせているとは言えませんし、
色々と試してみてから、いずれ、紹介することもあるかと思います。

あと、ルーチンに「プロジェクト」を紐づける方法(カスタマイズ)については、単純に、すっかり忘れていたのですが、
また、改めて、私なりの「プロジェクトとルーチンの使い分け」にまつわる実践例を、記事にする予定です。

ソースコードまとめ

最後に、これまで(終盤の三回ほどで)書いてきた JavaScript のコード全体を共有しておきます。
多少、改善されている部分もあるで、それも含めてご確認ください。

全体の構成は、こんな感じです。

Google Apps Script(JavaScript)のコードの構成

まぁ、まだまだ、改良の余地はあるかと思いますが、書き方の好みもありますし、参考までに。

function getChildren(id, secret) {
  const options = {
    'method': 'get',
    'headers': {
      'Authorization': 'Bearer ' + secret,
      'Notion-Version': '2022-06-28'
    }
  };
  const response = UrlFetchApp.fetch('https://api.notion.com/v1/blocks/' + id + '/children?page_size=100', options);
  const contents = JSON.parse(response.getContentText());
  return contents.results;
}

function repeat2boolean(repeat, day) {
  if (repeat == "毎日") {
    return true;
  } else if (repeat == "平日" && 0 < day && day < 6) {
    return true;
  } else if (repeat == "土日" && !(0 < day && day < 6)) {
    return true;
  } else if (repeat == day2repeat(day)) {
    return true;
  }
  return false;
}

function day2repeat(day) {
  switch (day) {
    case 0:
      return "日曜日";
    case 1:
      return "月曜日";
    case 2:
      return "火曜日";
    case 3:
      return "水曜日";
    case 4:
      return "木曜日";
    case 5:
      return "金曜日";
    case 6:
      return "土曜日";
    default:
      return "";
  }
}

function getRoutines(database_id, secret) {
  const data = { "filter": { "property": "有効", "checkbox": { "equals": true } } }
  const options = {
    'method': 'post',
    'headers': {
      'Authorization': 'Bearer ' + secret,
      'Content-Type': 'application/json',
      'Notion-Version': '2022-06-28'
    },
    'payload': JSON.stringify(data)
  };
  const response = UrlFetchApp.fetch('https://api.notion.com/v1/databases/' + database_id + '/query', options);
  const contents = JSON.parse(response.getContentText());
  return contents.results;
}

function getSections(database_id, secret) {
  const data = { "sorts": [{ "property": "開始時刻", "direction": "descending" }] }
  const options = {
    'method': 'post',
    'headers': {
      'Authorization': 'Bearer ' + secret,
      'Content-Type': 'application/json',
      'Notion-Version': '2022-06-28'
    },
    'payload': JSON.stringify(data)
  };
  const response = UrlFetchApp.fetch('https://api.notion.com/v1/databases/' + database_id + '/query', options);
  const contents = JSON.parse(response.getContentText());
  const object = {};
  for (const result of contents.results) {
    object[result.properties["開始時刻"].rich_text[0].plain_text] = result.id;
  }
  return object;
}

function myFunction() {
  const sections = getSections("xxxxx", "secret_XXXXX");
  const today = new Date();
  const tomorrow = new Date(today.getTime() + (24 * 60 * 60 * 1000));
  const events = Calendar.Events.list('primary', {
    timeMax: tomorrow.toISOString(),
    timeMin: today.toISOString(),
    singleEvents: true,
    orderBy: 'startTime'
  });
  for (const event of events.items) {
    const start = new Date(event.start.dateTime);
    const end = new Date(event.end.dateTime);
    const minutes = (end - start) / 1000 / 60;
    const children = event.description ? [{ "object": "block", "paragraph": { "rich_text": [{ "type": "text", "text": { "content": event.description } }] } }] : [];
    postNotion("yyyyy", event.summary, start, sections, minutes, children, "secret_XXXXX");
  }
  const routines = getRoutines("zzzzz", "secret_XXXXX");
  const day = today.getDay();
  routines.forEach((routine) => {
    const properties = routine.properties;
    const title = properties["名前"].title[0].plain_text;
    const time = properties["開始予定時刻"].rich_text[0].plain_text;
    const [h, m] = time.split(":").map(Number);
    const start = new Date(today.setHours(h, m, 0, 0));
    const minutes = properties["見積値"].number;
    const repeats = properties["繰り返し"].multi_select.map(option => option.name);
    repeats.forEach((repeat) => {
      if (repeat2boolean(repeat, day)) {
        const children = getChildren(routine.id, "secret_XXXXX");
        postNotion("yyyyy", title, start, sections, minutes, children, "secret_XXXXX");
      }
    });
  });
}

function time2section(sections, time) {
  const keys = Object.keys(sections);
  const start = keys.find(key => time >= key);
  return sections[start];
}

function postNotion(database_id, title, start, sections, minutes, children, secret) {
  const time = start.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false });
  const section = time2section(sections, time);
  const data = {
    "parent": { "type": "database_id", "database_id": database_id },
    "properties": {
      "title": { "type": "title", "title": [{ "type": "text", "text": { "content": title } }] },
      "実行日": { "type": "date", "date": { "start": start } },
      "開始予定時刻": { "rich_text": [{ "type": "text", "text": { "content": time } }] },
      "セクション": { "relation": [{ "id": section }] },
      "見積値": { "type": "number", "number": minutes }
    },
    "children": children
  };
  const options = {
    'method': 'post',
    'headers': {
      'Authorization': 'Bearer ' + secret,
      'Content-Type': 'application/json',
      'Notion-Version': '2022-06-28'
    },
    'payload': JSON.stringify(data)
  };
  UrlFetchApp.fetch('https://api.notion.com/v1/pages', options);
}

最低限、置き換える必要のある文字列は、以下の通りです。

  • "xxxxx":セクションリストデータベースの ID

  • "yyyyy":タスクリストデータベースの ID

  • "zzzzz":ルーチンリストデータベースの ID

  • "secret_XXXXX":Notion API のシークレット

実は、前の記事では何気に説明を回避したのですが、Google カレンダーの予定を今日のタスクとして追加する際、
上のように、予定の説明文(description)を「children」に設定しておけば、生成されるタスクのページにも記述されます。

ただし、単純な文字列なら、このように書けば事足りると思いますが、

const children = event.description ? [{ "object": "block", "paragraph": { "rich_text": [{ "type": "text", "text": { "content": event.description } }] } }] : [];

例えば、リンクが書いてある時には「Web ブックマーク」を作成したいなど、要望によっては、割と複雑な場合分けが必要になってくるので、
私も個人的に検討しつつ、またの機会に、ご紹介していくとしましょう。

今後の予定

さて、ひとまず、ここまでお付き合いいただき、ありがとうございました!

しかし、感覚的には、実のところ「ここからが本番」と言っても過言ではないような面もあり、これまで作成してきたシステムを土台として、
以降の記事では、少しずつ私の実践例も交えながら、もっと細かいトピックを取り上げていければと思います。

ページのタイトルも、そろそろ「ごくシンプルなタスク管理システム」から変えておきましょう。

現在の「My TaskChute」の画面

その他にも、しばらくしたら、Notion でタスクシュートを実践する際の「全く別の実装パターン」を検討してみたり、
タスクシュート協会公式で開発されている Notion テンプレートから始めた場合の、オススメのカスタマイズ方法など、書きたいことは多いですね。

まぁ、とりあえずは、この note を書くため、新たに作成してきたシステムを、実際に運用中だったものと置き換えることにより、
これを機に、環境をリニューアルしようかと思っているので、その際に行った自分なりのカスタマイズを、少しずつ紹介していくとしましょう。

ではまた。

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