ダッシュボードに数字を並べる
件数・合計のカード、並べ方、絞り込み、押したときの遷移。
ダッシュボードは items にカードを並べる。カード1枚は「小さな読み取りクエリ」と「その結果の見せ方」の組。
page:
type: dashboard
id: sales_dashboard
title: 売上ダッシュボード
repository: orderRepository # カードが省略したときの既定
layout: { columns: 4 }
search:
filters:
- { field: orderDate, label: 受注日, type: date, operator: between }
items:
- { id: orderCount, title: 受注件数 }
- { id: total, title: 受注金額, value: { aggregate: sum, field: amount },
format: currency, config: { symbol: "¥" } }
- { id: pending, title: 未出荷, filters: { status: 未出荷 } }
- { id: recent, type: table, title: 直近の受注, span: 2, limit: 5,
sort: { field: orderDate, ascending: false },
columns: [ { field: orderNo, label: 受注番号 } ] }3種類のカード
type で決める。省略すると metric(数字1つ)。
| type | 何が出るか |
|---|---|
metric | 数字1つ。件数、合計、平均 |
table | 小さな一覧。直近◯件、上位◯件 |
chart | グラフ(「ダッシュボードにグラフを出す」参照) |
value を省くと件数になる
metric カードで value を書かないと件数が出る。合計や平均が欲しいなら value に aggregate と field を書く。
- { id: orderCount, title: 受注件数 } # 件数
- { id: total, title: 受注金額, value: { aggregate: sum, field: amount } } # 合計count 以外は field が必須。書き忘れると落ちずに件数のまま出るので、「合計を出したのに件数が出ている」ときはここを見る(下の「よくある間違い」参照)。
集計しているのは Framework ではない
集計クエリは投げていない。 Repository が返した行を、その場で畳み込んでいるだけ。だから
- カードが読む行数は
limit(既定 100)で決まる。100 件を超えるデータの合計は正しくない - 大きなデータの合計が要るなら、集計済みの値を返すエンドポイントを Repository 側に用意して、
limitを小さくしたカードで受ける
ここは業務で一番効く落とし穴なので、金額の合計を出すカードを作ったら limit を必ず確認する。
カードごとの絞り込み
filters に固定値を書くと、そのカードだけの条件になる。画面上部の検索条件(search)は全カードに効くので、両方を組み合わせて「今月の・未出荷の件数」のようなカードが作れる。
- { id: pending, title: 未出荷, filters: { status: 未出荷 } }並べ方
layout.columns がグリッドの幅(既定 2。業務画面では 4 が使いやすい)。span で1枚のカードが占める列数を指定する(既定 1)。表やグラフは span: 2 以上にしないと潰れる。
押したら別の画面へ
action にページアクションの id を書くと、カードを押したときにそれが走る。件数のカードから、その条件の一覧へ飛ばすのが定番。
items:
- { id: orderCount, title: 受注件数, action: openOrders }
actions:
- { id: openOrders, type: navigate, label: 受注照会, page: order_search }repository はカードごとに変えられる
ページの repository はカードが省略したときの既定でしかない。売上と在庫のように別のデータを1画面に並べるなら、カードごとに書く。
ダッシュボードは1件のレコードを扱わないので key は書かない。
書けるキー
| キー | 書く場所 | 型 | 必須 | 既定値 | 有効なページ種別 | 説明 |
|---|---|---|---|---|---|---|
items | dashboardPage | array → dashboardItem | 必須 | — | dashboard | Cards, in declaration order. |
items | menuItem | array → menuItem | 任意 | — | すべて | — |
span | dashboardItem | integer | 任意 | 1 | dashboard | Grid columns this card occupies. |
value | condition | any | 任意 | — | crud dashboard detail form master report search wizard | — |
value | dashboardItem | object → dashboardValue | 任意 | — | dashboard | Reduction for a metric card. Omitted = count. |
value | option | `string | number | boolean | null` | 任意 |
value | optionsSource | string | 任意 | "code" | crud dashboard detail form master report search wizard | Field of a row to store. |
aggregate | chart | string (count / sum / avg / min / max ほか) | 任意 | — | dashboard | Aggregate applied per label. Omitted = one point per row. |
aggregate | dashboardValue | string (count / sum / avg / min / max ほか) | 任意 | "count" | dashboard | Aggregate operation. Open string; built-ins below. |
aggregate | reportTotal | string (count / sum / avg / min / max ほか) | 任意 | "sum" | report | Aggregate operation (same vocabulary as a dashboard's). |
limit | dashboardItem | integer | 任意 | 100 | dashboard | Rows to fetch (the query's pageSize). |
limit | optionsSource | integer | 任意 | 200 | crud dashboard detail form master report search wizard | Rows to fetch (a select is not a list screen). |
limit | report | integer | 任意 | 1000 | report | Rows read for one run (a report is printed, not paged). |
sort | dashboardItem | object → dashboardItem.sort | 任意 | — | dashboard | Sort passed to the repository. |
sort | report | object → report.sort | 任意 | — | report | Print order, passed to the repository. Groups are control breaks, so the rows must arrive in this order. |
ascending | dashboardItem.sort | boolean | 任意 | true | dashboard | — |
ascending | report.sort | boolean | 任意 | true | report | — |
filters | dashboardItem | object → dashboardItem.filters | 任意 | — | dashboard | Fixed filter values merged into the query. |
filters | search | array → filter | 任意 | — | crud dashboard master report search | — |
action | dashboardItem | string | 任意 | — | dashboard | Id of a page action to run when the card is tapped. |
この表は spec/reference.json から生成している(JSON Schema が正)。手元では npx hatake reference <キー名> で同じものが引ける。
近い例
例は丸ごと写して直すのが一番速い。以下は CI で検証済み(そのまま動く形)。
| ファイル | 種別 | 画面 | どういうときに使うか |
|---|---|---|---|
customer_master.yaml | crud | 顧客マスタ | 検索して一覧に出して、その場で登録・修正・削除まで面倒を見る画面が欲しい |
product_search.yaml | search | 商品照会 | 検索して一覧を見るだけ(登録も更新もさせない)画面が欲しい |
sales_dashboard.yaml | dashboard | 売上ダッシュボード | 件数・金額・グラフのカードを並べて、まず数字を見せたい |
sales_report.yaml | report | 売上明細表 | 一覧の印刷版が欲しい。顧客ごとに小計を出して、CSV も落としたい、紙にも刷りたい |
sales_app.yaml | app | 販売管理 | 複数の画面をメニューで束ねて、1つのアプリにしたい。会社の色にもしたい |
roles_app.yaml | app | 人事管理 | 見せる相手を役割で変えたい(画面ごと隠す・列や項目だけ隠す・押せる人を絞る) |
よくある間違い
ページ直下に filters を書く
なぜ駄目か 検索条件は search の持ち物。ページ直下の filters は無い(ダッシュボードのカードの filters は「固定の絞り込み値」で別物)。
こう直す search.filters に入れる。
page:
type: search
id: order_search
title: 受注照会
repository: orderRepository
filters:
- { field: status, label: 状態 }page:
type: search
id: order_search
title: 受注照会
repository: orderRepository
search:
filters:
- { field: status, label: 状態 }合計を出したいのに value を省く
なぜ駄目か value を省いた metric カードは 件数(count)。金額を足したいのに件数が出る、という間違いは画面を見ても気づきにくい。
こう直す value: { aggregate: sum, field: amount } のように、畳み込み方と対象項目を書く。count 以外は field が必須。
page:
type: dashboard
id: sales_dashboard
title: 売上ダッシュボード
repository: orderRepository
items:
- { id: orderCount, title: 受注件数 }
- id: total
title: 受注金額
value: { aggregate: sum, field: amount }
format: currencyspec/pitfalls.json から生成。各項目は CI で検証済み(間違いは本当に落ち、正しい方は本当に通る)。手元では npx hatake pitfalls <キー名>。
実物を見る
デモアプリの「売上ダッシュボード」がこれを使っている。 デモを開く