メインコンテンツまでスキップ

マスター用フロントパッケージ

MBC CQRS Serverlessアプリケーションでマスターデータと設定を管理するためのフロントエンドコンポーネントライブラリです。

インストール

npm install @mbc-cqrs-serverless/master-web

クイックスタート(推奨セットアップ)

ここから始める

これは master-web を Next.js App Router と統合する推奨方法です。 このパターンに従うことで、httpClient.get is not a function などの一般的な問題を回避できます。

このライブラリを Next.js App Router (v14+/v15) で使用する場合は、Layout ベースの Provider パターンを使用してください。layout.tsx ファイルで AppProviders をセットアップし、page.tsx ファイルではコンポーネントの動的インポートを使用します。

ステップ 1: layout.tsx を作成

Provider をセットアップするレイアウトファイルを作成します。これにより、子コンポーネントがマウントされる前にコンテキストが適切に初期化されます。

// app/admin/[tenant]/master/layout.tsx
'use client'

import { useMemo } from 'react'
import dynamic from 'next/dynamic'
import { useParams } from 'next/navigation'
import axios from 'axios'
import { fetchAuthSession } from 'aws-amplify/auth'
import type { IUrlProvider } from '@mbc-cqrs-serverless/master-web/UrlProvider'

// Dynamic import of AppProviders (SSR disabled) (AppProviders の動的インポート、SSR 無効)
const AppProviders = dynamic(
() =>
import('@mbc-cqrs-serverless/master-web/AppProviders').then(
(mod) => mod.AppProviders
),
{ ssr: false }
)

// Custom URL provider for your application's routing (アプリケーションのルーティング用カスタム URL プロバイダー)
class MasterUrlProvider implements IUrlProvider {
protected readonly baseUrl: string
public readonly SETTINGS_PAGE_URL: string
public readonly ADD_SETTINGS_PAGE_URL: string
public readonly EDIT_SETTINGS_PAGE_URL: string
public readonly DATA_PAGE_URL: string
public readonly ADD_DATA_PAGE_URL: string
public readonly EDIT_DATA_PAGE_URL: string
public readonly FAQ_CATEGORY_PAGE_URL: string
public readonly TOP_URL: string

constructor(tenantCode: string) {
this.baseUrl = `/admin/${tenantCode}/master`
this.SETTINGS_PAGE_URL = `${this.baseUrl}/master-setting`
this.ADD_SETTINGS_PAGE_URL = `${this.baseUrl}/master-setting/new`
this.EDIT_SETTINGS_PAGE_URL = this.SETTINGS_PAGE_URL
this.DATA_PAGE_URL = `${this.baseUrl}/master-data`
this.ADD_DATA_PAGE_URL = `${this.baseUrl}/master-data/new`
this.EDIT_DATA_PAGE_URL = this.DATA_PAGE_URL
this.FAQ_CATEGORY_PAGE_URL = `${this.baseUrl}/faq-category`
this.TOP_URL = `/admin/${tenantCode}`
}

public getCopySettingPageUrl(id: string): string {
return `${this.baseUrl}/master-setting/${id}/copy/new`
}
public getDetailedCopySettingPageUrl(id: string): string {
return `${this.baseUrl}/master-setting/${id}/copy`
}
}

export default function MasterLayout({ children }: { children: React.ReactNode }) {
const params = useParams<{ tenant: string }>()
const tenantCode = params?.tenant || 'common'

const urlProvider = useMemo(() => new MasterUrlProvider(tenantCode), [tenantCode])

// Create httpClient with Axios interceptor for automatic auth token injection (自動認証トークン注入のためのAxiosインターセプター付きhttpClientを作成)
const httpClient = useMemo(() => {
const baseEndpoint = process.env.NEXT_PUBLIC_API_ENDPOINT || 'http://localhost:3010'
const instance = axios.create({
baseURL: `${baseEndpoint}/api`,
headers: {
'Content-Type': 'application/json',
'x-tenant-code': tenantCode,
},
})

instance.interceptors.request.use(async (config) => {
try {
const session = await fetchAuthSession()
const token = session.tokens?.idToken?.toString()
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
} catch {
// Ignore auth errors (認証エラーを無視)
}
return config
})

return instance
}, [tenantCode])

const user = useMemo(() => ({ tenantCode, tenantRole: 'admin' }), [tenantCode])

return (
<AppProviders user={user} urlProvider={urlProvider} httpClient={httpClient}>
<div className="p-6">{children}</div>
</AppProviders>
)
}

ステップ 2: page.tsx を作成

レイアウトでプロバイダーをセットアップした後、各ページコンポーネントはシンプルになります:

// app/admin/[tenant]/master/master-setting/page.tsx
'use client'

import dynamic from 'next/dynamic'
import MsLayout from '@mbc-cqrs-serverless/master-web/MsLayout'
import '@mbc-cqrs-serverless/master-web/styles.css'

const MasterSetting = dynamic(
() => import('@mbc-cqrs-serverless/master-web/MasterSetting').then((mod) => mod.default),
{ ssr: false }
)

export default function MasterSettingPage() {
return (
<main>
<MsLayout useLoading>
<MasterSetting />
</MsLayout>
</main>
)
}

ステップ 3: 環境変数を設定

# .env.local
NEXT_PUBLIC_API_ENDPOINT=http://localhost:3010
NEXT_PUBLIC_MASTER_APPSYNC_URL=https://xxxxxxxx.appsync-api.ap-northeast-1.amazonaws.com/graphql
NEXT_PUBLIC_MASTER_APPSYNC_APIKEY=da2-xxxxxxxxxxxxxxxxx
NEXT_PUBLIC_MASTER_APPSYNC_REGION=ap-northeast-1

なぜこのパターンを使うのか?

メリット説明
コンテキストの分離を回避npmパッケージのReact Contextは分離されることがあります。Layoutを使用すると、コンテキストが最初に初期化されることが保証されます。
同期的な初期化useMemoを使用することで、httpClientが同期的に作成され、競合状態を回避できます。
認証トークンの自動注入Axiosインターセプターがリクエストごとに最新の認証トークンを注入します。
シンプルなページコンポーネントページは動的インポートとコンポーネントのレンダリングだけで済みます。
詳細について

代替パターン、トラブルシューティング、詳細な説明についてはNext.js App Router統合を参照してください。

概要

Master Webパッケージ(@mbc-cqrs-serverless/master-web)は、マスターデータと設定を管理するための完全なReactコンポーネントセットを提供します。バックエンドのMaster Serviceとシームレスに統合され、構築済みのページ、フォーム、データテーブルが含まれています。

機能

  • マスター設定管理: マスター設定の表示、作成、編集、削除
  • マスターデータ管理: マスターデータレコードのCRUD操作
  • リッチテキストエディタ: コンテンツフィールド用の組み込みリッチテキストエディタ
  • JSONエディタ: 構造化データフィールド用のJSONエディタ
  • データテーブル: TanStack Tableによるソート・ページネーション対応テーブル
  • リアルタイム更新: AWS AppSync統合によるリアルタイムデータ同期
  • コピー機能: マスター設定とデータのクローン

