PowerApps│Plannerと連動したタスク管理アプリの作り方

アイキャッチ

こんにちは、あんこ先生です。

チームメンバー全員のタスクをひとつのアプリで管理したくありませんか?

タスク管理はひとによってやり方が異なります。

OutlookやPlannerを使う人もいれば、メモ帳や付箋など物理的に管理するなど多種多様です。

とりあえずチームを任されたリーダーの立場を想定すると、複数名が同時にアクセスできる最適なツールはPlannerではないでしょうか?

ただ、Plannerも操作性がイマイチなところがあります。

そこで、今回は「チームのタスクを簡単に管理したい」、「Plannerと連動したアプリを作りたい」といった方を対象に、チームメンバーのタスクをまとめて管理できるアプリを作成します。

いつものとおり、主要個所のコードはコピペで動くようにしています。

アプリで実現できること
  1. チームメンバー全員のタスク表示、その制御
  2. 自分に割り当てられたタスクの完了と取消処理
  3. 自分が作ったタスクの削除
  4. ユーザーを選択してタスクを割り当てる
  5. タスクの完了状況をグラフで可視化

この記事で紹介したガントチャートと連動させると、さらに便利なアプリになりそうですね。


より高性能なアプリを手軽に導入されたい方へ。完成版の販売も行っています。

記事を取得できませんでした。記事IDをご確認ください。

Plannerと連動したタスク管理アプリの作り方


タスク管理とは

タスク管理とは、それぞれのタスクを誰に割り振るかを考えたり、進捗状況を確認したりすることです。

個人の業務を効率的に進めたり、チームに与えられたプロジェクトなどを円滑に解決するための方法のひとつです。

タスク管理によって、生産性や業務品質の向上が期待できます。

今回はポイントを可視化するため、チームメンバーのモチベーションアップにも効果があります。


アプリの機能

Plannerを経由したチームメンバー全員のタスクを表示します。

自分のみや完了したものも含むなど表示内容の制御もできます。

基本機能として、自分に割り当てられたタスクの完了と取消処理が行えます。

また、自分が作ったタスクの削除やユーザーを選択してタスクを割り当てることもできます。

タスクの完了状況をグラフで可視化します。


工夫した点

タスクをSharePointリストではなくPlannerとして管理します。

これにより、Planner側でも操作できるとともに、遅延タスクの一覧が毎日メールされるようになります。


アプリの構成

タスク管理アプリは主に3つのコンテナコントロールで構成されています。

作成手順はコンテナコントロール単位で説明していきます。

アプリとPlannerと連動させるための事前準備


Plannerで個人ごとのプランを作成する

委任に似た問題として、Plannerコネクタは1プラン400件までのタスクしか抜き取れません。

400件を超える場合、例えば1000件あっても最新の400件のみ抜き取ります。

これはフィルターなどで絞り込んでも回避できません。

そのため、今回は個人ごとにプランを作成します。

半期での運用を想定していますが、チーム全体で400件以内に収まる、そもそもポイント集計しないならプランを分ける必要はありません。


コネクタを接続する

使用するコネクタは全部で3つです。

Planner
Plannerと連動するために使用します。

Office365ユーザー
ユーザー情報の取得に使います。

Office365グループ
グループメンバー一覧やIDの取得に使います。


Plannerと接続するための各種IDを調べる

PowerAppsとPlannerが連動するために必要なIDは次の4つです。

  1. GroupID
  2. PlanID
  3. BucketID
  4. UserID

IDの調べ方は次の記事で詳しく解説しています。

今回はPlannerのURLから2つのID、その結果から残りの2つを取得できるため、上記記事とは別のアプリを作成しました。

グループIDとプランIDはURLから確認できますが、コピペで抜けるようにしました。

バケットIDは上記情報からプラン内の全バケット情報を表示させます。

そこから依頼事項のIDを抜き取ります。

ユーザーIDはグループIDから全員分がかんたんに抜けます。

人数が多いほど、調べるためのアプリ作成は有効です。

この調査用アプリはテキスト入力コントロール(URLCopy)のTextプロパティにURLをコピペして使います。

GroupID用のTextInputxx.Textプロパティ

First(
    LastN(
        Split(Substitute(URLCopy.Text,"&planId",""),"=").Result
    ,2)
).Result

PlanID用のTextInputxx.Textプロパティ

Last(
    Split(Substitute(URLCopy.Text,"&planId",""),"=")
).Result

BucketID用のGalleryxx.Itemsプロパティ

Planner.ListBucketsV3(PlanID.Text,GroupID.Text).value

