From d2734666b86f6278ac941f4c5713fca228cca23e Mon Sep 17 00:00:00 2001 From: testusuke Date: Mon, 20 May 2024 10:28:07 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E3=83=81=E3=83=BC=E3=83=A0?= =?UTF-8?q?=E3=83=A1=E3=83=B3=E3=83=90=E3=83=BC=E5=89=8A=E9=99=A4=E6=A9=9F?= =?UTF-8?q?=E8=83=BD=E3=81=AE=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/teams/teamEditor.tsx | 52 ++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/components/teams/teamEditor.tsx b/components/teams/teamEditor.tsx index 18f0719..6766f95 100644 --- a/components/teams/teamEditor.tsx +++ b/components/teams/teamEditor.tsx @@ -2,11 +2,11 @@ import { Box, Button, - FormControl, InputLabel, Paper, + FormControl, InputLabel, Paper, Stack, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, Typography } from "@mui/material"; -import { HiCheck } from "react-icons/hi2"; +import {HiCheck, HiMiniTrash} from "react-icons/hi2"; import React, {useState} from "react"; import {useRouter} from "next/navigation"; import {Team, teamFactory} from "@/src/models/TeamModel"; @@ -15,9 +15,9 @@ import {User} from "@/src/models/UserModel"; import TeamDelete from "@/components/teams/teamDelete"; type TeamEditorProps = { - class : Class; - team : Team; - teamUser : User[]; + class: Class; + team: Team; + teamUser: User[]; } export default function TeamEditor(props: TeamEditorProps) { @@ -37,6 +37,12 @@ export default function TeamEditor(props: TeamEditorProps) { } + const removeUser = async (userId: number) => { + await teamFactory().removeTeamUser(props.team.id, userId) + // refresh + router.refresh() + } + return ( <> @@ -49,7 +55,7 @@ export default function TeamEditor(props: TeamEditorProps) { { @@ -82,14 +88,14 @@ export default function TeamEditor(props: TeamEditorProps) { > 所属クラス - + {props.class.name} - + - + - 学籍番号 - 名前 - 性別 + 学籍番号 + 名前 + 性別 + 削除 {props.teamUser.map((member) => { return ( - {member.id} - {member.name} - + {member.id} + {member.name} + {member.gender === "male" ? "男性" : "女性"} + + + ); })} From 5f77e93248efbb36984b380995eb54b7b12b9d2f Mon Sep 17 00:00:00 2001 From: testusuke Date: Mon, 20 May 2024 21:29:12 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=E3=83=A6=E3=83=BC=E3=82=B6?= =?UTF-8?q?=E3=83=BC=E8=BF=BD=E5=8A=A0=E6=A9=9F=E8=83=BD=E3=81=AE=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(authenticated)/teams/[id]/page.tsx | 8 +- components/teams/addTeamMemberDialog.tsx | 131 +++++++++++++++++++++++ components/teams/teamEditor.tsx | 30 +++++- 3 files changed, 165 insertions(+), 4 deletions(-) create mode 100644 components/teams/addTeamMemberDialog.tsx diff --git a/app/(authenticated)/teams/[id]/page.tsx b/app/(authenticated)/teams/[id]/page.tsx index 4da4fba..361ce10 100644 --- a/app/(authenticated)/teams/[id]/page.tsx +++ b/app/(authenticated)/teams/[id]/page.tsx @@ -8,7 +8,8 @@ import {classFactory} from "@/src/models/ClassModel"; export default async function TeamDetailPage({ params }: { params: { id: string } }) { const teamId = parseInt(params.id, 10) const teamInfo = await teamFactory().show(teamId) - const classes = await classFactory().show(teamInfo.classId) + const classModel = await classFactory().show(teamInfo.classId) + const classUsers = await classFactory().getUsers(teamInfo.classId) const teamUsers = await teamFactory().getTeamUsers(teamId); return ( @@ -17,7 +18,7 @@ export default async function TeamDetailPage({ params }: { params: { id: string 管理者のダッシュボード - + チーム管理 {teamInfo.name} @@ -27,7 +28,8 @@ export default async function TeamDetailPage({ params }: { params: { id: string diff --git a/components/teams/addTeamMemberDialog.tsx b/components/teams/addTeamMemberDialog.tsx new file mode 100644 index 0000000..2908d91 --- /dev/null +++ b/components/teams/addTeamMemberDialog.tsx @@ -0,0 +1,131 @@ +'use client' +import {AgGridReact} from 'ag-grid-react'; +import "ag-grid-community/styles/ag-grid.css"; // Mandatory CSS required by the grid +import "ag-grid-community/styles/ag-theme-quartz.css"; // Optional Theme applied to the grid +import {ColDef, GridApi, GridReadyEvent, ModuleRegistry} from 'ag-grid-community'; +import {ClientSideRowModelModule} from 'ag-grid-community'; +import {Button, Dialog, DialogActions, DialogContent, DialogTitle, Typography} from "@mui/material"; +import {User} from "@/src/models/UserModel"; +import {Team, teamFactory} from "@/src/models/TeamModel"; +import {useState} from "react"; +import {useRouter} from "next/navigation"; + +ModuleRegistry.registerModules([ClientSideRowModelModule]); + +export type AddTeamMemberDialogProps = { + isOpen: boolean + setClose: () => void + team: Team + users: User[] +} + +// Row Data Interface +type IRow = { + id: number, + username: string, + gender: string, + emailAccountName: string, +} + +export default function AddTeamMemberDialog(props: AddTeamMemberDialogProps) { + const router = useRouter() + const height = 'calc(100vh - 230px)'; + const [gridApi, setGridApi] = useState | null>(null) + // Column Definitions: Defines & controls grid columns. + const colDefs: ColDef[] = [ + { + field: "id", + headerName: "ID", + headerCheckboxSelection: true, + checkboxSelection: true, + showDisabledCheckboxes: true, + }, + {field: "username", headerName: "ユーザー名"}, + {field: "gender", headerName: "性別"}, + {field: "emailAccountName", headerName: "学籍番号"}, + ] + + const rowData: IRow[] = props.users.map((user) => { + return { + id: user.id, + username: user.name, + gender: user.gender == "male" ? "男" : "女", + emailAccountName: user.email.split("@")[0] + } as IRow + }) + + const handleGridReady = (params: GridReadyEvent) => { + setGridApi(params.api) + } + + const handleSubmit = async () => { + if (!gridApi) { + alert("エラーが発生しました") + return + } + + const selectedRowIds = gridApi + .getSelectedRows() + .map((row) => row.id) + + if (selectedRowIds.length === 0) { + alert("ユーザーを選択してください") + return + } + + // add team user + await teamFactory().addTeamUsers(props.team.id, selectedRowIds) + + // refresh + router.refresh() + // close + props.setClose() + } + + return ( + + + + チームメンバーの追加 + + + +
+ +
+
+ + + + +
+ ) +} \ No newline at end of file diff --git a/components/teams/teamEditor.tsx b/components/teams/teamEditor.tsx index 6766f95..cab1f7a 100644 --- a/components/teams/teamEditor.tsx +++ b/components/teams/teamEditor.tsx @@ -6,23 +6,26 @@ import { Stack, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, Typography } from "@mui/material"; -import {HiCheck, HiMiniTrash} from "react-icons/hi2"; +import {HiCheck, HiMiniTrash, HiPlus} from "react-icons/hi2"; import React, {useState} from "react"; import {useRouter} from "next/navigation"; import {Team, teamFactory} from "@/src/models/TeamModel"; import {Class} from "@/src/models/ClassModel"; import {User} from "@/src/models/UserModel"; import TeamDelete from "@/components/teams/teamDelete"; +import AddTeamMemberDialog from "@/components/teams/addTeamMemberDialog"; type TeamEditorProps = { class: Class; team: Team; teamUser: User[]; + classUsers: User[]; } export default function TeamEditor(props: TeamEditorProps) { const router = useRouter() const [teamName, setTeamName] = useState(props.team.name) + const [isOpenTeamMemberAddDialog, setIsOpenTeamMemberAddDialog] = useState(false) const handleSubmit = async () => { await teamFactory().update(props.team.id, { @@ -43,6 +46,10 @@ export default function TeamEditor(props: TeamEditorProps) { router.refresh() } + const notTeamUsers = props.classUsers.filter((user) => { + return !props.teamUser.some((teamUser) => teamUser.id === user.id) + }) + return ( <> @@ -153,6 +160,27 @@ export default function TeamEditor(props: TeamEditorProps) { + { + setIsOpenTeamMemberAddDialog(false) + }} + team={props.team} + users={notTeamUsers} + /> + + +