主要コンポーネント

インポートオプション

コンポーネントはメインパッケージまたはサブパスインポートからインポートできます:

// Main package import (メインパッケージインポート)
import { MasterSetting } from "@mbc-cqrs-serverless/master-web";

// Sub-path import (サブパスインポート)
import MasterSetting from "@mbc-cqrs-serverless/master-web/MasterSetting";

MasterSetting

検索、フィルター、ページネーション機能を備えたマスター設定一覧を表示します。

import { MasterSetting } from "@mbc-cqrs-serverless/master-web";
import "@mbc-cqrs-serverless/master-web/styles.css";

export default function MasterSettingsPage() {
return <MasterSetting />;
}

EditMasterSettings

マスター設定の作成・編集用フォームコンポーネント。

import { EditMasterSettings } from "@mbc-cqrs-serverless/master-web";

export default function EditMasterSettingsPage({ params }: { params: { id: string } }) {
return <EditMasterSettings id={params.id} />;
}

CopyMasterSettings

既存の設定に基づいて新しい設定を作成するためのマスター設定コピーコンポーネント。

import { CopyMasterSettings } from "@mbc-cqrs-serverless/master-web";

export default function CopyMasterSettingsPage({ params }: { params: { id: string } }) {
return <CopyMasterSettings id={params.id} />;
}

NewCopyMasterSettings

新しい識別子でマスター設定の新しいコピーを作成するためのコンポーネント。

import { NewCopyMasterSettings } from "@mbc-cqrs-serverless/master-web";

export default function NewCopyMasterSettingsPage({ params }: { params: { id: string } }) {
return <NewCopyMasterSettings id={params.id} />;
}

DetailCopy

マスター設定のコピー詳細情報を表示するためのコンポーネント。

import { DetailCopy } from "@mbc-cqrs-serverless/master-web";

export default function DetailCopyPage({ params }: { params: { id: string } }) {
return <DetailCopy id={params.id} />;
}

MasterData

CRUD操作機能付きでマスターデータレコードをテーブル形式で表示します。

import { MasterData } from "@mbc-cqrs-serverless/master-web";

export default function MasterDataPage() {
return <MasterData />;
}

EditMasterData

マスターデータレコードの作成・編集用フォームコンポーネント。対応するマスター設定のattributes.fieldsfields定義に基づいて、フォームコントロールを自動的にレンダリングします。

コンポーネントは常にcodenameを固定フィールドとして表示します。attributes.fieldsに定義された追加フィールド(codename以外)は、uiComponentタイプに基づいてカスタムフォームコントロールとしてレンダリングされます。

import { EditMasterData } from "@mbc-cqrs-serverless/master-web";

export default function EditMasterDataPage({ params }: { params: { id: string } }) {
return <EditMasterData id={params.id} />;
}

AddJsonData

JSON経由でマスター設定とマスターデータを一括インポートするためのJSONエディタコンポーネント。このコンポーネントはEditMasterSettingsのJSONインポートタブで内部的に使用されます。

デフォルトでは新規作成のみ

AddJsonDataはハードコードされたAPI URL(設定用の/master-setting/bulk、データ用の/master-data/bulk)を使用し、フレームワークのcreateBulkメソッドを呼び出します。そのため、既存レコードのJSONデータを再インポートするとBadRequestExceptionで失敗します。upsert動作をサポートするには、AxiosインターセプターでこれらのURLをカスタムupsertエンドポイントに書き換えてください。詳細はMaster - Upsertパターンを参照してください。

プロバイダーのセットアップ

認証とAPIアクセスに必要なプロバイダーでアプリケーションをラップします。

AppProviders

AppProvidersコンポーネントは、テナント情報を含むUserContext型のuserプロパティが必要です。

import { AppProviders } from "@mbc-cqrs-serverless/master-web";
import type { UserContext } from "@mbc-cqrs-serverless/master-web";

export default function RootLayout({ children }: { children: React.ReactNode }) {
// UserContext contains tenant information (UserContextはテナント情報を含む)
const user: UserContext = {
tenantCode: "your-tenant-code",
tenantRole: "admin",
};

return (
<AppProviders user={user}>
{children}
</AppProviders>
);
}

AppProviders プロパティ

プロパティ必須説明
userUserContextはいテナント情報を含むユーザーコンテキスト
httpClientAxiosInstanceいいえHTTPリクエスト用のカスタムAxiosインスタンス
apolloClientApolloClientいいえカスタムApollo Clientインスタンス
urlProviderIUrlProviderいいえカスタムURLプロバイダーインスタンス

UserContext 型

UserContext型はユーザーオブジェクトの形状を定義します。useUserContextフックの戻り値の型を使用するか、互換性のある型を定義できます:

type UserContext = {
tenantCode: string; // Tenant identifier (テナント識別子)
tenantRole: string; // User role within the tenant (テナント内のユーザーロール)
};
型の使用方法

UserContextは内部的に使用されますが、userプロパティ用に互換性のあるオブジェクト型を作成できます。

URLプロバイダー

このパッケージは、アプリケーションURLを管理するためのURLプロバイダーシステムを提供します。

IUrlProvider インターフェース

IUrlProviderインターフェースはURL生成のコントラクトを定義します:

import type { IUrlProvider } from "@mbc-cqrs-serverless/master-web";

// Interface definition (インターフェース定義)
interface IUrlProvider {
// Static URLs (静的URL)
readonly SETTINGS_PAGE_URL: string;
readonly ADD_SETTINGS_PAGE_URL: string;
readonly EDIT_SETTINGS_PAGE_URL: string;
readonly DATA_PAGE_URL: string;
readonly ADD_DATA_PAGE_URL: string;
readonly EDIT_DATA_PAGE_URL: string;
readonly FAQ_CATEGORY_PAGE_URL: string;
readonly TOP_URL: string;

// Dynamic URL generators (動的URLジェネレーター)
getCopySettingPageUrl(id: string): string;
getDetailedCopySettingPageUrl(id: string): string;
}

BaseUrlProvider クラス

BaseUrlProviderクラスは拡張可能なデフォルト実装を提供します:

import { BaseUrlProvider, IUrlProvider } from "@mbc-cqrs-serverless/master-web/UrlProvider";

// Create a URL provider with a base segment (ベースセグメントでURLプロバイダーを作成)
const urlProvider = new BaseUrlProvider("my-tenant");

// Access static URLs (静的URLにアクセス)
console.log(urlProvider.SETTINGS_PAGE_URL); // "/my-tenant/master-setting"
console.log(urlProvider.DATA_PAGE_URL); // "/my-tenant/master-data"

// Generate dynamic URLs (動的URLを生成)
console.log(urlProvider.getCopySettingPageUrl("123")); // "/my-tenant/master-setting/123/copy/new"
サブパスインポート

