-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
1,129 additions
and
550 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,110 @@ | ||
import { Dialog } from "@headlessui/react"; | ||
import Member from "../../models/Member"; | ||
import { PlayerNumber } from "../../models/PlayerNumber"; | ||
import { useState } from "react"; | ||
import { PlayerNumberRepo } from "../../repository/PlayerNumberRepo"; | ||
|
||
function QueryFieldView({ query, setQuery }: { query: string, setQuery: (q: string) => void }) { | ||
return ( | ||
<input | ||
type="text" | ||
value={query} | ||
onChange={(e) => setQuery(e.target.value)} | ||
placeholder="名前やポジションでフィルタ" | ||
className="w-full p-2 border border-gray-300 rounded-md mb-2" | ||
/> | ||
); | ||
} | ||
|
||
function filterfunc(m: Member, query: string): boolean { | ||
if (m.slack.deleted) return false; | ||
if (query === "") return true; | ||
const q = query.toLowerCase(); | ||
if (m.slack.id.toLowerCase().includes(q)) return true; | ||
if (m.slack.name.toLowerCase().includes(q)) return true; | ||
if (m.slack.real_name.toLowerCase().includes(q)) return true; | ||
if (m.slack.profile.display_name.toLowerCase().includes(q)) return true; | ||
if (m.slack.profile.real_name.toLowerCase().includes(q)) return true; | ||
if (m.slack.profile.title.toLowerCase().includes(q)) return true; | ||
return false; | ||
} | ||
|
||
function MemberListView({ | ||
members, query, commit, | ||
previousassign | ||
}: { | ||
members: { [slack_id: string]: Member }, query: string, commit: (m: Member, deprive?: boolean) => void, | ||
previousassign?: Member; | ||
}) { | ||
return ( | ||
<div className="space-y-2 h-96 overflow-y-auto"> | ||
{Object.values(members).filter(m => filterfunc(m, query)).map((m) => ( | ||
<div key={m.slack.id} className="flex space-x-2 items-center"> | ||
<div> | ||
<img src={m.slack.profile.image_512} className="w-8 rounded-sm" | ||
alt={m.slack.real_name} | ||
/> | ||
</div> | ||
<div className="flex-1">{m.slack.real_name}</div> | ||
<div className="w-12 overflow-y-scroll whitespace-nowrap">{m.slack.profile.title}</div> | ||
<div className=""> | ||
{previousassign?.slack?.id == m.slack.id ? | ||
<button className="bg-red-400 text-white flex justify-center items-center py-1 px-2" | ||
onClick={() => commit(m, true)} | ||
>割当を外す</button> | ||
: | ||
<button className="bg-blue-200 text-white flex justify-center items-center py-1 px-2" | ||
onClick={() => commit(m)} | ||
>割り当てる</button> | ||
} | ||
</div> | ||
</div> | ||
))} | ||
</div> | ||
); | ||
} | ||
|
||
export function NumAssignModalContent({ | ||
close, members, playernumber, | ||
previousassign, | ||
loading, | ||
}: { | ||
close: () => void; | ||
members: { [slack_id: string]: Member }; | ||
playernumber: PlayerNumber; | ||
previousassign?: Member; | ||
loading: { start: () => void, stop: () => void }; | ||
}) { | ||
const [query, setQuery] = useState(""); | ||
return ( | ||
<div | ||
className="inline-block w-full max-w-md p-6 my-8 overflow-hidden text-left align-middle transition-all transform bg-white shadow-xl rounded-2xl" | ||
> | ||
<Dialog.Title as="h3" className="font-medium leading-6 text-gray-900 mb-2"> | ||
<span>背番号</span> <span className="text-4xl text-red-900 border p-1">{playernumber.number}</span> <span>を選手に割り当てる</span> | ||
</Dialog.Title> | ||
|
||
<div> | ||
<QueryFieldView query={query} setQuery={setQuery} /> | ||
<MemberListView members={members} query={query} previousassign={previousassign} | ||
commit={async (member, deprive = false) => { | ||
let message = `背番号${playernumber.number}を\n${member.slack.real_name}に割り当てますか?`; | ||
if (previousassign) message += `\n\nこれにより、${previousassign.slack.real_name}から背番号${playernumber.number}の割り当てを外します。`; | ||
if (deprive) message = `背番号${playernumber.number}の割り当てを${previousassign.slack.real_name}から外しますか?`; | ||
if (!window.confirm(message)) return; | ||
close(); | ||
loading.start(); | ||
const repo = new PlayerNumberRepo(); | ||
await repo.assign(playernumber, member.slack.id, deprive); | ||
loading.stop(); | ||
// refresh the page data | ||
location.reload(); | ||
}} | ||
/> | ||
</div> | ||
<div> | ||
<button onClick={close} className="mt-4 bg-red-200 text-gray-800 py-1 px-4 rounded-md">やっぱりやめる</button> | ||
</div> | ||
</div> | ||
); | ||
} |
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,25 @@ | ||
import Member from "./Member"; | ||
|
||
export class PlayerNumber { | ||
public player?: Member; // Populated field | ||
constructor( | ||
public number: number, | ||
public uniforms: Uniform[], | ||
public player_id: string, // Slack ID | ||
) { } | ||
|
||
static fromResponse(data: any) { | ||
return data.map((p) => new PlayerNumber(p.number, p.uniforms, p.player_id)); | ||
} | ||
} | ||
|
||
export class Uniform { | ||
public owner?: Member; // Populated field | ||
constructor( | ||
public number: number, | ||
public size: string, | ||
public color: boolean, | ||
public damaged: boolean, | ||
public owner_id: string, // Slack ID | ||
) { } | ||
} |
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,122 @@ | ||
import { useEffect, useMemo, useState } from "react"; | ||
import Layout from "../../components/layout"; | ||
import { PlayerNumberRepo } from "../../repository/PlayerNumberRepo"; | ||
import { useRouter } from "next/router"; | ||
import { PlayerNumber } from "../../models/PlayerNumber"; | ||
import Member from "../../models/Member"; | ||
import { Dialog } from "@headlessui/react"; | ||
import { NumAssignModalContent } from "../../components/Uniforms/ModalContents"; | ||
|
||
async function listMembers(incdel: boolean): Promise<Member[]> { | ||
const endpoint = process.env.API_BASE_URL + "/api/1/members"; | ||
const res = await fetch(endpoint + (incdel ? "?include_deleted=1" : "")); | ||
return (await res.json()).map((m) => Member.fromAPIResponse(m)); | ||
} | ||
|
||
function PlayerNumberListView({ playernumbers, members, loading }: { | ||
playernumbers: PlayerNumber[]; | ||
members: { [slack_id: string]: Member }; | ||
loading: { start: () => void, stop: () => void }; | ||
}) { | ||
const [modalContent, setModalContante] = useState<JSX.Element | null>(null); | ||
return ( | ||
<div className="divide-y"> | ||
<div className="flex space-x-2 justify-center items-center"> | ||
<div className="w-8">#</div> | ||
<div className="flex-1">ユニフォーム</div> | ||
<div>割り当て</div> | ||
</div> | ||
{playernumbers.sort((p, n) => p.number > n.number ? 1 : -1).map((p) => ( | ||
<div key={p.number} className="flex space-x-2 justify-center items-center py-1"> | ||
<div className="w-8">{p.number}</div> | ||
<div className="flex-1"> | ||
{(p.uniforms || []).map((u, i) => <div key={i}></div>)} | ||
<button className="bg-gray-200 hover:bg-blue-200 text-white w-8 h-8 flex justify-center items-center">+</button> | ||
</div> | ||
<button className="bg-gray-200 hover:bg-blue-200 text-white rounded-full w-8 h-8 flex justify-center items-center" | ||
onClick={() => setModalContante(<NumAssignModalContent | ||
playernumber={p} | ||
close={() => setModalContante(null)} | ||
members={members} | ||
loading={loading} | ||
previousassign={members[p.player_id]} | ||
/>)} | ||
> | ||
{p.player_id && members[p.player_id] ? <img src={members[p.player_id].slack.profile.image_512} | ||
alt={members[p.player_id].slack.real_name} | ||
/> : "+"} | ||
</button> | ||
</div> | ||
))} | ||
<Dialog | ||
open={modalContent !== null} | ||
as="div" | ||
className="fixed inset-0 z-10 overflow-y-auto" | ||
onClose={() => setModalContante(null)} | ||
> | ||
<div className="min-h-screen px-4 text-center"> | ||
<Dialog.Overlay className="fixed inset-0 bg-black bg-opacity-40" /> | ||
{/* This element is to trick the browser into centering the modal contents. */} | ||
<span className="inline-block h-screen align-middle" aria-hidden="true">​</span> | ||
{modalContent} | ||
</div> | ||
</Dialog> | ||
</div> | ||
); | ||
} | ||
|
||
function UniformClothesListView({ uniforms, members }: { | ||
uniforms: any[]; | ||
members: { [slack_id: string]: Member }; | ||
}) { | ||
return ( | ||
<div> | ||
{uniforms.map((u) => ( | ||
<div key={u.id} className="flex space-x-2"> | ||
<div>{u.number}</div> | ||
<div>{u.size}</div> | ||
<div>{u.color}</div> | ||
<div>{u.damaged}</div> | ||
<div>{u.owner_id}</div> | ||
</div> | ||
))} | ||
</div> | ||
); | ||
} | ||
|
||
export default function Uniforms(props) { | ||
const repo = useMemo(() => new PlayerNumberRepo(), []); | ||
const [playernumbers, setPlayernumbers] = useState<PlayerNumber[]>([]); | ||
const [members, setMembers] = useState<{[slack_id:string]:Member}>({}) | ||
useEffect(() => { | ||
listMembers(true).then(mems => setMembers(mems.reduce((acc, mem) => ({...acc, [mem.slack.id]: mem}), {}))); | ||
repo.all().then(a => setPlayernumbers(PlayerNumber.fromResponse(a))); | ||
}, [repo]); | ||
const active = `shadow-inner rounded-t-lg bg-blue-600 text-white`; | ||
const inactive = `shadow rounded-t-lg text-gray-600`; | ||
const router = useRouter(); | ||
const hash = router.asPath.split("#")[1]; | ||
return ( | ||
<Layout {...props}> | ||
<div className="flex space-x-2"> | ||
<div className={"flex-1 p-4 text-center cursor-pointer " + (hash !== "clothes" ? active : inactive)} | ||
onClick={() => router.push("/uniforms#numbers")} | ||
>背番号</div> | ||
<div className={"flex-1 p-4 text-center cursor-pointer " + (hash === "clothes" ? active : inactive)} | ||
// onClick={() => props.router.push("/uniforms")} | ||
onClick={() => router.push("/uniforms#clothes")} | ||
>ユニフォーム</div> | ||
</div> | ||
<div> | ||
{hash === "clothes" ? <UniformClothesListView | ||
uniforms={[]} | ||
members={members} | ||
/> : <PlayerNumberListView | ||
playernumbers={playernumbers} | ||
members={members} | ||
loading={{ start: props.startLoading, stop: props.stopLoading }} | ||
/>} | ||
</div> | ||
</Layout> | ||
); | ||
} |
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,16 @@ | ||
import { PlayerNumber } from "../models/PlayerNumber"; | ||
|
||
export class PlayerNumberRepo { | ||
constructor( | ||
public baseURL = process.env.API_BASE_URL, | ||
) { } | ||
all(): Promise<PlayerNumber[]> { | ||
return fetch(`${this.baseURL}/api/1/numbers`) | ||
.then((res) => res.json()) | ||
.then((json) => json.map((p) => new PlayerNumber(p.number, p.uniforms, p.player_id))); | ||
} | ||
assign(pn: PlayerNumber, player_id: string, deprive: boolean): Promise<Response> { // TODO: ちゃんとする | ||
const endpoint = `${this.baseURL}/api/1/numbers/${pn.number}/${deprive ? "deprive" : "assign"}`; | ||
return fetch(endpoint, {method: "POST", body: JSON.stringify({ player_id })}); | ||
} | ||
} |
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
Oops, something went wrong.