UserID用のGalleryxx.Itemsプロパティ

Office365グループ.ListGroupMembers(GroupID.Text).value

チームメンバーのタスクをコレクションに複写する


アプリ起動時に自動生成

IDさえ調べてしまえば、あとは地道な作業です。

ひたすら調べたIDをコードに転記してコレクション_PlanIDを作成します。

次に、チームメンバー全員のタスクを複写するコレクション_AllPlanを作成する。

ついでに変数もここで宣言しておきます。

これらは、アプリ起動時に読み込ませるため、AppOnStartプロパティに埋め込みます。

//起動時ユーザーID
Set(_UID,Office365ユーザー.MyProfileV2().id);

//集計開始日の前日
Set(_StDate,DateValue("2022/1/1"));

//IDまとめ 人数分転記
ClearCollect(_PlanID,
	{//せり
        UId:"ec3c7494-cfa5-4b0b-be6f-56ad5ed9efa3",
        name:Office365ユーザー.UserProfileV2("ec3c7494-cfa5-4b0b-be6f-56ad5ed9efa3").givenName,
        bucketId:"VhuERU_9KkW7sj0Z1zUwg_oAGBd1",
        planId:"6NmjyJ906kq3imUtQOKt7PoACUiR",
        groupId:"ec7990f1-965d-4c1f-a862-97020d05f296"
    },
	{//なずな
        UId:"1d750917-b662-41d5-9946-20c686443b6f",
        name:Office365ユーザー.UserProfileV2("1d750917-b662-41d5-9946-20c686443b6f").givenName,
        bucketId:"V-DSFLgblUqy53ZMSxZhk_oAHVTT",
        planId:"ui4WMiuDtkGcjmC0UnikS_oABFHu",
        groupId:"322baf90-8b02-44c1-8cb6-4661e411b053"
    },
	{//ごぎょう
        UId:"ae64f636-3397-4b2a-9729-da5a4d5b75d4",
        name:Office365ユーザー.UserProfileV2("ae64f636-3397-4b2a-9729-da5a4d5b75d4").givenName,
        bucketId:"nqEnb3gcZ0iXInS6w26iQvoADT1t",
        planId:"UN0U0mA_e0qmpCMWwPA9cPoAGZe2",
        groupId:"77349d37-05a6-4aa9-9ca6-798d2b2dc57b"
    },
	{//はこべら
        UId:"3789f418-3e86-485f-8760-58729de49c2b",
        name:Office365ユーザー.UserProfileV2("3789f418-3e86-485f-8760-58729de49c2b").givenName,
        bucketId:"3o_1Uek5CUOA7cQdjtgP5_oAB0Vw",
        planId:"d6AMMRcSvUSYgj1oX6H3WvoABYTc",
        groupId:"65d0d879-536a-4888-a675-5d1c57355427"
    },
	{//ほとけのざ
        UId:"e2e3df68-a51d-4c79-a5ca-489c68706d06",
        name:Office365ユーザー.UserProfileV2("e2e3df68-a51d-4c79-a5ca-489c68706d06").givenName,
        bucketId:"QmRKwjwbf0OICLpWVc3eWfoAFvJ1",
        planId:"UfrstYYj2UGWqzrxdkgTyvoAEeV9",
        groupId:"23fcae2e-e68e-495f-a6ab-abc4db62f25d"
    }
);

//人数分のPlan結合
Clear(_AllPlan);
ForAll(_PlanID,
    Collect(_AllPlan,
        Ungroup(Planner.ListTasksV3(
            ThisRecord.planId,
            ThisRecord.groupId
        ).value,"_assignments")
    )
);

Plannerと連動したタスクの一覧表示│アプリ作成①

事前準備は終えたので、ここからはアプリを作成していきます。

なお、アプリからPlannerのタスクを更新させた場合、手動で再読み込みするまでアプリ側のタスクは反映しません。

そのため、コレクションも同様に更新し同期させています。アプリ内ではこのコレクションを参照しています。

このようなやり方をとった理由は、更新の都度コレクションを再作成すると処理時間が長くなるためです。

気にされない場合は、直接コネクタを参照しても構いません。

また、今回のアプリ作成はただのラベルや線など解説が不要と判断するものは除外します。


チームメンバーのタスク表示を制御する

3つのチェックボックスコントロールと1つのアイコンコントロールで構成されています。

厳密にはコンテナの外側に配置しています。

C2_Checkbox1
タスクが完了、つまりpercentCompleteが100のタスクを表示するか制御します。
チェックを入れると、完了したタスクも表示されます。