BaseUrlProviderIUrlProviderはサブパスインポート@mbc-cqrs-serverless/master-web/UrlProviderから利用できます。IUrlProvider型はメインパッケージからもエクスポートされています。

カスタムURLプロバイダー

BaseUrlProviderを拡張するか、IUrlProviderインターフェースを実装してカスタムURLプロバイダーを作成できます:

import { AppProviders } from "@mbc-cqrs-serverless/master-web";
import { BaseUrlProvider } from "@mbc-cqrs-serverless/master-web/UrlProvider";

// Extend BaseUrlProvider for custom path structure (カスタムパス構造のためにBaseUrlProviderを拡張)
class CustomUrlProvider extends BaseUrlProvider {
constructor(tenantCode: string) {
super(`members/${tenantCode}`);
}
}

// Use custom URL provider with AppProviders (AppProvidersでカスタムURLプロバイダーを使用)
const customUrlProvider = new CustomUrlProvider("my-tenant");

<AppProviders user={user} urlProvider={customUrlProvider}>
{children}
</AppProviders>

カスタムフック

useApolloClient

GraphQL操作用のApollo Clientにアクセスします。

import { useApolloClient } from "@mbc-cqrs-serverless/master-web";

function MyComponent() {
const client = useApolloClient();
// Use client for custom GraphQL queries
}

useHttpClient

REST API呼び出し用のHTTPクライアントにアクセスします。

import { useHttpClient } from "@mbc-cqrs-serverless/master-web";

function MyComponent() {
const httpClient = useHttpClient();
// Use httpClient for custom API requests
}

useUserContext

テナント情報を含む現在のユーザーコンテキストにアクセスします。

import { useUserContext } from "@mbc-cqrs-serverless/master-web";

function MyComponent() {
const { tenantCode, tenantRole } = useUserContext();
// Access tenant information (テナント情報にアクセス)
console.log(`Tenant: ${tenantCode}, Role: ${tenantRole}`);
}

useLoadingStore

コンポーネント間でグローバルなローディング状態を管理します。

import { useLoadingStore } from "@mbc-cqrs-serverless/master-web";

function MyComponent() {
const { isLoading, setLoading, closeLoading } = useLoadingStore();

// Show loading indicator (ローディングインジケーターを表示)
setLoading();

// Hide loading indicator (ローディングインジケーターを非表示)
closeLoading();
}

useUrlProvider

アプリケーションURLを生成するためのURLプロバイダーにアクセスします。

import { useUrlProvider } from "@mbc-cqrs-serverless/master-web";

function MyComponent() {
const urlProvider = useUrlProvider();

// Use static URLs (静的URLを使用)
const settingsUrl = urlProvider.SETTINGS_PAGE_URL;

// Generate dynamic URLs (動的URLを生成)
const copyUrl = urlProvider.getCopySettingPageUrl("item-123");
}

useAppServices

すべてのアプリケーションサービスに一度にアクセスします。HTTPクライアント、Apolloクライアント、ユーザーコンテキスト、URLプロバイダーを返します。

import { useAppServices } from "@mbc-cqrs-serverless/master-web";

function MyComponent() {
const { httpClient, apolloClient, user, urlProvider } = useAppServices();

// Use multiple services in one component (1つのコンポーネントで複数のサービスを使用)
const fetchData = async () => {
const response = await httpClient.get("/api/data");
// ...
};
}

戻り値:

プロパティ説明
httpClientAxiosInstanceREST API呼び出し用のHTTPクライアント
apolloClientApolloClientGraphQL操作用のApolloクライアント
userUserContext現在のユーザーコンテキストと認証状態
urlProviderIUrlProviderURL生成用のURLプロバイダー

useSubscribeCommandStatus

内部API

このフックはメインパッケージからエクスポートされておらず、内部使用専用です。予告なく変更される可能性があります。

AppSyncコマンドステータスの更新を購読します。バックエンドコマンドの進捗と完了を追跡するために使用します。

import { useSubscribeCommandStatus } from "@mbc-cqrs-serverless/master-web";

function MyComponent() {
const { isListening, message, start } = useSubscribeCommandStatus(
tenantCode,
async (msg) => {
if (msg) {
// Command completed successfully (コマンドが正常に完了)
console.log("Command finished:", msg);
} else {
// Command timed out (コマンドがタイムアウト)
console.log("Command timed out");
}
},
true // Show processing toast (処理中トーストを表示)
);

const handleSubmit = async () => {
const requestId = await submitCommand();
start(requestId, 30000); // Start listening with 30s timeout (30秒タイムアウトでリスニング開始)
};
}

パラメータ:

パラメータ説明
xTenantCodestringサブスクリプション用のテナントコード
doneCallback(msg: DecodedMessage | null) => voidコマンド完了またはタイムアウト時のコールバック
isShowProcessboolean処理中トーストを表示するかどうか(デフォルト: true)

戻り値:

プロパティ説明
isListeningboolean更新をアクティブにリスニングしているかどうか
messageDecodedMessage | null最新の受信メッセージ
start(reqId: string, timeoutMs?: number) => voidリクエストIDのリスニングを開始

useSubscribeBulkCommandStatus

内部API

このフックはメインパッケージからエクスポートされておらず、内部使用専用です。予告なく変更される可能性があります。

一括コマンドステータスの更新を購読します。各アイテムが独自の完了メッセージを受信する複数アイテムの処理時に使用します。

import { useSubscribeBulkCommandStatus } from "@mbc-cqrs-serverless/master-web";

function BulkOperationComponent() {
const { isListening, messages, finishedCount, start, stop } =
useSubscribeBulkCommandStatus(
tenantCode,
() => {
// Handle timeout (タイムアウトを処理)
console.log("Bulk operation timed out");
}
);

const handleBulkSubmit = async (items: Item[]) => {
const requestId = await submitBulkCommand(items);
start(requestId, 60000); // 60s timeout (60秒タイムアウト)
};

// Check if all items are processed (すべてのアイテムが処理されたか確認)
useEffect(() => {
if (finishedCount === expectedCount) {
stop();
// All items processed (すべてのアイテムが処理完了)
}
}, [finishedCount]);
}

パラメータ:

パラメータ説明
xTenantCodestringサブスクリプション用のテナントコード
onTimeout() => void操作タイムアウト時のオプションコールバック

戻り値:

プロパティ説明
isListeningboolean更新をアクティブにリスニングしているかどうか
messagesDecodedMessage[]受信したすべてのメッセージ
finishedCountnumber完了したアイテム数
start(reqId: string, timeoutMs?: number) => voidリクエストIDのリスニングを開始
stop() => void手動でリスニングを停止

useHealthCheck

内部API

このフックはメインパッケージからエクスポートされておらず、内部使用専用です。予告なく変更される可能性があります。

コンポーネントマウント時にヘルスチェックAPI呼び出しを実行します。NEXT_PUBLIC_ENABLE_HEALTH_CHECK環境変数で制御されます。

import { useHealthCheck } from "@mbc-cqrs-serverless/master-web";

