見出し画像

Notion で(自分なりの)タスク・時間管理を行う方法(フォーム編①)

前回は、Notion で「プレ着手」を実践しやすくするため、
他のルーチンと「プレ着手リスト」を分けて管理し、各10分ずつのタスクを自動生成する方法について、説明させていただきました。

今回も、一見、かなりニッチな内容だと思われるかもしれませんが、
Google フォームから Notion の(タスクリスト)データベースを更新する方法を、紹介したいと思います。

確かに、本記事で解説する内容そのものは、主に、Notion カレンダー上でタスクを追加したり、動かしたりした際などに、
自動的には更新されない「開始予定時刻」や「見積値」などのプロパティの値を、追加・修正する手間を省くための手法なので、

タイムラインビューや Notion カレンダーを使って、あるいは、タスクごとに手動で「実行日」プロパティを更新して、
頻繁にプランを調整するような人でなければ、恩恵は少ないでしょう。

そもそも、「実行日」と「開始予定時刻」や「見積値」との間の齟齬を気にせず、例えば、上の記事の最後の方でも少し触れているように、
Notion カレンダーだけを見て過ごすなら、そこで「実行日」プロパティのみ更新できていれば、十分かもしれませんし、

逆に、Notion カレンダーを使わないなら、「開始予定時刻」と「見積値」さえ、ちゃんと設定できていれば、実用上、特に問題はないかもしれません。

ともかく、実行してしまえば、そのタスクのログ関連のプロパティは、同じように記録されることになるわけですからね。

とは言え、簡単に修正できるなら、それに越したことはないしょう。

しかし、今のところは、Notion の「ボタン」機能でやるには、やはり、ちょっと限界があります。

というわけで、いつものように、Google の Apps Script(GAS)を使おうということになるのですが、
少し違うのは、なんらかの処理を、定期的に自動実行させるのではなく、
任意のタイミングで、例えば、Notion カレンダーでプランを調整した直後に、データベースを更新したいという点です。

そこで、「Google フォーム」が出てくるわけですね。

この「フォーム」の「送信」ボタンを押した時などにも、GAS のスクリプトを実行することができるということは、知っていましたでしょうか?

しかも、Notion のページには「フォーム」を埋め込むことも可能なので、
ここまで言えば、これから何をやろうとしているのかは、大体、想像できたかもしれません。

それでは、そろそろ、始めていきましょう!


フォームを作成する

まず、Google にログインして、https://docs.google.com/forms/u/0/ にアクセスし、「空白のフォーム」を作成してください。

タイトルは、適当に「update」などとし、デフォルトで設置されている「ラジオボタン」を使った「無題の質問」は不要なので、削除したら、
右上のメニューから、「スクリプトエディタ」を選択して、いつもの Apps Script の画面を開きます。

「空白のフォーム」を作成する

まぁ、ここまでは、簡単ですね。

もちろん、何か質問を作成しておいて、例えば、タスクの新規作成を行うフォームなどを作ることも、可能なのですが、
それについては、次回のテーマに取っておくとして、今回は、フォームの実行だけができれば良いものとしましょう。

別に、質問の回答データは何もなくても、フォームを実行した際に、いつも同じ処理が行われるよう、設定しておくことはできます。

右上あたりにある「プレビュー」ボタンを押せば、ページを確認することもできますが、右上の「送信」ボタンからリンクをコピーし、

フォームのリンクを取得する

早速、Notion 上に埋め込んでみて、表示を確認してもらっても構いません。

フォームを Notion のページに埋め込む

ちなみに、見た目に関しても、ブラウザの機能でダークモードを強制するなど、検討の余地はありますが、ライトモードなら、以下のようになります。

ライトモードの場合

タスクリストを更新する処理を書く

では、いよいよ、上で開いた Apps Script のページに、例えば、以下のようなスクリプトを記述してみてください。

function myFunction() {
  const today = new Date();
  const after = today.toLocaleDateString("ja-JP", { year: "numeric", month: "2-digit", day: "2-digit" });
  const end = new Date(today.setHours(23, 59, 0, 0));
  const before = end.toLocaleDateString("ja-JP", { year: "numeric", month: "2-digit", day: "2-digit" });
  const filter = {
    "filter": { "and": [
      { "property": "実行日", "date": { "on_or_after": after.replaceAll("/", "-") } },
      { "property": "実行日", "date": { "on_or_before": before.replaceAll("/", "-") } },
      { "property": "ステータス", "status": { "equals": "未着手" } },
      { "property": "先送り", "checkbox": { "equals": false } }
    ] }
  };
  const options = {
    'method': 'post',
    'headers': {
      'Authorization': 'Bearer secret_XXXXX',
      'Content-Type': 'application/json',
      'Notion-Version': '2022-06-28'
    },
    'payload': JSON.stringify(filter)
  };
  const response = UrlFetchApp.fetch('https://api.notion.com/v1/databases/xxxxx/query', options);
  const contents = JSON.parse(response.getContentText());
  const results = contents.results;
  const sections = getSections();
  results.forEach((result) => {
    const properties = result.properties;
    const date = properties["実行日"].date;
    if (date.end) {
      const start = new Date(date.start);
      const end = new Date(date.end);
      const time = start.toLocaleTimeString("ja-JP", { hour: "2-digit", minute: "2-digit" });
      const section = time2section(sections, time);
      const minutes = (end.getTime() - start.getTime()) / 60000;
      const data = {
        "properties": {
          "開始予定時刻": { "rich_text": [{ "type": "text", "text": { "content": time } }] },
          "セクション": { "relation": [{ "id": section }] },
          "見積値": { "type": "number", "number": minutes }
        }
      };
      const patch = {
        'method': 'patch',
        'headers': {
          'Authorization': 'Bearer secret_XXXXX',
          'Content-Type': 'application/json',
          'Notion-Version': '2022-06-28'
        },
        'payload': JSON.stringify(data)
      };
      UrlFetchApp.fetch('https://api.notion.com/v1/pages/' + result.id, patch);
    }
  });
}

