SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

ComponentZine(ActiveReports)byメシウス(AD)

Firebase × Next.js × Wijmo × ActiveReportsJSでつくる、現場が自走できる業務システム

 「帳票のレイアウトを少し直したいのに、開発会社に依頼すると時間もコストもかかる」といった話は、業務システムの現場でよく耳にする悩みです。本シリーズでは、WijmoとActiveReportsJSを組み合わせ、現場担当者が自分で帳票を修正できる仕組みを実現します。

対象読者

  • Next.jsで業務システムを開発している方
  • 帳票機能の組み込みを検討している方
  • WijmoやActiveReportsJSに興味がある方

前提環境

 筆者の検証環境は以下の通りです。

  • macOS Tahoe 16.1
  • Node.js 25.2.1
  • npm 11.6.2
  • Next.js 16.1.3
  • React 19.2.3
  • Wijmo 5.20252.44
  • ActiveReportsJS 6.0.1
  • Firebase 12.8.0

はじめに

 業務システムの開発において、データの一覧表示や帳票の作成・確認は重要な要素です。Reactベースのフロントエンドでは、Wijmoが提供する豊富なUIコンポーネントを活用することで、効率的に業務アプリケーションを構築できます。また、帳票機能にはActiveReportsJSを組み合わせることで、柔軟なレイアウト設計と現場での編集が可能です。この組み合わせにより、見た目の品質を担保しつつ、開発現場はビジネスロジックに集中できます。

 本連載では、メシウスが提供する高機能UIコントロールを多数備えたJavaScript開発ライブラリ「Wijmo」と、JavaScript帳票ライブラリである「ActiveReportsJS」を軸に、業務システムの基盤構築から帳票連携までのステップを解説します。アプリケーションフレームワークとしては、ReactでのデファクトスタンダードとなっているNext.jsを採用します。データベースとしてはサンプルにFirebase Firestoreを採用していますが、Firestore特有の機能は使いませんので、現場では任意のデータベースを利用して構いません。連載の全体像としては、次の順序で解説します。

  1. WijmoとActiveReportsJSを組み合わせた業務システムの基盤構築(本記事)
  2. Firestoreデータとの連携とWijmoからActiveReportsJSへのデータフローの実装
  3. レポートパーツやマスターレポートを活用した帳票の部品化と、ActiveReportsJS Designerによる現場での帳票編集機能の実現

 本記事では、第1回としてNext.jsアプリケーションにWijmoとActiveReportsJS Viewerを組み込み、仮データを使ってUIの表示ができることを目指します。

プロジェクト構成の概要

 今回構築する受発注管理システムは、商品・取引先・商品分類のマスタ管理と、受発注データの管理を行います。主要なデータモデルとして、Product(商品)、Partner(取引先)、Category(商品分類)、Order(受発注)を定義しています(リスト1)。

[リスト1]src/lib/firebase/types.ts(データモデル抜粋)
export interface Product {
  id: string;           // FirestoreドキュメントID
  code: string;         // 商品コード(表示用)
  name: string;
  categoryId: string;   // 商品分類への参照
  price: number;
  stock: number;
  unit: string;         // 単位(個、箱、kgなど)
  …中略…
}

export interface Partner {/* 省略 */}

export interface Category {/* 省略 */}

export interface Order {
  id: string;
  orderNumber: string;            // 伝票番号
  type: "purchase" | "sales";     // 発注 or 受注
  partnerId: string;
  partnerName: string;            // 非正規化した取引先名
  …中略…
  items: OrderItem[];             // 明細行の配列
  totalAmount: number;
  …中略…
}

 これらのデータモデルは、Firestoreのドキュメント構造に対応しています。partnerNameのように一部のフィールドに非正規化した値を持たせることで、一覧表示時のクエリを効率化しています。

Wijmoの組み込み

 WijmoをNext.jsに組み込む際の最大のポイントは、SSR(サーバーサイドレンダリング)の無効化です。Wijmoはクライアント側で実行されることを前提としたライブラリのため、サーバー側で実行するとエラーになります。"use client"ディレクティブとdynamic関数を組み合わせて対応します(リスト2)。

[リスト2]src/app/products/page.tsx(SSR無効化パターン)
"use client";  // クライアントコンポーネントとして宣言

import dynamic from "next/dynamic";

const ProductsGrid = dynamic(
  () => import("@/components/wijmo/ProductsGrid"),
  { ssr: false }  // サーバーサイドレンダリングを無効化
);

 この2段階のアプローチ("use client"とdynamic import)により、Wijmoコンポーネントはブラウザでのみロードされるようになります。ライセンス設定は、環境変数から読み込んでsetLicenseKey()で適用します(リスト3)。NEXT_PUBLIC_プレフィックスにより、クライアントサイドからアクセス可能になります。