function App() {
// Automatically calls health check endpoint on mount (マウント時にヘルスチェックエンドポイントを自動的に呼び出す)
useHealthCheck();

return <div>Application content (アプリケーションコンテンツ)</div>;
}

環境変数:

変数説明
NEXT_PUBLIC_ENABLE_HEALTH_CHECK"true"に設定してヘルスチェック呼び出しを有効化

usePagination

内部API

このフックはメインパッケージからエクスポートされておらず、内部使用専用です。予告なく変更される可能性があります。

検索、ソート、テーブルビューを含むページネーションを処理するための包括的なフック。永続的な状態のためにURLクエリパラメータと統合されます。

import { usePagination } from "@mbc-cqrs-serverless/master-web";
import { parseAsString } from "next-usequerystate";

interface SearchProps extends SearchPropsBase {
name?: string;
status?: string;
}

function DataListPage() {
const {
searchProps,
paginate,
onSubmitSearch,
executeSearch,
handlePaginationChange,
handleSortChange,
} = usePagination<DataRecord, SearchProps, Paginate<DataRecord>>({
searchPropDefinitions: {
name: parseAsString,
status: parseAsString,
},
getData: async (queries) => {
return await fetchData(queries);
},
rootPath: "/data-list",
isSearchInit: true,
});

useEffect(() => {
executeSearch();
}, []);

return (
<DataTable
data={paginate?.results ?? []}
onPaginationChange={handlePaginationChange}
onSortChange={handleSortChange}
/>
);
}

パラメータ:

パラメータ説明
searchPropDefinitionsUseQueryStatesKeysMapnext-usquerystateを使用したクエリパラメータ定義
getData(queries) => Promise<Paginate>サーバーからページネーションされたデータを取得する関数
getDataClient(queries) => Promise<Array>クライアントサイドデータフィルタリング用の関数
isSearchInitboolean初期ロード時に検索するかどうか(デフォルト: true)
rootPathstringページのルートパス(パス検証に使用)
tableViewsTableView[]オプションの事前定義テーブルビュー設定
getStorage() => SearchProps保存された検索条件を取得する関数
setStorage(props) => void検索条件を保存する関数
resetUseFormResetreact-hook-formのフォームリセット関数
setValueUseFormSetValuereact-hook-formのsetValue関数
convertSearchProps(props) => SearchProps検索実行前に検索プロパティを変換するオプション関数
convertChangeQueries(props) => SearchPropsURL変更前にクエリパラメータを変換するオプション関数

戻り値:

プロパティ説明
searchPropsSearchProps現在の検索パラメータ
queriesSearchPropsURLクエリパラメータ
setQueries(props) => Promise<void>URLクエリパラメータを直接更新
paginatePaginate<T>カウントとデータを含むページネーション結果
setPaginate(paginate) => voidページネーション状態を手動で設定
setPaginateClient(items, page?) => voidクライアントサイドデータ用のページネーションを設定
getPaginateClient(items) => Paginate配列をページネーションオブジェクトに変換
isCalledSearchboolean検索がトリガーされたかどうか
onSubmitSearch(props) => Promise<void>新しいパラメータで検索を送信
executeSearch() => Promise<object>現在のパラメータで検索を実行
searchUsingTableView(props, tableView) => Promise<void>事前定義されたテーブルビューを使用して検索
getSearchQuery() => SearchProps | nullURLまたはストレージから現在の検索クエリを取得
handlePaginationChangeOnChangeFn<PaginationState>ページ/サイズ変更のハンドラー
handleSortChangeOnChangeFn<SortingState>ソート変更のハンドラー
onResetSearchForm() => Promise<void>検索フォームを空の値にリセット

usePaginationRange

内部API

このフックとDOTS定数はメインパッケージからエクスポートされておらず、内部使用専用です。予告なく変更される可能性があります。

大きなページ数の省略記号を含む、ページネーションUI用のページ番号範囲を計算します。

import { usePaginationRange, DOTS } from "@mbc-cqrs-serverless/master-web";

function PaginationUI({ totalPages, currentPage }: Props) {
const range = usePaginationRange({
totalPageCount: totalPages,
currentPage: currentPage,
siblingCount: 1,
});

return (
<nav>
{range.map((item, index) => (
item === DOTS ? (
<span key={index}>...</span>
) : (
<button key={index} onClick={() => goToPage(item as number)}>
{item}
</button>
)
))}
</nav>
);
}

パラメータ:

パラメータ説明
totalPageCountnumber総ページ数
currentPagenumber現在のアクティブページ番号
siblingCountnumber各サイドに表示するページボタンの数(デフォルト: 1)

戻り値:

(number | string)[] - ページ番号と省略記号用のDOTS("...")の配列

useLoadingForm

内部API

このフックはメインパッケージからエクスポートされておらず、内部使用専用です。予告なく変更される可能性があります。

react-hook-formとグローバルローディング状態を組み合わせます。ローディング状態管理とともにフォームユーティリティを提供します。

import { useLoadingForm } from "@mbc-cqrs-serverless/master-web";

interface FormData {
name: string;
email: string;
}

function MyForm() {
const {
form,
control,
handleSubmit,
loading,
loadingStore,
errors,
} = useLoadingForm<FormData>({
defaultValues: {
name: "",
email: "",
},
});

const onSubmit = async (data: FormData) => {
loadingStore.setLoading();
try {
await saveData(data);
} finally {
loadingStore.closeLoading();
}
};

return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* Form fields (フォームフィールド) */}
</form>
);
}

パラメータ:

パラメータ説明
propsUseFormProps<T>react-hook-formのuseFormオプション

戻り値:

プロパティ説明
formUseFormReturn<T>完全なreact-hook-formインスタンス
controlControl<T>制御コンポーネント用のフォームコントロール
handleSubmitUseFormHandleSubmit<T>フォーム送信ハンドラー
watchUseFormWatch<T>フォーム値を監視
getValuesUseFormGetValues<T>フォーム値を取得
setValueUseFormSetValue<T>フォーム値を設定
resetUseFormReset<T>フォームをリセット
triggerUseFormTrigger<T>バリデーションをトリガー
errorsFieldErrors<T>フォームバリデーションエラー
setErrorUseFormSetError<T>フォームエラーを手動で設定
loadingboolean現在のローディング状態
loadingStoreLoadingStatesetLoading/closeLoading付きのローディングストア
isValidbooleanフォームが有効かどうか

useAsyncAction

内部API

このフックはメインパッケージからエクスポートされておらず、内部使用専用です。予告なく変更される可能性があります。

自動ローディングオーバーレイ付きで非同期関数を実行します。非同期操作中にグローバルローディングインジケーターを表示します。

import { useAsyncAction } from "@mbc-cqrs-serverless/master-web";

function MyComponent() {
const { performAction, isLoading } = useAsyncAction();

const handleClick = async () => {
const result = await performAction(async () => {
// This runs with loading overlay (これはローディングオーバーレイ付きで実行される)
return await fetchData();
});
console.log(result);
};

return (
<button onClick={handleClick} disabled={isLoading}>
データを読み込む
</button>
);
}

