Skip to content
This repository was archived by the owner on Feb 10, 2025. It is now read-only.

チームエクスポート機能の実装 #27

Merged
merged 2 commits into from
May 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions app/(authenticated)/teams/export/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {Breadcrumbs, Link, Stack, Typography} from "@mui/material";
import CardBackground from "@/components/layout/cardBackground";
import {teamTagFactory} from "@/src/models/TeamTagModel";
import {teamFactory} from "@/src/models/TeamModel";
import React from "react";
import ExportTeams from "@/components/teams/export/exportTeams";
import {userFactory} from "@/src/models/UserModel";
import {classFactory} from "@/src/models/ClassModel";

export default async function TeamPage() {
const teams = await teamFactory().index()
const teamTags = await teamTagFactory().index()
const users = await userFactory().index()
const classes = await classFactory().index()

return (
<Stack spacing={1} mx={2} my={3}>
<Breadcrumbs aria-label="breadcrumb" sx={{pl: 2}}>
<Link underline="hover" color="inherit" href="/">
管理者のダッシュボード
</Link>
<Link underline="hover" color="inherit" href="/teams/">
チーム管理
</Link>
<Typography color="text.primary">
チームデータのエクスポート
</Typography>
</Breadcrumbs>
<CardBackground title={"チームデータのエクスポート"}>
<ExportTeams
teams={teams}
teamTags={teamTags}
users={users}
classes={classes}
/>
</CardBackground>
</Stack>
)
}
17 changes: 14 additions & 3 deletions app/(authenticated)/teams/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import {Breadcrumbs, Link, Stack, Typography} from "@mui/material";
import CardBackground from "@/components/layout/cardBackground";
import TeamsAgGrid from "@/components/teams/teamsTable";
import {classFactory} from "@/src/models/ClassModel";
import {teamFactory} from "@/src/models/TeamModel";
import {teamTagFactory} from "@/src/models/TeamTagModel";