[リスト3]src/components/wijmo/WijmoLicense.tsx
"use client";

import { setLicenseKey } from "@mescius/wijmo";

// 環境変数からライセンスキーを取得
const licenseKey = process.env.NEXT_PUBLIC_WIJMO_LICENSE_KEY;
if (licenseKey) {
  setLicenseKey(licenseKey);  // アプリケーション全体に適用
}

 このコンポーネントをルートレイアウトで読み込むことで、すべてのWijmoコンポーネントにライセンスが適用されます。評価版の場合はこの設定を省略できます。

 Wijmoは多言語対応しており、日本語カルチャを適用することで日本語表示が可能になります。言語設定は「カルチャ」と呼ばれており、日本語向けの設定はコンポーネントで@mescius/wijmo.cultures/wijmo.culture.jaをインポートするだけで適用されます。

業務アプリ向けUIコンポーネント

 Wijmoには業務システム向けのUIコンポーネントが豊富に用意されています。ここでは、本サンプルアプリケーションで使用する4つのコンポーネントを紹介します。ダウンロードサンプルでは、それぞれ以下のパスで実装を確認できます。

  • FlexGrid(商品一覧): src/components/wijmo/ProductsGrid.tsx
  • MultiRow(受発注入力): src/components/wijmo/OrdersMultiRow.tsx
  • TreeView(商品分類): src/components/wijmo/CategoriesTreeView.tsx
  • Accordion(管理メニュー): src/components/wijmo/AdminAccordion.tsx

 FlexGridはExcelライクな操作感を持つデータグリッド、MultiRowは1レコードを複数行で表現する伝票形式のグリッド、TreeViewは階層構造のツリー表示、Accordionは折りたたみ可能なパネルUIです。各コンポーネントの詳しいプロパティ設定やカスタマイズについては、次回以降Firestoreとの連携を進める中で随時解説します。

FlexGrid:商品一覧

 FlexGridは、Excelライクな操作感を持つデータグリッドです。ソート、フィルタ、インライン編集といった機能を標準で備えています。データソースにはWijmoのCollectionViewを使用します。CollectionViewは配列データをラップし、ソート・フィルタ・変更追跡といった機能をグリッドに提供するオブジェクトです。 

 リスト4では、useEffect内でCollectionViewのインスタンス(cv)を生成し、setView(cv)でReactのstate変数viewにセットしています。FlexGridのitemsSourceプロパティにはこのviewをバインドすることで、Reactのライフサイクルに沿ったデータ管理が実現できます(リスト4)。

[リスト4]src/components/wijmo/ProductsGrid.tsx(抜粋)
import { useState, useEffect } from "react";
import { FlexGrid, FlexGridColumn } from "@mescius/wijmo.react.grid";
import { FlexGridFilter } from "@mescius/wijmo.react.grid.filter";
import { CollectionView } from "@mescius/wijmo";

const [view, setView] = useState<CollectionView | null>(null); 

useEffect(() => { 
  // CollectionViewでデータソースを作成
  const cv = new CollectionView(products, { 
    trackChanges: true,  // データの変更を追跡 
  });
  setView(cv);  // state変数viewにセット 
}, [products]);

…中略…

<FlexGrid
  itemsSource={view}           // CollectionViewをバインド
  alternatingRowStep={1}       // 1行おきに背景色を変える
  …中略…
>
  {/* フィルタ機能を追加 */}
  <FlexGridFilter />

  {/* カラム定義 */}
  <FlexGridColumn binding="code" header="商品コード" width={100} />
  {/* widthに*を指定することで、残り幅を自動調整 */}
  <FlexGridColumn binding="name" header="商品名" width="*" />
  …中略…
  {/* 金額バインディングのとき、format属性で通貨形式を指定できる */}
  <FlexGridColumn binding="price" header="単価" format="c0" />
  …中略…
</FlexGrid>

 FlexGridFilterを子コンポーネントとして配置するだけで、カラムヘッダーにフィルタ機能が追加されます。bindingプロパティでデータソースのフィールド名を、formatプロパティで表示形式を指定します(図1)。

図1:FlexGridによる商品一覧画面
図1:FlexGridによる商品一覧画面

 図1のように、各カラムヘッダーにフィルタアイコンが表示されます。カラムヘッダーをクリックするとソートが、フィルタアイコンをクリックすると条件指定による絞り込みが可能です。これらの機能は追加のコードなしで利用できます。

 図2は、商品名カラムのフィルタアイコンをクリックした際に表示されるフィルタダイアログの例です。値フィルタと条件フィルタの2種類が用意されており、チェックボックスの切り替えや条件式の指定で直感的にデータを絞り込めます。