戻り値:

プロパティ説明
performAction<T>(fn: () => Promise<T>) => Promise<T>ローディングオーバーレイ付きで非同期関数を実行
isLoadingboolean現在のローディング状態

useNavigation

内部API

このフックはメインパッケージからエクスポートされておらず、内部使用専用です。予告なく変更される可能性があります。

自動ローディングインジケーター付きでページ間を移動します。Next.jsルーターをローディング状態管理でラップします。

import { useNavigation } from "@mbc-cqrs-serverless/master-web";

function MyComponent() {
const { navigate, reload, hardNavigate } = useNavigation();

return (
<div>
<button onClick={() => navigate("/dashboard")}>
ダッシュボードへ移動
</button>
<button onClick={() => reload()}>
ページを更新
</button>
<button onClick={() => hardNavigate("/external-page")}>
フルページナビゲーション
</button>
</div>
);
}

戻り値:

プロパティ説明
navigate(url: string) => voidローディングインジケーター付きでNext.jsルーターを使用してナビゲート
reload() => voidローディングインジケーター付きで現在のページを更新
hardNavigate(url: string) => voidフルブラウザナビゲーション(window.location)

UIコンポーネント

このパッケージには、いくつかの再利用可能なUIコンポーネントが含まれています:

JsonEditor

構造化データを編集するためのJSONエディタコンポーネント。jsoneditorライブラリのツリーモードを使用します。

import { JsonEditor } from "@mbc-cqrs-serverless/master-web";

function MyForm() {
const [jsonData, setJsonData] = useState({ key: "value" });

return (
<JsonEditor
json={jsonData}
onChange={setJsonData}
/>
);
}

JsonEditor プロパティ

プロパティ必須説明
jsonobjectはい表示・編集するJSONデータ
onChange(json: object) => voidいいえJSONコンテンツ変更時のコールバック

RichTextEditor

コンテンツフィールド用のリッチテキストエディタ。カスタマイズ可能なツールバー付きのReact Quill上に構築されています。

import { RichTextEditor } from "@mbc-cqrs-serverless/master-web";

function MyForm() {
const [content, setContent] = useState("");

return (
<RichTextEditor
value={content}
onChange={setContent}
placeholder="ここにコンテンツを入力..."
/>
);
}

RichTextEditor プロパティ

プロパティ必須説明
valuestringいいえエディタのHTMLコンテンツ(デフォルトは空文字列)
onChange(value: string) => voidはいコンテンツ変更時のコールバック
placeholderstringいいえエディタが空の場合のプレースホルダーテキスト(デフォルトは空文字列)

MsLayout

マスター管理ページ用のレイアウトコンポーネント。ローディングオーバーレイとトースト通知を提供します。

import { MsLayout } from "@mbc-cqrs-serverless/master-web";

function MasterPage() {
return (
<MsLayout useLoading={true}>
<MasterSetting />
</MsLayout>
);
}

MsLayout プロパティ

プロパティ必須説明
useLoadingbooleanはいローディングオーバーレイの有効化または無効化
childrenReact.ReactNodeはいレンダリングする子コンポーネント

ConfirmButton

内部API

このコンポーネントはメインパッケージからエクスポートされておらず、内部使用専用です。予告なく変更される可能性があります。

アクションを実行する前に確認ダイアログを表示するボタンコンポーネント。削除などの破壊的な操作に便利です。

ConfirmButton プロパティ

プロパティデフォルト説明
size'default' | 'sm' | 'lg' | 'icon''default'ボタンサイズ
triggerBtnTextstring-トリガーボタンに表示されるテキスト
titlestring-確認ダイアログのタイトル
cancelTextstring-キャンセルボタンのテキスト
confirmTextstring-確認ボタンのテキスト
loadingbooleanfalseボタンのローディング状態を表示
onConfirm() => void-確認がクリックされた時のコールバック関数
classNamestring-追加のCSSクラス
disabledbooleanfalseボタンを無効化
variantstring-ボタンのバリアントスタイル
// Note: Internal import path - may change without notice (注意: 内部インポートパス - 予告なく変更される可能性があります)
import ConfirmButton from "@mbc-cqrs-serverless/master-web/dist/components/buttons/ConfirmButton";

function DeleteAction() {
const handleDelete = () => {
// Perform delete operation (削除操作を実行)
};

return (
<ConfirmButton
triggerBtnText="Delete"
title="Are you sure you want to delete this item?"
cancelText="Cancel"
confirmText="Delete"
variant="destructive"
onConfirm={handleDelete}
/>
);
}

BackButton

内部API

このコンポーネントはメインパッケージからエクスポートされておらず、内部使用専用です。予告なく変更される可能性があります。

前のページまたは指定された場所に戻るためのナビゲーションボタンコンポーネント。

BackButton プロパティ

プロパティデフォルト説明
onClickPrev() => void-ボタンがクリックされた時のコールバック関数
classNamestring-追加のCSSクラス
// Note: Internal import path - may change without notice (注意: 内部インポートパス - 予告なく変更される可能性があります)
import { BackButton } from "@mbc-cqrs-serverless/master-web/dist/components/buttons/back-button";
import { useRouter } from "next/navigation";

function DetailPage() {
const router = useRouter();

return (
<div>
{/* Page content (ページコンテンツ) */}
<BackButton onClickPrev={() => router.back()} />
</div>
);
}

DatePicker

内部API

このコンポーネントはメインパッケージからエクスポートされておらず、内部使用専用です。予告なく変更される可能性があります。

カレンダーポップアップ付きの日付選択コンポーネント。フォーマットと日本語ロケールサポートにdate-fnsを使用。

DatePicker プロパティ

プロパティデフォルト説明
valueDate | string-現在選択されている日付値
onChange(date?: string) => void-日付が選択された時のコールバック(タイムゾーン付きISO文字列を返す)
disabledbooleanfalse日付ピッカーを無効化
// Note: Internal import path - may change without notice (注意: 内部インポートパス - 予告なく変更される可能性があります)
import DatePicker from "@mbc-cqrs-serverless/master-web/dist/components/form/DatePicker";
import { useState } from "react";

function DateForm() {
const [date, setDate] = useState<string>();

return (
<DatePicker
value={date}
onChange={(newDate) => setDate(newDate)}
/>
);
}

FormSubmitButton

内部API

このコンポーネントはメインパッケージからエクスポートされておらず、内部使用専用です。予告なく変更される可能性があります。

react-hook-formと連携するように設計された送信ボタンコンポーネント。フォームの状態、バリデーションエラー、ローディング状態を自動的に処理します。

FormSubmitButton プロパティ

プロパティデフォルト説明
childrenReact.ReactNode-ボタンの内容
disabledbooleanfalseボタンを手動で無効化
loadingbooleanfalseローディング状態を表示
classNamestring-追加のCSSクラス
disableDirtybooleanfalsetrueの場合、フォームが変更されていなくてもボタンが有効

以下の場合、ボタンは自動的に無効化されます:

  • フォームにバリデーションエラーがある場合
  • フォームが変更されていない場合(disableDirtyがtrueでない限り)
// Note: Internal import path - may change without notice (注意: 内部インポートパス - 予告なく変更される可能性があります)
import FormSubmitButton from "@mbc-cqrs-serverless/master-web/dist/components/form/FormSubmitButton";
import { FormProvider, useForm } from "react-hook-form";
import { useState } from "react";

function MyForm() {
const methods = useForm();
const [isSubmitting, setIsSubmitting] = useState(false);

return (
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)}>
{/* Form fields (フォームフィールド) */}
<FormSubmitButton loading={isSubmitting}>
Save
</FormSubmitButton>
</form>
</FormProvider>
);
}

DataTable

内部API

このコンポーネントはメインパッケージからエクスポートされておらず、内部使用専用です。予告なく変更される可能性があります。

TanStack Table上に構築されたフル機能のデータテーブルコンポーネント。サーバーサイドページネーション、ソート、行選択、カスタムカラム定義をサポート。

DataTable プロパティ

プロパティデフォルト説明
columnsColumnDef<TData, TValue>[]-TanStack Table形式のカラム定義
dataTData[]-表示するデータの配列
pageCountnumber-総ページ数
rowCountnumber-総行数
paginationPaginationState-現在のページネーション状態(pageIndex、pageSize)
onPaginationChange(pagination: PaginationState) => void-ページネーション変更時のコールバック
sortingSortingState-現在のソート状態
onSortingChange(sorting: SortingState) => void-ソート変更時のコールバック
onClickRow(row: TData) => void-行がクリックされた時のコールバック
rowKeykeyof TData | ((row: TData) => string)-行識別用のキー抽出
rowSelectionRowSelectionState-現在の行選択状態
onRowSelectionChange(state: RowSelectionState) => void-行選択変更時のコールバック
State 型

PaginationStateSortingStateRowSelectionStateはTanStack Tableの型です。PaginationStateにはpageIndexpageSizeプロパティが含まれています。

DataTable 機能

  • ページサイズオプション(10、20、50、100)付きのサーバーサイドページネーション
  • 特定ページへのジャンプ機能
  • カラムソート
  • 行選択
  • 行ナビゲーション用のクリックハンドラー
  • 空状態の表示
  • カラムメタによるカスタムカラム幅
// Note: Internal import path - may change without notice (注意: 内部インポートパス - 予告なく変更される可能性があります)
import { DataTable } from "@mbc-cqrs-serverless/master-web/dist/components/table/data-table";
import { ColumnDef, OnChangeFn, PaginationState, SortingState } from "@tanstack/react-table";
import { useState } from "react";
import { useRouter } from "next/navigation";

type User = {
id: string;
name: string;
email: string;
};

const columns: ColumnDef<User>[] = [
{
accessorKey: "name",
header: "Name",
meta: { size: "200px" },
},
{
accessorKey: "email",
header: "Email",
},
];

function UserList() {
const router = useRouter();
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
});
const [sorting, setSorting] = useState<SortingState>([]);

// Fetch data based on pagination and sorting (ページネーションとソートに基づいてデータを取得)
const { data, pageCount, rowCount } = useUsers(pagination, sorting);

return (
<DataTable
columns={columns}
data={data}
pageCount={pageCount}
rowCount={rowCount}
pagination={pagination}
onPaginationChange={setPagination}
sorting={sorting}
onSortingChange={setSorting}
rowKey="id"
onClickRow={(row) => router.push(`/users/${row.id}`)}
/>
);
}

LoadingOverlay

内部API

このコンポーネントはメインパッケージからエクスポートされておらず、内部使用専用です。予告なく変更される可能性があります。

スピナーアニメーション付きのフルスクリーンローディングオーバーレイコンポーネント。非同期操作中のローディング状態を示すのに便利です。

LoadingOverlay プロパティ

プロパティデフォルト説明
isLoadingboolean-オーバーレイの表示/非表示を制御
// Note: Internal import path - may change without notice (注意: 内部インポートパス - 予告なく変更される可能性があります)
import LoadingOverlay from "@mbc-cqrs-serverless/master-web/dist/components/LoadingOverlay";
import { useState } from "react";

function MyPage() {
const [isLoading, setIsLoading] = useState(false);

const handleSubmit = async () => {
setIsLoading(true);
try {
await saveData();
} finally {
setIsLoading(false);
}
};

return (
<div>
<LoadingOverlay isLoading={isLoading} />
{/* Page content (ページコンテンツ) */}
</div>
);
}

環境変数

Master Webパッケージ用に以下の環境変数を設定します:

変数説明
NEXT_PUBLIC_MASTER_API_BASEREST APIエンドポイントのベースURL
NEXT_PUBLIC_MASTER_APPSYNC_URLAWS AppSync GraphQL エンドポイントURL
NEXT_PUBLIC_MASTER_APPSYNC_APIKEY認証用 AWS AppSync API キー
NEXT_PUBLIC_MASTER_APPSYNC_REGIONAppSync用のAWSリージョン

.env.local の例

NEXT_PUBLIC_MASTER_API_BASE=https://api.example.com
NEXT_PUBLIC_MASTER_APPSYNC_URL=https://xxxxxxxx.appsync-api.ap-northeast-1.amazonaws.com/graphql
NEXT_PUBLIC_MASTER_APPSYNC_APIKEY=da2-xxxxxxxxxxxxxxxxx
NEXT_PUBLIC_MASTER_APPSYNC_REGION=ap-northeast-1

スタイリング

アプリケーションでパッケージのスタイルをインポートします:

import "@mbc-cqrs-serverless/master-web/styles.css";

コンポーネントはスタイリングにTailwind CSSを使用しています。プロジェクトでTailwind CSSが設定されていることを確認してください。

Next.js App Router との統合

バージョン情報

v0.0.42で、React/Next.jsがpeer dependenciesとして外部化され、httpClient.get is not a functionエラーの原因となっていたContext分離問題が解消されました。v0.0.41以前をお使いの場合は、v0.0.42以降にアップグレードしてください。

Next.js App Router(v14+/v15)で master-web コンポーネントを使用する場合、サーバーサイドレンダリング(SSR)とクライアントサイドの状態管理に関する重要な考慮事項があります。

SSR 互換性の問題

JsonEditor コンポーネントは内部で jsoneditor ライブラリを使用しており、SSR 中には利用できないブラウザ API(selfwindow)を必要とします。これにより次のようなエラーが発生します:

ReferenceError: self is not defined

解決策:SSR を無効にした動的インポート

Next.js の動的インポートで ssr: false を使用して、master-web コンポーネントをクライアントサイドでのみロードします:

'use client'

import dynamic from 'next/dynamic'
import { useMemo } from 'react'

// Create wrapper component that handles dynamic import (動的インポートを処理するラッパーコンポーネントを作成)
function MasterSettingWrapper({ httpClient, urlProvider, user }) {
// Dynamic import inside component for proper context handling (適切なコンテキスト処理のためにコンポーネント内で動的インポート)
const MasterSetting = useMemo(
() =>
dynamic(() => import('@mbc-cqrs-serverless/master-web/MasterSetting'), {
ssr: false,
loading: () => <div>Loading...</div>,
}),
[]
)

return (
<AppProviders user={user} httpClient={httpClient} urlProvider={urlProvider}>
<MasterSetting />
</AppProviders>
)
}
重要

動的インポートはモジュールレベルではなく、ラッパーコンポーネント内(useMemo を使用)で定義してください。これにより、コンポーネントがマウントされる際に AppProviders のコンテキストが適切に利用可能になります。

推奨パターン:Layout ベースの Provider パターン

最も推奨される実装パターンは、Next.js App Router の layout.tsx を使用して AppProviders をセットアップすることです。このパターンにより、httpClient.get is not a function エラーを確実に回避できます。

Layout パターンが推奨される理由

  1. React Context の分離問題を解決:npm パッケージにバンドルされた React Context は、アプリケーションのコンテキストから分離される可能性があります。Layout でプロバイダーをセットアップすることで、子コンポーネントがマウントされる前にコンテキストが初期化されることが保証されます。

  2. 同期的な httpClient の初期化useMemo を使用することで、非同期の状態管理(useState + useEffect)なしに httpClient を同期的に作成します。

  3. 認証トークンの自動注入:Axios インターセプターを使用して、各リクエストで最新の認証トークンを自動的に取得します。

実装例:layout.tsx

// app/admin/[tenant]/master/layout.tsx
'use client'

import { useMemo } from 'react'
import dynamic from 'next/dynamic'
import { useParams } from 'next/navigation'
import axios from 'axios'
import { fetchAuthSession } from 'aws-amplify/auth'
import type { IUrlProvider } from '@mbc-cqrs-serverless/master-web/UrlProvider'
import '@/modules/common/components/ConfigureAmplifyClientSide'

// Dynamic import of AppProviders (SSR disabled) (AppProviders の動的インポート、SSR 無効)
const AppProviders = dynamic(
() =>
import('@mbc-cqrs-serverless/master-web/AppProviders').then(
(mod) => mod.AppProviders
),
{ ssr: false }
)

// Multi-tenant URL provider (マルチテナント URL プロバイダー)
class MasterUrlProvider implements IUrlProvider {
protected readonly baseUrl: string

public readonly SETTINGS_PAGE_URL: string
public readonly ADD_SETTINGS_PAGE_URL: string
public readonly EDIT_SETTINGS_PAGE_URL: string
public readonly DATA_PAGE_URL: string
public readonly ADD_DATA_PAGE_URL: string
public readonly EDIT_DATA_PAGE_URL: string
public readonly FAQ_CATEGORY_PAGE_URL: string
public readonly TOP_URL: string

constructor(tenantCode: string) {
this.baseUrl = `/admin/${tenantCode}/master`

this.SETTINGS_PAGE_URL = `${this.baseUrl}/master-setting`
this.ADD_SETTINGS_PAGE_URL = `${this.baseUrl}/master-setting/new`
this.EDIT_SETTINGS_PAGE_URL = this.SETTINGS_PAGE_URL
this.DATA_PAGE_URL = `${this.baseUrl}/master-data`
this.ADD_DATA_PAGE_URL = `${this.baseUrl}/master-data/new`
this.EDIT_DATA_PAGE_URL = this.DATA_PAGE_URL
this.FAQ_CATEGORY_PAGE_URL = `${this.baseUrl}/faq-category`
this.TOP_URL = `/admin/${tenantCode}`
}

public getCopySettingPageUrl(id: string): string {
return `${this.baseUrl}/master-setting/${id}/copy/new`
}

public getDetailedCopySettingPageUrl(id: string): string {
return `${this.baseUrl}/master-setting/${id}/copy`
}
}

export default function MasterLayout({
children,
}: {
children: React.ReactNode
}) {
const params = useParams<{ tenant: string }>()
const tenantCode = params?.tenant || 'common'

// Create URL provider synchronously with useMemo (useMemo で URL プロバイダーを同期的に作成)
const urlProvider = useMemo(() => new MasterUrlProvider(tenantCode), [tenantCode])

// Create httpClient synchronously with useMemo (interceptor auto-injects auth token) (useMemo で httpClient を同期的に作成、インターセプターが認証トークンを自動注入)
const httpClient = useMemo(() => {
const baseEndpoint = process.env.NEXT_PUBLIC_API_ENDPOINT || 'http://localhost:3010'
const instance = axios.create({
baseURL: `${baseEndpoint}/api`,
headers: {
'Content-Type': 'application/json',
'x-tenant-code': tenantCode,
},
})

// Interceptor to get auth token (認証トークンを取得するインターセプター)
instance.interceptors.request.use(async (config) => {
try {
const session = await fetchAuthSession()
const token = session.tokens?.idToken?.toString()
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
} catch {
// Ignore auth errors (認証エラーを無視)
}
return config
})

return instance
}, [tenantCode])

const user = useMemo(
() => ({
tenantCode,
tenantRole: 'admin',
}),
[tenantCode]
)

return (
<AppProviders user={user} urlProvider={urlProvider} httpClient={httpClient}>
<div className="p-6">{children}</div>
</AppProviders>
)
}

ページコンポーネントの実装

Layout でプロバイダーをセットアップした後、各ページコンポーネントはシンプルになります:

// app/admin/[tenant]/master/master-setting/page.tsx
'use client'

import dynamic from 'next/dynamic'
import MsLayout from '@mbc-cqrs-serverless/master-web/MsLayout'

const MasterSetting = dynamic(
() => import('@mbc-cqrs-serverless/master-web/MasterSetting').then((mod) => mod.default),
{ ssr: false }
)

export default function MasterSettingPage() {
return (
<main>
<MsLayout useLoading>
<MasterSetting />
</MsLayout>
</main>
)
}
// app/admin/[tenant]/master/master-setting/new/page.tsx
'use client'

import dynamic from 'next/dynamic'
import MsLayout from '@mbc-cqrs-serverless/master-web/MsLayout'

const EditMasterSettings = dynamic(
() => import('@mbc-cqrs-serverless/master-web/EditMasterSettings').then((mod) => mod.default),
{ ssr: false }
)

export default function NewMasterSettingPage() {
return (
<main>
<MsLayout useLoading>
<EditMasterSettings />
</MsLayout>
</main>
)
}
キーポイント
  • layout.tsx:AppProviders、httpClient、urlProvider をセットアップ
  • page.tsx:動的インポートでコンポーネントをシンプルにレンダリング
  • MsLayout:ローディングオーバーレイとトースト通知を提供

AWS Amplify v6 との統合

