タイトル : Next.js FastAPIと通信 その4 選択用ダイアログ
更新日 : 2024-02-23
カテゴリ : プログラミング
画面
ダイアログで選択する例です。
ボタンの文字は大文字になってしまうのね...以下で解決。
【Material ui】Button配下の英字を自動的に大文字になることを防ぐ
FastAPIの方
テーブル名とカラム名を適当に返すだけ
@dataclass
class Field:
name: str
dtype: str
size: int = None
@dataclass
class Table:
name: str
fields: List[Field]
# groups: List[GroupItem]
class TableInfos(BaseModel):
version: str
# groupInfo: GroupInfo
tables: List[Table]
@app.get("/tables", response_model=TableInfos)
async def get_tables():
tables = []
for t_index in range(1, 15 + 1):
t_name = f"Table{t_index}"
fields = []
for f_index in range(1, t_index + 5 + 1):
size = None
if f_index%4 == 0:
type = "int"
elif f_index%4 == 1:
type = "str"
size = f_index
elif f_index%4 == 2:
type = "float"
else:
type = "bool"
fields.append(
Field(f"t{t_index}_col{f_index}_{type}", type, size)
)
tables.append(
Table(name=t_name, fields=fields)
)
return TableInfos(version="0.9.1", tables=tables)
Next.jsの方
page.tsx
"use client"
import { Typography, Stack, Box, Paper } from "@mui/material";
import * as React from 'react';
import Button from '@mui/material/Button';
import Grid from '@mui/material/Grid';
import { useEffect, useState } from "react";
import {useTableInfoStore} from "@/store/tableinfostore";
import ColumnSelectDialog from "./columnSelectDialog";
export default function Home() {
// テーブル情報取得ストア
const {tableinfo, fetchTables} = useTableInfoStore()
// ダイアログの開閉用
const [showColumnSelectDialog, setColumnSelectDialog] = useState(false);
// 選択したテーブル名
const [tableName, setTableName] = useState("");
// 選択したカラム名(全体)
const [selectedColumn, setSelectedColumn] = useState([""]);
useEffect(() => {
fetchTables();
setSelectedColumn([]);
}, []);
const clickTableName = (tname:string) => {
setTableName(tname)
setColumnSelectDialog(true)
}
const handleClose = (values: string[]) => {
console.log(values)
setColumnSelectDialog(false);
};
return (
<Box width={600}>
<Stack spacing={1} padding={2}>
<Typography>テーブル ダイアログのページ(カラムの選択をダイアログで行います)</Typography>
<Grid container spacing={2}>
{
tableinfo?.tables.map((tinfo) => {
let selected_col = false
tinfo.fields.map((field) => {
if ( selectedColumn.includes(field.name)) {
selected_col = true
}
})
return (
<Grid item xs={2} key={`g-${tinfo.name}`}>
<Button key={tinfo.name}
variant={selected_col? "contained" : "outlined"} color="secondary"
sx={{textTransform: "none"}}
onClick={ () => clickTableName(tinfo.name)}>
{tinfo.name}
</Button>
</Grid>
)
})
}
</Grid>
{/* カラム選択ダイアログ openの真偽値を更新してダイアログを開く */}
<ColumnSelectDialog
tableName={tableName}
tableInfo={tableinfo}
selectedColumn={selectedColumn}
open={showColumnSelectDialog}
onClose={handleClose}
/>
</Stack>
<Stack spacing={2} padding={2}>
<Typography>選択したカラムを以下に表示します</Typography>
<Grid container spacing={1}>
{
selectedColumn.map((scinfo) => {
return (
<Grid item xs={4} key={`g-${scinfo}`}>
<Button key={scinfo} variant="outlined"
color="secondary" sx={{textTransform: "none"}}>
{scinfo}
</Button>
</Grid>
)
})
}
</Grid>
</Stack>
</Box>
);
}
ダイアログ
import * as React from 'react';
import Dialog from '@mui/material/Dialog';
import Button from '@mui/material/Button';
import { Typography, Stack } from "@mui/material";
import Grid from '@mui/material/Grid';
import { TableInfos } from "@/openapi_generated";
import { useState } from "react";
export interface ColumnSelectDialogProps {
open: boolean;
tableName: string;
onClose: (values: string[]) => void;
tableInfo?: TableInfos;
selectedColumn: string[]
}
export default function columnSelectDialog(props: ColumnSelectDialogProps) {
const { onClose, tableName, tableInfo, open, selectedColumn } = props;
// 状態管理用
const [selectedCount, setSelectedCount] = useState(0);
const handleClose = () => {
onClose(selectedColumn);
};
const clickColumnName = (columnName:string) => {
if ( selectedColumn.includes(columnName)) {
const index = selectedColumn.indexOf(columnName);
selectedColumn.splice(index, 1)
} else {
selectedColumn.push(columnName)
}
// 状態更新
setSelectedCount(selectedCount+1)
}
return (
<Dialog onClose={handleClose} open={open} sx={{width: 900}}>
<Stack spacing={1} margin={2}>
<Typography>テーブル名 : {tableName}</Typography>
<Typography>カラムを選択してください</Typography>
{
tableInfo?.tables.filter((tinfo) => tinfo.name == tableName).map((tinfo) => {
return (
<Grid key={`g-${tinfo.name}`} container spacing={1}>
{
tinfo.fields.map((field) => {
let selected_col = false
if ( selectedColumn.includes(field.name)) {
selected_col = true
}
return (
<Grid item xs={4} key={`g-${tinfo.name}-${field.name}`}>
<Button key={`${tinfo.name}-${field.name}`}
variant={selected_col? "contained" : "outlined"}
color={"secondary"} sx={{textTransform: "none"}}
onClick={() => clickColumnName(field.name)}>
{field.name}
</Button>
</Grid>
)
})
}
</Grid>
)
})
}
<Button variant='contained' onClick={handleClose}>閉じる</Button>
</Stack>
</Dialog>
);
}
ストア
import { create } from "zustand";
import { TableInfos } from "@/openapi_generated";
import { API_URL } from "@/store/settings";
type Tables = {
tableinfo?: TableInfos;
fetchTables: () => void;
};
export const useTableInfoStore = create<Tables>((set) => ({
tableinfo: undefined,
fetchTables: async () => {
try {
const response = await fetch(`${API_URL}/tables`);
const res_json = await response.json();
set({tableinfo: res_json})
} catch (error) {
console.error("Error fetching tables:", error);
}
},
}));