図2:FlexGridのフィルタダイアログ

図2:FlexGridのフィルタダイアログ

 FlexGridでは、DataMapを使ってセルにコンボボックス形式のカスタムエディタを設定できます。DataMapはキーと表示名のマッピングを提供するオブジェクトで、FlexGridColumndataMapプロパティに指定します。これにより、ユーザーはドロップダウンから値を選択でき、内部的にはキー値(ID)が保持されます(リスト5)。

[リスト5]src/components/wijmo/ProductsGrid.tsx(カスタムエディタ設定抜粋)
import { DataMap } from "@mescius/wijmo.grid";

// カテゴリマスタからDataMapを作成(idをキー、nameを表示名に)
const categoryMap = useMemo(
  () => new DataMap(categories, "id", "name"),
  [categories]
);
// 文字列配列を渡すだけでもDataMapとして機能する
const unitOptions = ["個", "箱", "本", "袋", "缶", "切", "冊", "台", "セット", "100g", "kg"];

…中略…

{/* dataMap属性でコンボボックスエディタを設定 */}
<FlexGridColumn binding="categoryId" header="分類" width={120} dataMap={categoryMap} />
<FlexGridColumn binding="unit" header="単位" width={80} dataMap={unitOptions} />

 分類列のセルをクリックすると、図3のようにカテゴリ一覧がドロップダウンで表示されます。DataMapによりIDと表示名が自動変換されるため、表示用の変換処理を別途書く必要がありません。

図3:FlexGridのカスタムエディタ(コンボボックス)

図3:FlexGridのカスタムエディタ(コンボボックス)

 FlexGridのカスタムエディタには、コンボボックスの他にもInputMaskによるマスク入力があります。取引先一覧(PartnersGrid.tsx)の電話番号列を例に見てみましょう(リスト6)。

[リスト6]src/components/wijmo/PartnersGrid.tsx(マスクエディタ設定抜粋)
import { InputMask } from "@mescius/wijmo.input";

// InputMaskエディタを作成(数字3桁-4桁-4桁の電話番号マスク)
useEffect(() => {
      const editor = new InputMask(document.createElement("div"), {
            mask: "000-0000-0000",  // 0は数字1桁、ハイフンはリテラル文字
  });
  setPhoneEditor(editor);
  return () => editor.dispose();
}, []);

…中略…

<FlexGridColumn binding="phone" header="電話番号" width={130} editor={phoneEditor} />

 InputMaskmaskプロパティで0は数字1桁を表し、ハイフンはリテラル文字として自動挿入されます。InputMaskInputDateと同様に@mescius/wijmo.inputパッケージに含まれるコントロールです。

図4:FlexGridのカスタムエディタ(マスク入力)

図4:FlexGridのカスタムエディタ(マスク入力)

MultiRow:受発注入力

 MultiRowは、1レコードを複数行で表示するコンポーネントです。受発注データのように項目が多い場合に有効です。layoutDefinitionで行のレイアウトを定義します。各グループのcolspanプロパティで、そのグループが何列分の幅を占めるかを指定します。グループ内のcells配列に定義したセルは、colspanの列数に収まるように自動的に複数行に折り返されます。個々のセルにもcolspanを指定でき、備考欄のように複数列にまたがるセルを作れます(リスト7)。

[リスト7]src/components/wijmo/OrdersMultiRow.tsx(抜粋)
const layoutDefinition = [
  {
    header: "伝票情報",    // グループヘッダー
    colspan: 3,           // このグループは3列分の幅
    cells: [              // 3列×2行に自動配置される
      { binding: "orderNumber", header: "伝票番号", width: 120 },
      { binding: "type", header: "区分", width: 70 },
      { binding: "status", header: "ステータス", width: 90 },
      { binding: "orderDate", header: "発注日", width: 100, format: "yyyy/M/d" },
      { binding: "deliveryDate", header: "納期", width: 100, format: "yyyy/M/d" },
      // colspanで2列分の幅を指定
      { binding: "notes", header: "備考", width: 150, colspan: 2 },
    ],
  },
  {
    header: "取引先・金額",
    colspan: 2,           // このグループは2列分の幅
    cells: [
      { binding: "partnerName", header: "取引先名", width: 180 },
      { binding: "itemCount", header: "明細数", width: 70, align: "right" },
      { binding: "totalAmount", header: "合計金額", width: 120, format: "c0", align: "right" },
    ],
  },
];

…中略…

