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

Commit

Permalink
Merge pull request #43 from Sports-day/feature/rename-teamname-automa…
Browse files Browse the repository at this point in the history
…tion

チーム名一括変更機能の実装
  • Loading branch information
testusuke authored May 20, 2024
2 parents b3f8bb7 + 2f58928 commit 38fa6ed
Show file tree
Hide file tree
Showing 5 changed files with 553 additions and 70 deletions.
30 changes: 30 additions & 0 deletions app/(authenticated)/teams/automatic-rename/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {Breadcrumbs, Link, Stack, Typography} from "@mui/material";
import CardBackground from "@/components/layout/cardBackground";
import {teamFactory} from "@/src/models/TeamModel";
import React from "react";
import AutomaticRename from "@/components/teams/automatic-rename/automaticRename";

export default async function TeamAutomaticRenamePage() {
const teams = await teamFactory().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>
<AutomaticRename
teams={teams}
/>
</CardBackground>
</Stack>
)
}
25 changes: 22 additions & 3 deletions app/(authenticated)/teams/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Breadcrumbs, Link, Stack, Typography} from "@mui/material";
import {Breadcrumbs, Button, Link, Stack, Typography} from "@mui/material";
import CardBackground from "@/components/layout/cardBackground";
import TeamsAgGrid from "@/components/teams/teamsTable";
import {classFactory} from "@/src/models/ClassModel";
Expand All @@ -12,13 +12,32 @@ export default async function TeamPage() {

return (
<Stack spacing={1} mx={2} my={3}>
<Breadcrumbs aria-label="breadcrumb" sx={{pl:2}}>
<Breadcrumbs aria-label="breadcrumb" sx={{pl: 2}}>
<Link underline="hover" color="inherit" href="/">
管理者のダッシュボード
</Link>
<Typography color="text.primary">チーム管理</Typography>
</Breadcrumbs>
<CardBackground title={"すべてのチーム"} button={"エクスポート"} link={"/teams/export"}>
<CardBackground title={"すべてのチーム"}>
<Stack
spacing={1}
direction={"row"}
pb={1.5}
>
<Button
variant={"contained"}
href={"/teams/export"}
>
エクスポート
</Button>

<Button
variant={"contained"}
href={"/teams/automatic-rename"}
>
一括名前変更
</Button>
</Stack>
<TeamsAgGrid
classes={classes}
teams={teams}
Expand Down
205 changes: 205 additions & 0 deletions components/teams/automatic-rename/automaticRename.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
'use client'
import {Team, teamFactory} from "@/src/models/TeamModel";
import {useRouter} from "next/navigation";
import {
Button,
LinearProgress,
Stack,
TextField,
TextFieldProps,
Typography
} from "@mui/material";
import {useRef, useState} from "react";
import {HiArrowPath, HiCheck} from "react-icons/hi2";
import TeamRenameStatus from "@/components/teams/automatic-rename/teamRenameStatus";

export type AutomaticRenameProps = {
teams: Team[]
}

export type EditedTeam = {
id: number
oldTeamName: string
newTeamName: string | undefined
status: "success" | "not_found_team" | "not_link_yet" | "invalid_team_name"
}

export default function AutomaticRename(props: AutomaticRenameProps) {
const router = useRouter()
// ref
const csvDataRef = useRef<TextFieldProps>(null)
// state
const [editedTeams, setEditedTeams] = useState<EditedTeam[]>([])
const [progress, setProgress] = useState<number>(0)
const [isExecuting, setIsExecuting] = useState<boolean>(false)

const handleSubmit = async () => {
if (isExecuting) {
alert("処理中です")
return
}

// update
handleCSVDataChange()

// execute
setIsExecuting(true)
setProgress(0)

let index = 0;
for (const editedTeam of editedTeams) {
// original
const originalTeam = props.teams.find(team => team.id === editedTeam.id)

if (originalTeam !== undefined) {
if (editedTeam.status == "success" && editedTeam.newTeamName !== undefined) {
// update match
await teamFactory().update(editedTeam.id, {
name: editedTeam.newTeamName,
// origin
description: originalTeam.description,
classId: originalTeam.classId,
teamTagId: originalTeam.teamTagId,
})
}
}

// increment progress
index++
setProgress((index) / editedTeams.length * 100)

// delay for api server load
await timeout(100);
}

setIsExecuting(false)
// refresh
router.push("/teams")
}

const handleCSVDataChange = () => {
if (csvDataRef.current?.value === undefined) {
return
}

const csvData = csvDataRef.current.value as string
const rows = csvData.split("\n")

const editedTeamList: EditedTeam[] = []

for (const row of rows) {
const data = row.split(",")
const oldTeamName = data[0]
const newTeamName = data[1]

// team
const team = props.teams.find(team => team.name === oldTeamName)

// if team not found
if (team === undefined) {
// failed
editedTeamList.push({
id: -1,
oldTeamName: oldTeamName,
newTeamName: newTeamName,
status: "not_found_team"
})
continue
}

if (newTeamName === undefined || newTeamName === "") {
// failed
editedTeamList.push({
id: team.id,
oldTeamName: oldTeamName,
newTeamName: newTeamName,
status: "invalid_team_name"
})
continue
}

// success
editedTeamList.push({
id: team.id,
oldTeamName: oldTeamName,
newTeamName: newTeamName,
status: "success"
})
}

// add matches not exist in csv
const notExistTeams = props.teams.filter(team => {
return !editedTeamList.some(editedTeam => editedTeam.id === team.id)
}).map(team => {
return {
id: team.id,
oldTeamName: team.name,
newTeamName: undefined,
status: "not_link_yet"
} as EditedTeam
})

const results = editedTeamList.concat(notExistTeams)
setEditedTeams(results)
}

return (
<Stack m={0} spacing={1} direction={"column"}>
<Typography>
CSVデータ
</Typography>
<TextField
type={"text"}
name={"csv-data"}
id={"csv-data"}
placeholder={"CSVデータ"}
inputRef={csvDataRef}
multiline
rows={10}
fullWidth
required
onChange={() => handleCSVDataChange()}
/>

<TeamRenameStatus
editedTeamList={editedTeams}
/>

<Stack
direction={"row"}
my={0.5}
pt={3}
spacing={1}
width={"100%"}
justifyContent={"space-between"}
alignItems="center"
>
<Button
variant={"outlined"}
color={"error"}
startIcon={<HiArrowPath/>}
href={"/teams"}
>
戻る
</Button>
<Button
variant={"contained"}
color={"info"}
sx={{flexGrow: 3}}
startIcon={<HiCheck/>}
onClick={handleSubmit}
>
実行
</Button>
</Stack>

{isExecuting &&
<LinearProgress variant="determinate" value={progress}/>
}
</Stack>
)
}

function timeout(delay: number) {
return new Promise(res => setTimeout(res, delay));
}
76 changes: 76 additions & 0 deletions components/teams/automatic-rename/teamRenameStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
'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, ModuleRegistry} from 'ag-grid-community';
import {ClientSideRowModelModule} from 'ag-grid-community';
import {EditedTeam} from "@/components/teams/automatic-rename/automaticRename";

ModuleRegistry.registerModules([ClientSideRowModelModule]);

export type TeamRenameStatusProps = {
editedTeamList: EditedTeam[]
}

// Row Data Interface
type IRow = {
id: number,
oldTeamName: string,
newTeamName: string,
status: string
}

export default function TeamRenameStatus(props: TeamRenameStatusProps) {
const height = 'calc(100vh - 230px)';
// Column Definitions: Defines & controls grid columns.
const colDefs: ColDef<IRow>[] = [
{field: "id", headerName: "ID"},
{field: "oldTeamName", headerName: "変更前の名前"},
{field: "newTeamName", headerName: "変更後の名前"},
{field: "status", headerName: "ステータス"},
]

const rowData: IRow[] = props.editedTeamList.map((editedTeam) => {

// status
let status = ""
switch (editedTeam.status) {
case "success":
status = "✅成功"
break
case "not_found_team":
status = "❌チームが見つからない"
break
case "not_link_yet":
status = "❌紐付けできない"
break
case "invalid_team_name":
status = "❌チーム名が無効"
break
}

return {
id: editedTeam.id,
oldTeamName: editedTeam.oldTeamName,
newTeamName: editedTeam.newTeamName ?? "",
status: status
}
})

return (
<div
className={"ag-theme-quartz"}
style={{
width: '100%',
height: height,
borderRadius: "10px"
}}
>
<AgGridReact
rowData={rowData}
columnDefs={colDefs}
/>
</div>
)
}
Loading

0 comments on commit 38fa6ed

Please sign in to comment.