This repository was archived by the owner on Feb 10, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #43 from Sports-day/feature/rename-teamname-automa…
…tion チーム名一括変更機能の実装
- Loading branch information
Showing
5 changed files
with
553 additions
and
70 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
) | ||
} |
Oops, something went wrong.