<MultiRow
  itemsSource={view}
  layoutDefinition={layoutDefinition}  // 複数行レイアウトを適用
  …中略…
/>

 1レコードが2行で表示され、伝票情報と金額情報が見やすく配置されます(図5)。

図5:MultiRowによる受発注入力画面
図5:MultiRowによる受発注入力画面

 通常のグリッドでは横スクロールが必要になる項目数でも、MultiRowなら1画面に収まります。業務システムでよくある「伝票形式」の表示に最適です。

 MultiRowでも、FlexGridと同様にDataMapを使ったコンボボックスや、InputDateを使ったカレンダーピッカーをカスタムエディタとして設定できます。layoutDefinitionの各セル定義にdataMapeditorプロパティを追加するだけで、セルごとに適切な入力UIを提供できます(リスト8)。

[リスト8]src/components/wijmo/OrdersMultiRow.tsx(カスタムエディタ設定抜粋)
import { DataMap } from "@mescius/wijmo.grid";
import { InputDate } from "@mescius/wijmo.input";

// 区分・ステータスのDataMapを定義
const typeDataMap = useMemo(() => new DataMap(
  [{ key: "purchase", name: "発注" }, { key: "sales", name: "受注" }],
  "key", "name"
), []);

const statusDataMap = useMemo(() => new DataMap([
  { key: "draft", name: "下書き" }, { key: "confirmed", name: "確定" },
  { key: "shipped", name: "出荷済" }, { key: "completed", name: "完了" },
  { key: "cancelled", name: "キャンセル" },
], "key", "name"), []);

// InputDateエディタを作成
useEffect(() => {
  const editor = new InputDate(document.createElement("div"), {
    format: "yyyy/M/d",
  });
  setDateEditor(editor);
  return () => editor.dispose();
}, []);

// layoutDefinitionのセルにdataMapとeditorを設定
{ binding: "type", header: "区分", width: 70, dataMap: typeDataMap },
{ binding: "status", header: "ステータス", width: 90, dataMap: statusDataMap },
{ binding: "orderDate", header: "発注日", width: 100, format: "yyyy/M/d", editor: dateEditor },
{ binding: "deliveryDate", header: "納期", width: 100, format: "yyyy/M/d", editor: dateEditor },

 DataMapにより、内部値("purchase""draft")が自動的に表示名(「発注」「下書き」)に変換されるため、表示用のデータ変換処理が不要になります。また、日付列ではカレンダーピッカーから直感的に日付を選択できます(図6)。

図6:MultiRowのカスタムエディタ(カレンダーピッカー)
図6:MultiRowのカスタムエディタ(カレンダーピッカー)

TreeView:商品分類

 TreeViewは、階層構造のデータを表示するコンポーネントです。まず、表示するデータの構造を確認しましょう。商品分類データは、以下のように親子関係を持つ再帰的なツリー構造で定義されています(リスト9)。

[リスト9]src/lib/mockData.ts(商品分類データ抜粋)
// CategoryTree型: Categoryを継承し、items?: CategoryTree[] を追加した再帰構造
const mockCategoryTree: CategoryTree[] = [
  {
    id: "cat1",
    name: "食品",
    …中略…
    items: [
      {
        id: "cat1-1",
        name: "生鮮食品",
        …中略…
        items: [
          { id: "cat1-1-1", name: "野菜", …中略… },
          { id: "cat1-1-2", name: "果物", …中略… },
          …中略…
        ],
      },
      { id: "cat1-2", name: "加工食品", …中略…, items: [ …中略… ] },
      { id: "cat1-3", name: "飲料", …中略…, items: [ …中略… ] },
    ],
  },
  { id: "cat2", name: "日用品", …中略…, items: [ …中略… ] },
  { id: "cat3", name: "事務用品", …中略…, items: [ …中略… ] },
];

 このツリーデータをTreeViewdisplayMemberPathchildItemsPathで表示します。displayMemberPathで表示する項目名(ここではname)、childItemsPathで子要素の配列(ここではitems)を指定します(リスト10)。

[リスト10]src/components/wijmo/CategoriesTreeView.tsx(抜粋)
<TreeView
  itemsSource={categories}
  displayMemberPath="name"                    // 表示するプロパティ名
  childItemsPath="items"                      // 子要素の配列プロパティ名
  selectedItemChanged={handleSelectedItemChanged}  // 選択変更時のコールバック
  …中略…
  isAnimated={true}                           // 展開/折りたたみをアニメーション
/>

 階層構造がツリー形式で表示され、クリックで展開・折りたたみができます。図7の左側の表示がTreeViewです。