C2_Checkbox2
自分以外のユーザーを表示するか制御します。
チェックを入れると自分に割り当てられたタスクのみ表示されます。

C2_Checkbox3
削除モードを制御します。
チェックを入れると自分が登録したタスクにゴミ箱アイコンが表示されます。
自分が他ユーザーに割り当てたものも含みます。

C2_Icon
押下するとプランナーからタスクを複写し、コレクションを更新します。

C2_Icon.OnSelectプロパティ

//Plan更新
Clear(_AllPlan);
ForAll(_PlanID,
    Collect(_AllPlan,
        Ungroup(Planner.ListTasksV3(
            ThisRecord.planId,
            ThisRecord.groupId
        ).value,"_assignments")
    )
)


チームメンバーのタスクを表示する

ギャラリーにタスクを表示します。

表示条件は上記制御部に依存します。

タスク名や期限のラベルコントロールは解説を省きます。

GalleryPlan
コレクション_AllPlanを各種制御に基づいて表示します。

GP_Fill
期限を超過したタスクに色を付けます。

GP_Label1
タスクを割り当てられたユーザー名を表示します。

GP_Image
タスクを割り当てられたユーザーの画像を表示します。
画像を登録していない場合は、表示されません。

GP_Checkbox
自分に割り当てられたタスクのみ表示されます。
チェック時は完了、外れた時は未完になります。
プランナーだけではなく、コレクションも同時に更新します。

GP_Icon
削除モードの場合、自分が登録したタスクにゴミ箱アイコンが表示されます。
押下することで、そのタスクが削除されます。
プランナーだけではなく、コレクションも同時に更新します。

GalleryPlan.Itemsプロパティ

Switch(C2_Checkbox1.Value&C2_Checkbox2.Value,
    "truetrue",
        Sort(
            Filter(_AllPlan,userId=Office365ユーザー.MyProfileV2().id),
            dueDateTime,Ascending
        ),
    "truefalse",
        Sort(_AllPlan,dueDateTime,Ascending),
    "falsetrue",
        Sort(
            Filter(_AllPlan,userId=Office365ユーザー.MyProfileV2().id,percentComplete<100),
            dueDateTime,Ascending
        ),
    "falsefalse",
        Sort(
            Filter(_AllPlan,percentComplete<100),
            dueDateTime,Ascending
        )
)

GP_Label1.Textプロパティ

//ユーザーが存在しているなら名前を表示
If(!IsBlank(ThisItem.userId),
    Office365ユーザー.UserProfileV2(ThisItem.userId).givenName
)

GP_Image.Imageプロパティ

//ユーザーが存在してて、かつ画像を設定していればそれを表示する
If(!IsBlank(ThisItem.userId) && Office365ユーザー.UserPhotoMetadata(ThisItem.userId).HasPhoto,
    Office365ユーザー.UserPhoto(ThisItem.userId)
)

GP_Checkbox.DefaultOnSelectプロパティ

//タスクが完了していればチェックを入れる
ThisItem.percentComplete=100

.OnCheckプロパティ

//タスクを完了する
Planner.UpdateTaskV2(ThisItem.id,{percentComplete:"Completed"});
Patch(_AllPlan,ThisItem,{percentComplete:100});

.OnUncheckプロパティ

//タスクを未了にする
Planner.UpdateTaskV2(ThisItem.id,{percentComplete:"Not Started"});
Patch(_AllPlan,ThisItem,{percentComplete:0});

.DisplayModeプロパティ

//タスク登録者が自分なら表示
If(ThisItem.userId=Office365ユーザー.MyProfileV2().id,
    DisplayMode.Edit,
    DisplayMode.Disabled
)

.Visibleプロパティ

!C2_Checkbox3.Value

GP_Icon.OnSelectプロパティ

//タスクを削除する
Planner.DeleteTask(ThisItem.id)

.Visibleプロパティ

//このタスクを自分が作成したか、もしくは割り当てられたものであればC2_Checkbox3に連動して表示する
(ThisItem.createdBy.user.id=Office365ユーザー.MyProfileV2().id||
ThisItem.userId=Office365ユーザー.MyProfileV2().id)
&&C2_Checkbox3.Value

Plannerのタスク割り当て│アプリ作成②

個別にタスクを割り当てる際に使用する部分です。

同じ内容を複数名に割り当てることもできるようにしています。


ユーザーを選択してタスクを割り当てる

C4_ComboBox
タスクを割り当てるユーザーを選択します。
ユーザーは複数名選択することも可能です。
選択肢として表示されるユーザーは、Office365グループコネタで抽出しています。