function getSections() {
  const data = { "sorts": [{ "property": "開始時刻", "direction": "descending" }] };
  const options = {
    'method': 'post',
    'headers': {
      'Authorization': 'Bearer secret_XXXXX',
      'Content-Type': 'application/json',
      'Notion-Version': '2022-06-28'
    },
    'payload': JSON.stringify(data)
  };
  const response = UrlFetchApp.fetch('https://api.notion.com/v1/databases/yyyyy/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 time2section(sections, time) {
  const keys = Object.keys(sections);
  const start = keys.find(key => time >= key);
  return sections[start];
}

ちょっと長いですが、下の「getSections()」や「time2section()」といった関数は、「セクション」プロパティを扱うためのもので、
実は、以前「セクションリスト」を導入した際に書いたコードと、ほとんど変わりないため、細かい説明は省略させてもらいます。

まぁ、単に、「time2section()」を使って、「開始予定時刻」をもとに、「セクション」を自動判定するためのスクリプトですね。

いつものように、"xxxxx" と "secret_XXXXX" は、「タスクデータリストデータベースの ID」と「Notion API のシークレット」に、全て書き換え、
今回は、それらと同様、"yyyyy" も、変数にはしていないので、少し分かりにくいかもしれませんが、下の方の「UrlFetchApp.fetch()」の引数の、
URL 中に含まれる "yyyyy" には、「セクションリストデータベースの ID」を入れておいてください。

そして、上の「myFunction()」でやっていることを、簡単に説明すると、相変わらず、日付の扱いは少し難しいですが、
まず、最初に出てくる「UrlFetchApp.fetch()」では、フィルター設定により、「実行日」が「今日」の「未着手」のタスクだけを取得しています。

あと、「先送りチェックボックス」がオンになっているものは、一応、省いていますね。

ともあれ、こうして取得したタスクの中に、「開始予定時刻」や「見積値」などのプロパティが、ちゃんと設定されていなかったり、
Notion カレンダー上で動かした「実行日」の値との間に、齟齬が生じている場合があったりするかもしれないので、
下の「forEach()」の中にある「
UrlFetchApp.fetch()」で、Notion API を実行し、一つずつ上書きしていこう、というわけです。

もちろん、上述した「セクション」プロパティについても、同じようなコードを書けば、追加・修正されていくことになります。

まぁ、どういう条件に当てはまるタスクを処理対象とするか、どのプロパティを更新するかは自由なので、特に「filter」や「data」変数の中身は、
是非、自分の運用に合わせて、考えてみてください。

トリガーを追加する

最後に、フォームの「送信」ボタンを押したら上のスクリプトが実行されるような、新しい「トリガー」を追加しましょう。

今までは、「時間主導型」のトリガーで、「午前0時〜1時」に実行するとか、

例えば、「12時間おき」に実行するといった感じで、指定することが多かったかと思いますが、

今回は、次のようにして、「フォーム送信時」に「myFunction()」が実行されるよう、設定しておく必要があります。

「トリガーを追加」する

これで、Notion カレンダーなどから追加した、「実行日」プロパティしか設定されていないようなタスクについても、
上で設置したフォームの「送信」ボタンを押せば、「開始予定時刻」や「見積値」そして「セクション」まで、自動的に入力されるはずです。

また、ボタンを押し忘れた場合などに備え、念の為、「時間主導型」のトリガーも、別で追加しておくといいかもしれませんね。

まとめ

さて、今回、ご紹介した内容は、個人的に必要に迫られて作成した、ちょっと特殊なニーズに対応するためのものでしたが、
Google フォームを利用した、このような仕組み自体は、色々な用途に、応用できそうだと思いませんか?

例えば、「実行日」というか、「日付」プロパティを、開始終了時刻まで手動で入力するのは、割と面倒なので、今回の内容とは逆に、
タスクの「開始予定時刻」と「見積値」だけが入力されている状態から、「実行日」の方を更新するような、スクリプトを書くこともできます。

まぁ、それを言うなら、そもそも、新しいタスクを追加する方法としても、これは、結構、便利かもしれません。

というわけで、次回は、フォームに「開始予定時刻」や「見積値」などを入力して、タスクを生成する方法についても、考えてみるとしましょう。

ついでに、Apple の「ショートカット」アプリを使って、簡易的にタスクを追加する手法についても、紹介する予定です。

ではまた。

いいなと思ったら応援しよう!