Skip to content

Commit

Permalink
Merge branch 'main' into feature/#21-league
Browse files Browse the repository at this point in the history
  • Loading branch information
1nayu committed May 9, 2024
2 parents 245e0dc + 5faf068 commit 38ca775
Show file tree
Hide file tree
Showing 6 changed files with 267 additions and 29 deletions.
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 @@ -27,7 +27,8 @@
"react-icons": "^5.0.1",
"react-use": "^17.5.0",
"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 @@ -5098,6 +5098,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

0 comments on commit 38ca775

Please sign in to comment.