C4_DatePicker
タスクの実施期限を選択します。

C4_TextInput
タスク名を入力します。

C4_Icon
押下することでユーザーごとのバケット名依頼事項にタスクが登録されます。
ユーザーとタイトルが含まれていれば押せるようになります。
この部分のみプランナー更新後、コレクションを再作成しています。
なぜなら、差分をコレクションに追加するのが結構めんどくさいからです。
更新や削除はかんたんなんですけどね。
なので、対応できるならコレクションの再作成は不要です。

C4_ComboBox.Itemsプロパティ

Office365グループ.ListGroupMembers(First(_PlanID).groupId).value

C4_DatePicker.DefaultDateプロパティ

DefaultDate

C4_Icon.OnSelectプロパティ

ForAll(C4_ComboBox.SelectedItems As CB,

    Planner.CreateTaskV3(LookUp(_PlanID,UId=CB.id).groupId,LookUp(_PlanID,UId=CB.id).planId,
        C4_TextInput.Text,
        {
            bucketId:LookUp(_PlanID,UId=CB.id).bucketId,
            dueDateTime:C4_DatePicker.SelectedDate,
            assignments:CB.id
        }
    )
);
Notify("依頼しました。");
Select(C2_Icon);
Reset(C4_ComboBox);Reset(C4_TextInput);

.DisplayModeプロパティ

If(IsBlank(C4_TextInput.Text),DisplayMode.Disabled,DisplayMode.Edit)

グラフ化によるタスクの可視化│アプリ作成③

①部分を可視化したグラフを作成します。

図形の高さやY座標を算出して表現しています。

棒グラフの作り方はこの記事を参照してください。


タスクの完了状況をグラフで可視化する

GalleryGraph
グラフ部分です。
総数を枠のみ、完了数を塗りつぶしで表現しています。
凡例も作るとわかりやすいですね。

GG_Title
ユーザー名を表示します。
ファーストネームとしていますが、特に意味はありません。

GG_Complete
期限内に完了したタスク数を塗りつぶしの高さで表現しています。
期限後に完了したタスクは含まれません。
コードの最後にある数字は重みです。
描画エリアからはみ出る場合は小さくしてください。

GG_Plan
割り当てられたタスクの総数を枠のみの高さで表現しています。

GalleryGraph.Itemsプロパティ

SortByColumns(_PlanID,"name",["せり","なずな","ごぎょう","はこべら","ほとけのざ"])

GG_Title.Textプロパティ

ThisItem.name

GG_Complete.Heightプロパティ

CountRows(
    Filter(_AllPlan,userId=ThisItem.UId,percentComplete=100,
        dueDateTime>=_StDate,completedDateTime<=dueDateTime+1
    )
)*20

GG_Complete.Yプロパティ

GG_Line.Y-Self.Height

GG_Plan.Heightプロパティ

CountRows(
    Filter(_AllPlan,userId=ThisItem.UId,
        dueDateTime>=_StDate
    )
)*20

GG_Plan.Yプロパティ

GG_Line.Y-Self.Height

以上でアプリの作成はおわりです。

実際に操作して動作するか確認してください。

まとめ

今回は、チームメンバーのタスクをまとめて管理するアプリを作成してみました。

他メンバーの状況も確認できるので、相互牽制も働きますし、競争意識も煽れますね!

ポイントの獲得に応じたインセンティブな何かを用意するとより効果的です。

さっそくアプリを運用し、チームの成果をあげてみましょう!

もう1歩進んだ機能追加版は、後日別記事にまとめますのでしばらくお待ちください。

ご支援よろしくおねがいします

この記事を気に入ってくださった方、寄付してあげてもいいよという方がいらっしゃっいましたら、ご支援いただけると助かります。

下記リンクからAmazonギフト券で、金額は15円からお気持ちを入力してください。

受取人のメールアドレスは amazon@anko7793.com までお願いします。

メッセージもいただけるとたいへん喜びます。

よろしくお願いします。

Amazonギフト券(Eメールタイプ) 

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

ABOUT US
七草あんこ
非IT系中間管理職やってます。社命によりoffice365を主軸とした業務改善プロジェクトメンバーに任命されたことでPowerAppsと出会えました。いまではビジネス・プライベートを問わず、欠かせないツールになっています。導入初期やアプリ作成時に遭遇した諸問題の解決法とサンプルアプリの作り方を紹介していきます。主にTwitterで情報収集しているので不明点など呟いているとお邪魔するかもしれません。