export default async function TeamPage() {
const classes = await classFactory().index()
const teams = await teamFactory().index()
const teamTags = await teamTagFactory().index()

export default function TeamPage() {
return (
<Stack spacing={1} mx={2} my={3}>
<Breadcrumbs aria-label="breadcrumb" sx={{pl:2}}>
Expand All @@ -11,8 +18,12 @@ export default function TeamPage() {
</Link>
<Typography color="text.primary">チーム管理</Typography>
</Breadcrumbs>
<CardBackground title={"すべてのチーム"} button={"CSVで一括作成"}>
<TeamsAgGrid/>
<CardBackground title={"すべてのチーム"} button={"エクスポート"} link={"/teams/export"}>
<TeamsAgGrid
classes={classes}
teams={teams}
teamTags={teamTags}
/>
</CardBackground>
</Stack>
)
Expand Down
160 changes: 160 additions & 0 deletions components/teams/export/exportTeams.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
"use client"
import {Team} from "@/src/models/TeamModel";
import {TeamTag} from "@/src/models/TeamTagModel";
import {User} from "@/src/models/UserModel";
import {Class} from "@/src/models/ClassModel";
import {InputLabel, Select, FormControl, SelectChangeEvent, Stack, MenuItem, Button} from "@mui/material";
import {useState} from "react";
import * as XLSX from "xlsx";

export type ExportTeamsProps = {
teams: Team[]
teamTags: TeamTag[]
users: User[]
classes: Class[]
}

type DetailPageData = {
"チーム名": string
"経験者数": number
"クラス": string
"メンバー数": number
}

type TeamData = {
teamName: string
athleteNumber: number
teamClassName: string
teamMembers: TeamMemberData[]
}

type TeamMemberData = {
"名前": string
"番号": string
"性別": string
}

type AthleteDataType = {
value: number
}

export default function ExportTeams(props: ExportTeamsProps) {
const [teamTagId, setTeamTagId] = useState<string>("-1")

const handleChange = (event: SelectChangeEvent) => {
setTeamTagId(event.target.value as string)
}

const handleExport = () => {
// check if teamTagId is selected
if (teamTagId === "-1") {
alert("チームタグを選択してください")
return
}

// check if teamTagId is valid
const teamTag = props.teamTags.find((teamTag) => teamTag.id === parseInt(teamTagId))
if (!teamTag) {
alert("チームタグが見つかりません")
return
}

// filter teams by teamTagId
const filteredTeams = props.teams.filter((team) => team.teamTagId === parseInt(teamTagId))
if (filteredTeams.length === 0) {
alert("選択したチームタグに関連するチームが見つかりません")
return
}

// format data
const teamDataList: TeamData[] = filteredTeams.map((team) => {
// get team members
const teamMembers: TeamMemberData[] = team.userIds.map((userId) => {
const user = props.users.find((user) => user.id === userId)
return {
"名前": user?.name || "",
// get email account name
"番号": user?.email.split("@")[0] || "",
"性別": user?.gender == "male" ? "男" : "女",
}
})

// get class name
const teamClass = props.classes.find((class_) => class_.id === team.classId)

// get athlete number
let athleteNumber: number
try {
const athleteData: AthleteDataType = JSON.parse(team.description);
athleteNumber = athleteData.value
} catch (e) {
athleteNumber = 0
}

// formatted data
return {
teamName: team.name,
athleteNumber: athleteNumber,
teamClassName: teamClass?.name ?? "無所属",
teamMembers: teamMembers
}
})

const detailPageDataList: DetailPageData[] = teamDataList.map((teamData) => {
return {
"チーム名": teamData.teamName,
"経験者数": teamData.athleteNumber,
"クラス": teamData.teamClassName,
"メンバー数": teamData.teamMembers.length
}
})

// create workbook
const workbook = XLSX.utils.book_new()

// create detail page worksheet
const detailWorksheet = XLSX.utils.json_to_sheet(detailPageDataList)
XLSX.utils.book_append_sheet(workbook, detailWorksheet, "詳細ページ")

// create team member worksheet
teamDataList.forEach((teamData) => {
const teamMemberWorksheet = XLSX.utils.json_to_sheet(teamData.teamMembers)
XLSX.utils.book_append_sheet(workbook, teamMemberWorksheet, teamData.teamName)
})

// export workbook
XLSX.writeFile(workbook, `${teamTag.name}のチームデータ.xlsx`)
}

return (
<Stack
spacing={1}
>
<FormControl fullWidth>
<InputLabel id="team-tag-select-label">チームタグ</InputLabel>
<Select
labelId="team-tag-select-label"
id="team-tag-select"
value={teamTagId}
label="team-tag"
onChange={handleChange}
>
<MenuItem value={-1}>チームタグを選択してください</MenuItem>
{
props.teamTags.map((teamTag) => (
<MenuItem key={teamTag.id} value={teamTag.id}>{teamTag.name}</MenuItem>
))
}
</Select>
</FormControl>

<Button
variant="contained"
fullWidth
onClick={handleExport}
>
エクスポート
</Button>
</Stack>
)
}
73 changes: 48 additions & 25 deletions components/teams/teamsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,69 @@
'use client'
import React, { useState } from 'react';
import { AgGridReact } from 'ag-grid-react';
import {useEffect, useState} from 'react';
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, ModuleRegistry } from 'ag-grid-community';
import { ClientSideRowModelModule } from 'ag-grid-community';
ModuleRegistry.registerModules([ ClientSideRowModelModule ]);
import {ColDef, ModuleRegistry} from 'ag-grid-community';
import {ClientSideRowModelModule} from 'ag-grid-community';
import {Team} from "@/src/models/TeamModel";
import {Class} from '@/src/models/ClassModel';
import {TeamTag} from "@/src/models/TeamTagModel";

ModuleRegistry.registerModules([ClientSideRowModelModule]);

export type TeamsAgGridProps = {
classes: Class[]
teams: Team[]
teamTags: TeamTag[]
}

// Row Data Interface
type IRow = {
userId: number;
studentName: string;
studentId: number;
studentClass: string;
studentTeam: string;
teamId: number;
teamName: string;
className: string;
teamTagName: string;
}


// Create new GridExample component
const TeamsAgGrid = () => {

const height= 'calc(100vh - 230px)';
const TeamsAgGrid = (props: TeamsAgGridProps) => {
const height = 'calc(100vh - 230px)';
// Row Data: The data to be displayed.
const [rowData, ] = useState<IRow[]>([
{ userId: 1, studentName: "山田太郎", studentId: 123456, studentClass: "D", studentTeam: "A2" },
{ userId: 2, studentName: "田中太郎", studentId: 123455, studentClass: "B", studentTeam: "C4" },
{ userId: 3, studentName: "岡田太郎", studentId: 123459, studentClass: "A", studentTeam: "A1" },
]);
const [rowData, setRowData] = useState<IRow[]>([]);

// Column Definitions: Defines & controls grid columns.
const [colDefs, ] = useState<ColDef<IRow>[]>([
{ field: "userId", headerName:"ユーザーID" },
{ field: "studentName", headerName:"名前" },
{ field: "studentId", headerName:"学籍番号" },
{ field: "studentClass", headerName:"クラス" },
{ field: "studentTeam", headerName:"チーム" },
const [colDefs,] = useState<ColDef<IRow>[]>([
{field: "teamId", headerName: "チームID"},
{field: "teamName", headerName: "チーム名"},
{field: "className", headerName: "クラス"},
{field: "teamTagName", headerName: "タグ"},
]);

useEffect(() => {
const rows = props.teams.map((team): IRow => {
const className = props.classes.find((c) => c.id === team.classId)?.name
const teamTagName = props.teamTags.find((t) => t.id === team.teamTagId)?.name

return {
teamId: team.id,
teamName: team.name,
className: className ?? "不明",
teamTagName: teamTagName ?? "不明",
}
})

// set row data
setRowData(rows)
},
[props.classes, props.teams, props.teamTags]
)


// Container: Defines the grid's theme & dimensions.
return (
<div className={"ag-theme-quartz"} style={{ width: '100%', height: height, borderRadius:"10px" }}>
<div className={"ag-theme-quartz"} style={{width: '100%', height: height, borderRadius: "10px"}}>
<AgGridReact
rowData={rowData}
columnDefs={colDefs}
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"react-dom": "^18",
"react-icons": "^5.0.1",
"reactflow": "^11.11.0",
"sharp": "^0.33.3"
"sharp": "^0.33.3",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz"
},
"devDependencies": {
"@svgr/webpack": "^8.1.0",
Expand Down
4 changes: 4 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4912,6 +4912,10 @@ wrappy@1:
resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==

"xlsx@https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz":
version "0.20.2"
resolved "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz#0f64eeed3f1a46e64724620c3553f2dbd3cd2d7d"

yallist@^3.0.2:
version "3.1.1"
resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz"
Expand Down
Loading