図7:TreeViewによる商品分類画面
図7:TreeViewによる商品分類画面

 商品分類のような親子関係を持つデータを、視覚的に分かりやすく表示できます。分類を選択したときに呼び出されるhandleSelectedItemChangedコールバックの実装は以下の通りです(リスト11)。

[リスト11]src/components/wijmo/CategoriesTreeView.tsx(コールバック抜粋)
const [selectedCategory, setSelectedCategory] = useState<CategoryTree | null>(null);

const handleSelectedItemChanged = (sender: TreeView) => {
  const item = sender.selectedItem as CategoryTree | null;
  setSelectedCategory(item);
};

 sender.selectedItemで現在選択されているアイテムを取得し、Reactのstateに保存しています。これにより、図7の右側パネルのように選択した分類の詳細情報(分類ID、分類名、子分類数)を表示できます。このパターンは、マスタ選択UIとして幅広く活用できます。

Accordion:マスタ管理メニュー

 Accordionは、折りたたみ可能なメニューを作成するコンポーネントです。まず、メニューデータの構造を見てみましょう(リスト12)。

[リスト12]src/components/wijmo/AdminAccordion.tsx(メニューデータ抜粋)
interface MenuItem {
  label: string;
  href: string;
  description: string;
}

interface MenuSection {
  title: string;
  icon: string;
  items: MenuItem[];
}

const menuSections: MenuSection[] = [
  {
    title: "マスタ管理",
    icon: "📁",
    items: [
      { label: "商品マスタ", href: "/products", description: "商品の登録・編集・削除" },
      { label: "取引先マスタ", href: "/partners", description: "取引先の登録・編集・削除" },
      { label: "商品分類マスタ", href: "/categories", description: "商品カテゴリの階層管理" },
    ],
  },
  …中略…  // 受発注管理、帳票出力、システム設定の3セクションが続く
];

 allowExpandManyfalseにすると、一度に開けるパネルを1つに制限できます(リスト13)。

[リスト13]src/components/wijmo/AdminAccordion.tsx(抜粋)
<Accordion
  allowExpandMany={false}  // 同時に1つのパネルのみ展開可能
  isAnimated={true}
>
  {menuSections.map((section) => (
    <AccordionPane key={section.title}>
      <div>{section.title}</div>     {/* パネルヘッダー */}
      <ul>{/* メニュー項目 */}</ul>  {/* パネルコンテンツ */}
    </AccordionPane>
  ))}
</Accordion>

 セクションをクリックすると、該当するメニューが展開されます(図8)。

図8:Accordionによるマスタ管理メニュー
図8:Accordionによるマスタ管理メニュー

 マスタ管理、受発注管理、帳票出力といった機能カテゴリをAccordionで整理することで、多機能なシステムでもナビゲーションが煩雑になりません。

ActiveReportsJS Viewerの組み込み

 ActiveReportsJS ViewerもWijmoと同様、SSR無効化が必要です。また、必須のCSSファイルを2つインポートします(リスト14)。

[リスト14]src/components/reports/ReportViewer.tsx(抜粋)
"use client";

// 必須のスタイルシート
import "@mescius/activereportsjs/styles/ar-js-ui.css";
import "@mescius/activereportsjs/styles/ar-js-viewer.css";

const initViewer = async () => {
  // 日本語ロケールを動的インポート
  await import("@mescius/activereportsjs-i18n");

  // Viewerモジュールを動的インポート
  const ArViewer = await import("@mescius/activereportsjs/reportviewer");

  // DOM要素にViewerをマウント
  viewerRef.current = new ArViewer.Viewer(hostRef.current);

  // レポート定義ファイルを開く
  await viewerRef.current.open(reportUri);
};

 @mescius/activereportsjs-i18nをインポートすることで、ViewerのUIが日本語化されます(図9)。

図9:ActiveReportsJS Viewerによる帳票表示
図9:ActiveReportsJS Viewerによる帳票表示

 ViewerのツールバーからPDF出力や印刷が可能です。ページ送り、ズーム、検索といった機能も標準で備わっており、業務帳票の閲覧に必要な機能が揃っています。

まとめ

 本記事では、Next.js + Firebase + Wijmo + ActiveReportsJSの4つの技術スタックを導入し、受発注管理システムの基盤を構築しました。SSR無効化のパターンは、両ライブラリで共通して適用できます。次回は、FirestoreのデータをActiveReportsJSにバインドし、WijmoのFlexGridで選択した行を帳票に表示する連携パターンを解説します。

この記事は参考になりましたか?

提供:メシウス株式会社

【AD】本記事の内容は記事掲載開始時点のものです 企画・制作 株式会社翔泳社

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/23423 2026/03/30 12:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング