制作Note

MUI Data Gridを使って合計値を出す|NextJS, TypeScript

Material UIのデータグリッドを使って色々試していた。

まずは公式サイトで情報収集した。

基本的なMUIの使い方

UI Tools

プラン(無料でも色々できる)

MUI X データグリッド

Data Grid Demo

基本的なグリッドを作るだけでもソートやフィルタ機能など多機能なのが分かった。

そして、チェックボックスで選んだものにあるひとつのセルを数字にして合計を出せるようにしたらどうかな、と思って実装開始。

まずはデータグリッドを作成

NextJSはsrcでラップしたApp Routerにする。

npx create-next-app@latest my-mui-datagrid --typescript

MUIインストール

npm install @mui/material @emotion/react @emotion/styled @mui/x-data-grid

アーキテクチャは下記のように設計。

my-mui-datagrid/
├── src/
│   ├── app/
│   │   ├── layout.tsx
│   │   ├── page.tsx
│   └── components/
│       └── DataGrid.tsx
├── public/
├── styles/
├── .gitignore
├── package.json
└── tsconfig.json

src/components/DataGrid.tsxにデータグリッドコンポーネントを作成

// src/components/DataGrid.tsx
'use client'; // クライアントコンポーネントとして指定

import * as React from 'react';
import { DataGrid, GridColDef } from '@mui/x-data-grid';

const columns: GridColDef[] = [
  { field: 'id', headerName: 'ID', width: 90 },
  {
    field: 'name',
    headerName: 'Name',
    width: 150,
    editable: true,
  },
  {
    field: 'time',
    headerName: 'time',
    type: 'string',
    width: 110,
    editable: true,
  },
  {
    field: 'conditional',
    headerName: '条件',
    type: 'string',
    width: 110,
    editable: true,
  },
  {
    field: 'price',
    headerName: 'number',
    width: 200,
    editable: true,
  },
];

const rows = [
  { "id": 1, "name": "飲み放題コース", "time": "1時間", "conditional": "2人以上", "price": 1000 },
  { "id": 2, "name": "料理3品コース", "time": "2時間", "conditional": "4人以上", "price": 3000 },
  { "id": 3, "name": "料理5品コース", "time": "2時間", "conditional": "4人以上", "price": 5000 },
  { "id": 4, "name": "ディナーフルコース", "time": "3時間", "conditional": "2人以上", "price": 12000 },
  { "id": 5, "name": "延長飲み放題", "time": "1時間", "conditional": "4人以上", "price": 1500 }
];

const DataGridComponent: React.FC = () => {
  return (
    <div style={{ height: 400, width: '100%' }}>
      <DataGrid
        rows={rows}
        columns={columns}
        paginationModel={{ page: 0, pageSize: 5 }}
        checkboxSelection
        disableRowSelectionOnClick
      />
    </div>
  );
};

export default DataGridComponent;

src/app/page.tsxにデータグリッドを追加

// src/app/page.tsx
import React from 'react';
import DataGridComponent from '../components/DataGrid';

export default function Home() {
  return (
    <main>
      <h1>Data Grid Example</h1>
      <DataGridComponent />
    </main>
  );
}

src/app/layout.tsxに必要な設定を追加

// src/app/layout.tsx
import React from 'react';
import { CssBaseline } from '@mui/material';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ja">
      <head>
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
      </head>
      <body>
        <CssBaseline />
        {children}
      </body>
    </html>
  );
}
チェックボックスで選んだ行を抽出して別のエリアに表示
  • 選択された行の状態を管理する: ReactのuseStateフックを使って、選択された行のIDを管理します。
  • 選択状態の変更を監視する: onRowSelectionModelChangeイベントを使って、選択状態が変更されたときに行のデータを更新します。
  • 選択された行のデータを表示する: 選択された行のデータをフィルタリングして表示します。
// src/components/DataGrid.tsx
'use client'; // クライアントコンポーネントとして指定

import * as React from 'react';
import { DataGrid, GridColDef, GridRowSelectionModel } from '@mui/x-data-grid';

// 列の定義
const columns: GridColDef[] = [
  { field: 'id', headerName: 'ID', width: 90 },
  {
    field: 'name',
    headerName: 'Name',
    width: 150,
    editable: true,
  },
  {
    field: 'time',
    headerName: 'time',
    type: 'string',
    width: 110,
    editable: true,
  },
  {
    field: 'conditional',
    headerName: '条件',
    type: 'string',
    width: 110,
    editable: true,
  },
  {
    field: 'price',
    headerName: 'number',
    width: 200,
    editable: true,
  },
];

// データの定義
const rows = [
    { "id": 1, "name": "飲み放題コース", "time": "1時間", "conditional": "2人以上", "price": 1000 },
    { "id": 2, "name": "料理3品コース", "time": "2時間", "conditional": "4人以上", "price": 3000 },
    { "id": 3, "name": "料理5品コース", "time": "2時間", "conditional": "4人以上", "price": 5000 },
    { "id": 4, "name": "ディナーフルコース", "time": "3時間", "conditional": "2人以上", "price": 12000 },
    { "id": 5, "name": "延長飲み放題", "time": "1時間", "conditional": "4人以上", "price": 1500 }
  ]
;

const DataGridComponent: React.FC = () => {
  const [selectedRows, setSelectedRows] = React.useState<number[]>([]);

  // チェックボックスの選択が変更されたときに呼ばれる関数
  const handleRowSelection = (newSelection: GridRowSelectionModel) => {
    setSelectedRows(newSelection as number[]);
  };

  // 選択された行をフィルタリングする
  const selectedRowData = rows.filter((row) => selectedRows.includes(row.id));

  return (
    <div style={{ display: 'flex' }}>
      <div style={{ height: 400, width: '50%' }}>
        <DataGrid
          rows={rows}
          columns={columns}
          paginationModel={{ page: 0, pageSize: 5 }}
          checkboxSelection
          disableRowSelectionOnClick
          onRowSelectionModelChange={handleRowSelection}
          rowSelectionModel={selectedRows}
        />
      </div>
      <div style={{ marginLeft: 20, width: '50%' }}>
        <h2>Selected Rows</h2>
        <ul>
          {selectedRowData.map((row) => (
            <li key={row.id}>
              {row.name} - {row.email}
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
};

export default DataGridComponent;

ポイント

  • handleRowSelection 関数で選択された行のIDを管理しています。
  • selectedRowData で、選択された行のデータをフィルタリングしています。
  • DataGrid の onRowSelectionModelChange イベントで選択状態の変更を追跡しています。
列の定義に価格を追加して、抽出したものは合計を出す

1. 列の定義に価格を追加
列の定義に新しいフィールド「価格」を追加します。

2. 選択された行の価格を合計
選択された行の価格を合計するロジックを追加します。

// src/components/DataGrid.tsx
'use client'; // クライアントコンポーネントとして指定

import * as React from 'react';
import { DataGrid, GridColDef, GridRowSelectionModel, GridValueFormatterParams } from '@mui/x-data-grid';

// 列の定義
const columns: GridColDef[] = [
  { field: 'id', headerName: 'ID', width: 90 },
  {
    field: 'name',
    headerName: 'Name',
    width: 150,
    editable: true,
  },
  {
    field: 'time',
    headerName: 'time',
    type: 'string',
    width: 110,
    editable: true,
  },
  {
    field: 'conditional',
    headerName: '条件',
    type: 'string',
    width: 110,
    editable: true,
  },
  {
    field: 'price',
    headerName: 'Price',
    type: 'number',
    width: 150,
    editable: true,
    valueFormatter: (params: GridValueFormatterParams<number>) => `$${(params.value ?? 0).toFixed(2)}`, // フォーマット
  },
];

// データの定義
const rows = [
  { "id": 1, "name": "飲み放題コース", "time": "1時間", "conditional": "2人以上", "price": 1000 },
  { "id": 2, "name": "料理3品コース", "time": "2時間", "conditional": "4人以上", "price": 3000 },
  { "id": 3, "name": "料理5品コース", "time": "2時間", "conditional": "4人以上", "price": 5000 },
  { "id": 4, "name": "ディナーフルコース", "time": "3時間", "conditional": "2人以上", "price": 12000 },
  { "id": 5, "name": "延長飲み放題", "time": "1時間", "conditional": "4人以上", "price": 1500 }
];

const DataGridComponent: React.FC = () => {
  const [selectedRows, setSelectedRows] = React.useState<number[]>([]);

  // チェックボックスの選択が変更されたときに呼ばれる関数
  const handleRowSelection = (newSelection: GridRowSelectionModel) => {
    setSelectedRows(newSelection as number[]);
  };

  // 選択された行をフィルタリングする
  const selectedRowData = rows.filter((row) => selectedRows.includes(row.id));

  // 選択された行の価格の合計を計算する
  const totalPrice = selectedRowData.reduce((sum, row) => sum + (row.price || 0), 0);

  return (
    <div style={{ display: 'flex' }}>
      <div style={{ height: 400, width: '50%' }}>
        <DataGrid
          rows={rows}
          columns={columns}
          paginationModel={{ page: 0, pageSize: 5 }}
          checkboxSelection
          disableRowSelectionOnClick
          onRowSelectionModelChange={handleRowSelection}
          rowSelectionModel={selectedRows}
        />
      </div>
      <div style={{ marginLeft: 20, width: '50%' }}>
        <h2>Selected Rows</h2>
        <ul>
          {selectedRowData.map((row) => (
            <li key={row.id}>
              {row.name} - {row.email} - ${row.price.toFixed(2)}
            </li>
          ))}
        </ul>
        <h3>Total Price: ${totalPrice.toFixed(2)}</h3>
      </div>
    </div>
  );
};

export default DataGridComponent;
import 文で JSON ファイルを読み込む

JSONファイルを src フォルダ内に置き、import 文を使って読み込む方法です。

// src/components/DataGrid.tsx
'use client'; // クライアントコンポーネントとして指定

import * as React from 'react';
import { DataGrid, GridColDef, GridRowSelectionModel, GridPaginationModel } from '@mui/x-data-grid';

const columns: GridColDef[] = [
  { field: 'id', headerName: 'ID', width: 90 },
  {
    field: 'name',
    headerName: 'コース名',
    width: 200,
    editable: true,
  },
  {
    field: 'time',
    headerName: '制限時間',
    type: 'string',
    width: 100,
    editable: true,
  },
  {
    field: 'conditional',
    headerName: '条件',
    width: 100,
    editable: true,
  },
  {
    field: 'price',
    headerName: '価格',
    type: 'number',
    width: 150,
    editable: true,
    // valueFormatter: (params) => `$${(params.value ?? 0).toFixed(2)}`, // フォーマット
  },
];

const DataGridComponent: React.FC = () => {
  const [rows, setRows] = React.useState<any[]>([]);
  const [selectedRows, setSelectedRows] = React.useState<number[]>([]);
  const [paginationModel, setPaginationModel] = React.useState<GridPaginationModel>({ page: 10, pageSize: 100 });

  // データの取得と状態の更新
  React.useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch('/data.json', {
          cache: 'no-store', // キャッシュを無効にする
        });
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        const data = await response.json();
        setRows(data);
      } catch (error) {
        console.error('Failed to fetch data:', error);
      }
    };

    fetchData();

    // 定期的にデータを更新
    const intervalId = setInterval(fetchData, 30000); // 30秒ごとにデータを取得

    return () => clearInterval(intervalId); // クリーンアップ
  }, []);

  // チェックボックスの選択が変更されたときに呼ばれる関数
  const handleRowSelection = (newSelection: GridRowSelectionModel) => {
    setSelectedRows(newSelection as number[]);
  };

  // ページネーションの状態が変更されたときに呼ばれる関数
  const handlePaginationChange = (model: GridPaginationModel) => {
    setPaginationModel(model);
  };

  // ページネーションに基づいて表示するデータを取得
  const { page, pageSize } = paginationModel;
  const paginatedRows = rows.slice(page * pageSize, (page + 1) * pageSize);

  // 選択された行をフィルタリングする
  const selectedRowData = paginatedRows.filter((row) => selectedRows.includes(row.id));

  // 選択された行の価格の合計を計算する
  const totalPrice = selectedRowData.reduce((sum, row) => sum + (row.price || 0), 0);

  // 数字にカンマを付けるフォーマット関数
  const formatCurrency = (value: number) => new Intl.NumberFormat().format(value);

  return (
    <div style={{ display: 'flex' }}>
      <div style={{ height: 400, width: '100%' }}>
        <DataGrid
          rows={paginatedRows}
          columns={columns}
          paginationModel={paginationModel}
          onPaginationModelChange={handlePaginationChange}
          checkboxSelection
          disableRowSelectionOnClick
          onRowSelectionModelChange={handleRowSelection}
          rowSelectionModel={selectedRows}
        />
      </div>
      <div style={{ marginLeft: 20, width: '50%' }}>
        <h2>選択したコース</h2>
        <ul>
          {selectedRowData.map((row) => (
            <li key={row.id}>
              {row.name} - {row.email} - ¥{formatCurrency(row.price)}
            </li>
          ))}
        </ul>
        <h3>合計金額: ¥{formatCurrency(totalPrice)}</h3>
      </div>
    </div>
  );
};

export default DataGridComponent;

JSONデータ

// publicフォルダに配置
[
    { "id": 1, "name": "飲み放題コース", "time": "1時間", "conditional": "2人以上", "price": 1000 },
    { "id": 2, "name": "料理3品コース", "time": "2時間", "conditional": "4人以上", "price": 3000 },
    { "id": 3, "name": "料理5品コース", "time": "2時間", "conditional": "4人以上", "price": 5000 },
    { "id": 4, "name": "ディナーフルコース", "time": "3時間", "conditional": "2人以上", "price": 12000 },
    { "id": 5, "name": "延長飲み放題", "time": "1時間", "conditional": "4人以上", "price": 1500 }
  ]
Vercel にデプロイ

1, Git リポジトリの作成
プロジェクトのディレクトリで Git リポジトリを初期化し、コードをコミットします。

git init
git add .
git commit -m "Initial commit"

リモートリポジトリにプッシュ

git remote add origin https://github.com/YOUR_USERNAME/YOUR_REPOSITORY_NAME.git
git push -u origin main

2、Vercel にデプロイ
1. Vercel にログインし、ダッシュボードから「New Project」をクリックします。

2. GitHubと連携し、デプロイしたいリポジトリを選択します。

3、デプロイ成功