master-web のデフォルト httpClient は AWS Amplify v5 API(Auth.currentSession())を使用しています。プロジェクトで AWS Amplify v6 を使用する場合は、カスタム httpClient を提供する必要があります。

上記の Layout ベースの Provider パターンの例では、Amplify v6 の fetchAuthSession を Axios インターセプターと組み合わせて使用しています。これが推奨されるアプローチです。

代替パターン:ページコンポーネントでの設定

Layout パターンを使用しない場合の代替実装:

'use client'

import { useState, useEffect, useCallback, useMemo } from 'react'
import axios, { AxiosInstance } from 'axios'
import * as Auth from 'aws-amplify/auth' // Amplify v6 import (Amplify v6 インポート)
import { AppProviders } from '@mbc-cqrs-serverless/master-web/AppProviders'
import { BaseUrlProvider } from '@mbc-cqrs-serverless/master-web/UrlProvider'
import dynamic from 'next/dynamic'

interface MasterTemplateProps {
tenantCode: string
}

// Custom URL provider for multi-tenant routing (マルチテナントルーティング用のカスタム URL プロバイダー)
class MasterUrlProvider extends BaseUrlProvider {
constructor(tenantCode: string) {
// BaseUrlProvider adds leading slash, so omit it here (BaseUrlProvider が先頭スラッシュを追加するため、ここでは省略)
super(`admin/${tenantCode}/master`)
}
}

// Wrapper component for proper context handling (適切なコンテキスト処理のためのラッパーコンポーネント)
function MasterSettingWrapper({
httpClient,
urlProvider,
user,
}: {
httpClient: AxiosInstance
urlProvider: MasterUrlProvider
user: { tenantCode: string; tenantRole: string }
}) {
const MasterSetting = useMemo(
() =>
dynamic(() => import('@mbc-cqrs-serverless/master-web/MasterSetting'), {
ssr: false,
loading: () => <div>Loading component...</div>,
}),
[]
)

return (
<AppProviders user={user} httpClient={httpClient} urlProvider={urlProvider}>
<MasterSetting />
</AppProviders>
)
}

export function MasterTemplate({ tenantCode }: MasterTemplateProps) {
const [httpClient, setHttpClient] = useState<AxiosInstance | null>(null)
const [isReady, setIsReady] = useState(false)

// Setup httpClient with Amplify v6 authentication (Amplify v6 認証で httpClient をセットアップ)
const setupHttpClient = useCallback(async () => {
let authToken = ''
try {
// Amplify v6 API for fetching auth session (認証セッションを取得する Amplify v6 API)
const session = await Auth.fetchAuthSession()
authToken = session.tokens?.idToken?.toString() || ''
} catch {
// Handle unauthenticated state (未認証状態を処理)
}

const client = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_ENDPOINT || 'http://localhost:3010',
headers: {
'Content-Type': 'application/json',
'x-tenant-code': tenantCode,
...(authToken && { Authorization: `Bearer ${authToken}` }),
},
})

setHttpClient(client)
setIsReady(true)
}, [tenantCode])

useEffect(() => {
setupHttpClient()
}, [setupHttpClient])

const urlProvider = useMemo(() => new MasterUrlProvider(tenantCode), [tenantCode])

const user = useMemo(
() => ({
tenantCode,
tenantRole: 'admin',
}),
[tenantCode]
)

// Wait for httpClient to be ready before rendering (レンダリング前に httpClient の準備を待つ)
if (!isReady || !httpClient) {
return <div>Loading...</div>
}

return (
<MasterSettingWrapper
httpClient={httpClient}
urlProvider={urlProvider}
user={user}
/>
)
}

マルチテナントルーティングのセットアップ

マルチテナントアプリケーションでは、URL にテナントコードを含む動的ルートをセットアップします:

ディレクトリ構造

app/
└── admin/
└── [tenant]/
└── master/
├── layout.tsx # AppProviders setup (recommended) (AppProviders のセットアップ、推奨)
├── page.tsx # Master top page (マスタートップページ)
├── master-setting/
│ ├── page.tsx # Settings list (設定一覧)
│ ├── new/
│ │ └── page.tsx # Create new setting (新規設定作成)
│ └── [pk]/
│ └── [sk]/
│ └── page.tsx # Edit setting (設定編集)
└── master-data/
├── page.tsx # Data list (データ一覧)
├── new/
│ └── page.tsx # Create new data (新規データ作成)
└── [pk]/
└── [sk]/
└── page.tsx # Edit data (データ編集)

ページコンポーネントの例

// app/admin/[tenant]/master/master-setting/page.tsx
import { MasterTemplate } from '@/modules/master/templates/MasterTemplate'

export default async function MasterSettingPage({
params,
}: {
params: Promise<{ tenant: string }>
}) {
const { tenant } = await params

return <MasterTemplate tenantCode={tenant} />
}

よくある問題と解決策

httpClient.get is not a function エラー

このエラーは、コンポーネントが使用しようとする前に httpClient が適切に初期化されていない場合に発生します。

原因

  • npm パッケージの React Context がアプリケーションのコンテキストから分離される
  • httpClient が非同期で初期化され、コンポーネントがマウントされた時点で準備ができていない

解決策

  1. Layout ベースの Provider パターンを使用(推奨):上記の推奨パターンを参照
  2. ラッパーコンポーネントパターンを使用:httpClient の準備を確認
  3. 明示的な isReady 状態チェックを追加:AppProviders をレンダリングする前に確認
  4. 動的インポートをラッパーコンポーネント内で定義:モジュールレベルではなく

URL ルーティングの問題

URL が正しく生成されない場合(例:/admin/... ではなく //admin/...)、BaseUrlProvider の設定を確認してください:

// ❌ Wrong - double slash issue (❌ 間違い - ダブルスラッシュの問題)
class MasterUrlProvider extends BaseUrlProvider {
constructor(tenantCode: string) {
super(`/admin/${tenantCode}/master`) // Leading slash causes issue (先頭スラッシュが問題を引き起こす)
}
}

// ✅ Correct - no leading slash (✅ 正しい - 先頭スラッシュなし)
class MasterUrlProvider extends BaseUrlProvider {
constructor(tenantCode: string) {
super(`admin/${tenantCode}/master`) // BaseUrlProvider adds the slash (BaseUrlProvider がスラッシュを追加)
}
}

IUrlProvider を直接実装する

BaseUrlProvider を継承せずに IUrlProvider インターフェースを直接実装する場合は、すべての URL プロパティを明示的に定義する必要があります。参考として上記の Layout ベースの Provider パターンの例を参照してください。

依存関係

このパッケージで使用される主要な依存関係:

  • React 18.x
  • Next.js 14.x / 15.x
  • TanStack React Table 8.x
  • Apollo Client
  • Radix UIコンポーネント
  • Tailwind CSS 3.x
  • react-hook-form
  • バリデーション用Zod

変更履歴

バージョン履歴

すべてのバージョン履歴とリリースノートはWebパッケージ変更履歴を参照してください。