From c8421f1aa61e6563f728e2a2f0e56211473a35d2 Mon Sep 17 00:00:00 2001 From: K-shir0 <50326556+K-shir0@users.noreply.github.com> Date: Mon, 18 Dec 2023 23:34:53 +0900 Subject: [PATCH] =?UTF-8?q?update:=20=E4=BA=88=E9=81=B8=E7=94=A8=E3=82=AB?= =?UTF-8?q?=E3=82=B9=E3=82=BF=E3=83=A0=E3=82=92=E8=BF=BD=E5=8A=A0=20(#114)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update: 予選用カスタムを追加 * add: preRoundMode フラグ * fix: session が切れたときにログアウト状態にするようにするようにした * update: 筆記問題と選択問題をグループ分けするように変更 * update: 回答済み一覧の採点のところを見えないように変更 * update: 回答送信済みか分かるように追加 * update: 筆記問題のときに再展開情報を取り行かないように修正 * update: 参加者一覧に所属を表示するように変更 * update: ranking hook に preRound の分岐を追加 * update: users 所属のテストを追加 * update: users 所属の e2e テストを追加 * update: commit hash used in e2e * fix: add _preRoundMode in _const --- .github/workflows/e2e.yml | 4 +- frontend/octavio/.env | 2 + .../octavio/__e2e__/scoring/index.test.ts | 13 ++-- frontend/octavio/__e2e__/users.test.ts | 27 ++++--- .../octavio/__test__/pages/login.test.tsx | 18 ++--- .../__test__/pages/problems/index.test.tsx | 5 +- .../__test__/pages/scoring/index.test.tsx | 21 ----- .../octavio/__test__/pages/signUp.test.tsx | 78 ++++++++++++++----- .../octavio/__test__/pages/users.test.tsx | 3 +- .../_components/answer-list-section.tsx | 33 +++++--- .../_components/multiple-answer-form.tsx | 2 +- .../octavio/app/problems/[problemId]/page.tsx | 2 +- frontend/octavio/app/problems/page.tsx | 42 ++++++++-- frontend/octavio/app/ranking/layout.tsx | 6 ++ frontend/octavio/app/scoring/page.tsx | 2 - frontend/octavio/app/users/page.tsx | 4 + frontend/octavio/components/_const.ts | 5 +- .../octavio/components/markdown-preview.tsx | 4 +- frontend/octavio/components/navbar.tsx | 15 ++-- frontend/octavio/components/problem-card.tsx | 27 +++++-- frontend/octavio/hooks/auth.ts | 12 ++- frontend/octavio/hooks/problems.ts | 12 ++- frontend/octavio/hooks/ranking.ts | 3 +- frontend/octavio/types/Problem.ts | 4 +- 24 files changed, 222 insertions(+), 122 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index b43aa342..afbebec1 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -138,7 +138,7 @@ jobs: with: repository: ictsc/ictsc-rikka path: ictsc-rikka - ref: c9fb8c2731f6bd2d1eeb3c3083732e296162fa01 + ref: 023061a63b1a2fb425939c36bcb7dd8bd2235b7c # MariaDB cache - name: Cache a MariaDB Docker image @@ -252,7 +252,7 @@ jobs: working-directory: ictsc-rikka run: | set +e # curlのエラーを無視する - cp scripts/docker-compose.override.yml docker-compose.override.yml + cp scripts/compose.ci.yml compose.override.yml make up url="http://localhost:8080" diff --git a/frontend/octavio/.env b/frontend/octavio/.env index 8ca5d405..1bc8e9f1 100644 --- a/frontend/octavio/.env +++ b/frontend/octavio/.env @@ -10,3 +10,5 @@ NEXT_PUBLIC_RULE= NEXT_PUBLIC_SHORT_RULE= # 再展開時のモーダルに表示される再展開時の注意事項 NEXT_PUBLIC_RECREATE_RULE= +# 予選モードを有効化 +NEXT_PUBLIC_PRE_ROUND_MODE= \ No newline at end of file diff --git a/frontend/octavio/__e2e__/scoring/index.test.ts b/frontend/octavio/__e2e__/scoring/index.test.ts index 9fd5de0a..9282f484 100644 --- a/frontend/octavio/__e2e__/scoring/index.test.ts +++ b/frontend/octavio/__e2e__/scoring/index.test.ts @@ -20,7 +20,7 @@ test("画面項目が表示されること", async ({ page }) => { const problem1 = problems.nth(0).locator("td"); await expect(problem1.nth(1)).toHaveText("1/1/1"); await expect(problem1.nth(2)).toHaveText( - "00000000-0000-4000-a000-000000000000" + "00000000-0000-4000-a000-000000000000", ); await expect(problem1.nth(3)).toHaveText("abc"); await expect(problem1.nth(4)).toHaveText("問題タイトル1"); @@ -28,12 +28,11 @@ test("画面項目が表示されること", async ({ page }) => { await expect(problem1.nth(6)).toHaveText("100"); await expect(problem1.nth(7)).toHaveText("100"); await expect(problem1.nth(8)).toHaveText(""); - await expect(problem1.nth(9)).toHaveText("自分"); const problem2 = problems.nth(1).locator("td"); await expect(problem2.nth(1)).toHaveText("1/1/1"); await expect(problem2.nth(2)).toHaveText( - "00000000-0000-4000-a000-000000000001" + "00000000-0000-4000-a000-000000000001", ); await expect(problem2.nth(3)).toHaveText("def"); await expect(problem2.nth(4)).toHaveText("問題タイトル2"); @@ -41,12 +40,11 @@ test("画面項目が表示されること", async ({ page }) => { await expect(problem2.nth(6)).toHaveText("200"); await expect(problem2.nth(7)).toHaveText("200"); await expect(problem2.nth(8)).toHaveText(""); - await expect(problem2.nth(9)).toHaveText("自分"); const problem3 = problems.nth(2).locator("td"); await expect(problem3.nth(1)).toHaveText("1/1/1"); await expect(problem3.nth(2)).toHaveText( - "00000000-0000-4000-a000-000000000002" + "00000000-0000-4000-a000-000000000002", ); await expect(problem3.nth(3)).toHaveText("ghi"); await expect(problem3.nth(4)).toHaveText("問題タイトル3"); @@ -54,7 +52,6 @@ test("画面項目が表示されること", async ({ page }) => { await expect(problem3.nth(6)).toHaveText("300"); await expect(problem3.nth(7)).toHaveText("300"); await expect(problem3.nth(8)).toHaveText(""); - await expect(problem3.nth(9)).toHaveText("自分"); }); test("採点ページへ遷移できること", async ({ page }) => { @@ -83,10 +80,10 @@ test("問題のプレビューができること", async ({ page }) => { // then await expect(ScoringPage.ProblemPreviewProblemInfo(page)).toHaveText( - "問題タイトル1満点100 pt 採点基準100 pt採点する" + "問題タイトル1満点100 pt 採点基準100 pt採点する", ); await expect(ScoringPage.ProblemPreviewProblemContent(page)).toHaveText( - "問題内容1" + "問題内容1", ); }); diff --git a/frontend/octavio/__e2e__/users.test.ts b/frontend/octavio/__e2e__/users.test.ts index 84896afa..9f9a104d 100644 --- a/frontend/octavio/__e2e__/users.test.ts +++ b/frontend/octavio/__e2e__/users.test.ts @@ -20,45 +20,54 @@ test("画面項目が表示されること", async ({ page }) => { const user1 = await cols.nth(0).locator("td"); await expect(user1.nth(0)).toHaveText("user1"); await expect(user1.nth(1)).toHaveText("team1"); - await expect(user1.nth(2)).toHaveText("自己紹介内容1"); + await expect(user1.nth(2)).toHaveText("user-org1"); + await expect(user1.nth(3)).toHaveText("自己紹介内容1"); const user2 = await cols.nth(1).locator("td"); await expect(user2.nth(0)).toHaveText("user2"); await expect(user2.nth(1)).toHaveText("team1"); - await expect(user2.nth(2)).toHaveText(""); + await expect(user2.nth(2)).toHaveText("user-org1"); + await expect(user2.nth(3)).toHaveText(""); const user3 = await cols.nth(2).locator("td"); await expect(user3.nth(0)).toHaveText("user3"); await expect(user3.nth(1)).toHaveText("team1"); - await expect(user3.nth(2)).toHaveText(""); + await expect(user3.nth(2)).toHaveText("user-org1"); + await expect(user3.nth(3)).toHaveText(""); const user4 = await cols.nth(3).locator("td"); await expect(user4.nth(0)).toHaveText("user4"); await expect(user4.nth(1)).toHaveText("team2"); - await expect(user4.nth(2)).toHaveText("自己紹介内容2"); + await expect(user4.nth(2)).toHaveText("user-org2"); + await expect(user4.nth(3)).toHaveText("自己紹介内容2"); const user5 = await cols.nth(4).locator("td"); await expect(user5.nth(0)).toHaveText("user5"); await expect(user5.nth(1)).toHaveText("team2"); - await expect(user5.nth(2)).toHaveText(""); + await expect(user5.nth(2)).toHaveText("user-org2"); + await expect(user5.nth(3)).toHaveText(""); const user6 = await cols.nth(5).locator("td"); await expect(user6.nth(0)).toHaveText("user6"); await expect(user6.nth(1)).toHaveText("team2"); - await expect(user6.nth(2)).toHaveText(""); + await expect(user6.nth(2)).toHaveText("user-org2"); + await expect(user6.nth(3)).toHaveText(""); const user7 = await cols.nth(6).locator("td"); await expect(user7.nth(0)).toHaveText("user7"); await expect(user7.nth(1)).toHaveText("team3"); - await expect(user7.nth(2)).toHaveText("自己紹介内容3"); + await expect(user7.nth(2)).toHaveText("user-org3"); + await expect(user7.nth(3)).toHaveText("自己紹介内容3"); const user8 = await cols.nth(7).locator("td"); await expect(user8.nth(0)).toHaveText("user8"); await expect(user8.nth(1)).toHaveText("team3"); - await expect(user8.nth(2)).toHaveText(""); + await expect(user8.nth(2)).toHaveText("user-org3"); + await expect(user8.nth(3)).toHaveText(""); const user9 = await cols.nth(8).locator("td"); await expect(user9.nth(0)).toHaveText("user9"); await expect(user9.nth(1)).toHaveText("team3"); - await expect(user9.nth(2)).toHaveText(""); + await expect(user9.nth(2)).toHaveText("user-org3"); + await expect(user9.nth(3)).toHaveText(""); }); diff --git a/frontend/octavio/__test__/pages/login.test.tsx b/frontend/octavio/__test__/pages/login.test.tsx index b56e2549..5d22eb4b 100644 --- a/frontend/octavio/__test__/pages/login.test.tsx +++ b/frontend/octavio/__test__/pages/login.test.tsx @@ -63,7 +63,7 @@ describe("Login", () => { expect(screen.queryByPlaceholderText("ユーザー名")).toBeInTheDocument(); expect(screen.queryByPlaceholderText("パスワード")).toBeInTheDocument(); expect(loginButton).toBeInTheDocument(); - expect(loginButton).not.toHaveAttribute("loading"); + expect(loginButton).not.toHaveAttribute("btn-disabled"); // verify expect(useAuth).toHaveBeenCalledTimes(1); @@ -83,10 +83,10 @@ describe("Login", () => { // then expect( - screen.queryByText("ユーザー名を入力してください") + screen.queryByText("ユーザー名を入力してください"), ).toBeInTheDocument(); expect( - screen.queryByText("パスワードを入力して下さい") + screen.queryByText("パスワードを入力して下さい"), ).toBeInTheDocument(); // verify @@ -109,10 +109,10 @@ describe("Login", () => { // then expect( - screen.queryByText("ユーザー名を入力してください") + screen.queryByText("ユーザー名を入力してください"), ).toBeInTheDocument(); expect( - screen.queryByText("パスワードを入力して下さい") + screen.queryByText("パスワードを入力して下さい"), ).not.toBeInTheDocument(); }); @@ -132,10 +132,10 @@ describe("Login", () => { // then expect( - screen.queryByText("ユーザー名を入力してください") + screen.queryByText("ユーザー名を入力してください"), ).not.toBeInTheDocument(); expect( - screen.queryByText("パスワードを入力して下さい") + screen.queryByText("パスワードを入力して下さい"), ).toBeInTheDocument(); // verify @@ -214,7 +214,7 @@ describe("Login", () => { setTimeout(() => { resolve({ code: 200 }); }, 1000); - }) + }), ); (useAuth as Mock).mockReturnValue({ user: null, @@ -233,7 +233,7 @@ describe("Login", () => { }); // then - expect(loginButton).toHaveClass("loading"); + expect(loginButton).toHaveClass("btn-disabled"); // verify expect(useAuth).toHaveBeenCalledTimes(2); diff --git a/frontend/octavio/__test__/pages/problems/index.test.tsx b/frontend/octavio/__test__/pages/problems/index.test.tsx index 2958a93e..1f65df79 100644 --- a/frontend/octavio/__test__/pages/problems/index.test.tsx +++ b/frontend/octavio/__test__/pages/problems/index.test.tsx @@ -51,7 +51,7 @@ describe("Problems", () => { expect(screen.queryByText("テスト通知タイトル")).toBeInTheDocument(); expect(screen.getAllByTestId("markdown-preview")[1]).toHaveAttribute( "data-content", - "テスト通知本文" + "テスト通知本文", ); expect(screen.queryByText("XYZ")).toBeInTheDocument(); expect(screen.queryByText("テスト問題タイトル")).toBeInTheDocument(); @@ -203,6 +203,7 @@ describe("Problems", () => { title: "title", site: "site", shortRule: "# ルール本文", + preRoundMode: false, })); (useRecoilState as Mock).mockReturnValue([[], vi.fn()]); (useProblems as Mock).mockReturnValue({ @@ -218,7 +219,7 @@ describe("Problems", () => { // then expect(screen.getAllByTestId("markdown-preview")[0]).toHaveAttribute( "data-content", - "# ルール本文" + "# ルール本文", ); // verify diff --git a/frontend/octavio/__test__/pages/scoring/index.test.tsx b/frontend/octavio/__test__/pages/scoring/index.test.tsx index d3f21100..12d8052a 100644 --- a/frontend/octavio/__test__/pages/scoring/index.test.tsx +++ b/frontend/octavio/__test__/pages/scoring/index.test.tsx @@ -130,7 +130,6 @@ describe("Scoring", () => { expect(tds[6]).toHaveTextContent("100"); expect(tds[7]).toHaveTextContent("150"); expect(tds[8]).toHaveTextContent(""); - expect(tds[9]).toHaveTextContent("自分"); // then expect(useAuth).toHaveBeenCalledTimes(1); @@ -255,26 +254,6 @@ describe("Scoring", () => { expect(useProblems).toHaveBeenCalledTimes(2); }); - test("問題作成者id が自分でない場合空文字が表示される", () => { - // setup - (useAuth as Mock).mockReturnValue({ - user: testAdminUser, - }); - (useProblems as Mock).mockReturnValue({ - problems: [{ ...testProblem, author_id: "other" }], - isLoading: false, - }); - render(); - const tds = screen.queryAllByRole("cell"); - - // when - expect(tds[9]).toHaveTextContent(""); - - // then - expect(useAuth).toHaveBeenCalledTimes(1); - expect(useProblems).toHaveBeenCalledTimes(2); - }); - test("問題をクリックした場合、問題文が表示される", async () => { // setup (useAuth as Mock).mockReturnValue({ diff --git a/frontend/octavio/__test__/pages/signUp.test.tsx b/frontend/octavio/__test__/pages/signUp.test.tsx index 5889d76c..81860f52 100644 --- a/frontend/octavio/__test__/pages/signUp.test.tsx +++ b/frontend/octavio/__test__/pages/signUp.test.tsx @@ -10,6 +10,23 @@ import { Mock, vi } from "vitest"; import SignUp from "@/app/signUp/page"; import useAuth from "@/hooks/auth"; +class CustomError extends Error { + code: number; + + response: { data: { error: string } }; + + constructor( + message: string, + code: number, + response: { data: { error: string } }, + ) { + super(message); + this.code = code; + this.response = response; + Object.setPrototypeOf(this, CustomError.prototype); + } +} + vi.mock("@/hooks/auth"); vi.mock("@/components/Alerts", () => ({ ICTSCSuccessAlert: ({ @@ -93,10 +110,10 @@ describe("SignUp", () => { // then expect( - screen.queryByText("ユーザー名を入力してください") + screen.queryByText("ユーザー名を入力してください"), ).toBeInTheDocument(); expect( - screen.queryByText("パスワードを入力して下さい") + screen.queryByText("パスワードを入力して下さい"), ).toBeInTheDocument(); // verify @@ -119,10 +136,10 @@ describe("SignUp", () => { // then expect( - screen.queryByText("ユーザー名を入力してください") + screen.queryByText("ユーザー名を入力してください"), ).toBeInTheDocument(); expect( - screen.queryByText("パスワードを入力して下さい") + screen.queryByText("パスワードを入力して下さい"), ).not.toBeInTheDocument(); // verify @@ -145,10 +162,10 @@ describe("SignUp", () => { // then expect( - screen.queryByText("ユーザー名を入力してください") + screen.queryByText("ユーザー名を入力してください"), ).not.toBeInTheDocument(); expect( - screen.queryByText("パスワードを入力して下さい") + screen.queryByText("パスワードを入力して下さい"), ).toBeInTheDocument(); // verify @@ -172,7 +189,7 @@ describe("SignUp", () => { // then expect( - screen.queryByText("パスワードは8文字以上である必要があります") + screen.queryByText("パスワードは8文字以上である必要があります"), ).toBeInTheDocument(); // verify @@ -204,7 +221,7 @@ describe("SignUp", () => { expect(alert).toBeInTheDocument(); expect(alert).toHaveAttribute( "data-message", - "ユーザー登録に成功しました!" + "ユーザー登録に成功しました!", ); expect(alert).not.toHaveAttribute("data-sub-message"); @@ -215,9 +232,13 @@ describe("SignUp", () => { test("ユーザーが既に存在する場合にエラーが表示されることを確認する", async () => { // setup - const signUp = vi.fn().mockResolvedValue({ + const signUp = vi.fn().mockRejectedValue({ code: 400, - data: "Error 1062: Duplicate entry 'user' for key 'name'", + response: { + data: { + error: "Error 1062: Duplicate entry 'user' for key 'name'", + }, + }, }); (useAuth as Mock).mockReturnValue({ @@ -241,7 +262,7 @@ describe("SignUp", () => { expect(alert).toHaveAttribute("data-message", "エラーが発生しました"); expect(alert).toHaveAttribute( "data-sub-message", - "ユーザー名が重複しています。" + "ユーザー名が重複しています。", ); // verify @@ -250,9 +271,14 @@ describe("SignUp", () => { test("UserGroupID が無効な場合にエラーが表示されることを確認する", async () => { // setup - const signUp = vi.fn().mockResolvedValue({ + const signUp = vi.fn().mockRejectedValue({ code: 400, - data: "Error:Field validation for 'UserGroupID' failed on the 'required' tag", + response: { + data: { + error: + "Error:Field validation for 'UserGroupID' failed on the 'required' tag", + }, + }, }); (useAuth as Mock).mockReturnValue({ @@ -276,7 +302,7 @@ describe("SignUp", () => { expect(alert).toHaveAttribute("data-message", "エラーが発生しました"); expect(alert).toHaveAttribute( "data-sub-message", - "無効なユーザーグループです。" + "無効なユーザーグループです。", ); // verify @@ -285,9 +311,14 @@ describe("SignUp", () => { test("UserGroupID の uuid 形式が無効な場合にエラーが表示されることを確認する", async () => { // setup - const signUp = vi.fn().mockResolvedValue({ + const signUp = vi.fn().mockRejectedValue({ code: 400, - data: "Error:Field validation for 'UserGroupID' failed on the 'uuid' tag", + response: { + data: { + error: + "Error:Field validation for 'UserGroupID' failed on the 'uuid' tag", + }, + }, }); (useAuth as Mock).mockReturnValue({ @@ -311,7 +342,7 @@ describe("SignUp", () => { expect(alert).toHaveAttribute("data-message", "エラーが発生しました"); expect(alert).toHaveAttribute( "data-sub-message", - "無効なユーザーグループです。" + "無効なユーザーグループです。", ); // verify @@ -320,9 +351,14 @@ describe("SignUp", () => { test("InvitationCode が無効の場合", async () => { // setup - const signUp = vi.fn().mockResolvedValue({ + const signUp = vi.fn().mockRejectedValue({ code: 400, - data: "Error:Field validation for 'InvitationCode' failed on the 'required' tag", + response: { + data: { + error: + "Error:Field validation for 'InvitationCode' failed on the 'required' tag", + }, + }, }); (useAuth as Mock).mockReturnValue({ @@ -390,7 +426,7 @@ describe("SignUp", () => { setTimeout(() => { resolve({ code: 200 }); }, 1000); - }) + }), ); (useAuth as Mock).mockReturnValue({ user: null, @@ -408,7 +444,7 @@ describe("SignUp", () => { }); // then - expect(button).toHaveClass("loading"); + expect(button).toHaveClass("btn-disabled"); // verify expect(useAuth).toHaveBeenCalledTimes(2); diff --git a/frontend/octavio/__test__/pages/users.test.tsx b/frontend/octavio/__test__/pages/users.test.tsx index d8c8e35a..c219f4b0 100644 --- a/frontend/octavio/__test__/pages/users.test.tsx +++ b/frontend/octavio/__test__/pages/users.test.tsx @@ -38,7 +38,8 @@ describe("Users", () => { const cells = screen.getAllByRole("cell"); expect(cells[0]).toHaveTextContent(testUser.display_name); expect(cells[1]).toHaveTextContent(testUserGroup.name); - expect(cells[2]).toHaveTextContent(testUser.profile?.self_introduction!); + expect(cells[2]).toHaveTextContent(testUserGroup.organization); + expect(cells[3]).toHaveTextContent(testUser.profile?.self_introduction!); const links = screen.getAllByRole("link"); expect(links[0]).toHaveAttribute( "href", diff --git a/frontend/octavio/app/problems/[problemId]/_components/answer-list-section.tsx b/frontend/octavio/app/problems/[problemId]/_components/answer-list-section.tsx index a22031b8..155db759 100644 --- a/frontend/octavio/app/problems/[problemId]/_components/answer-list-section.tsx +++ b/frontend/octavio/app/problems/[problemId]/_components/answer-list-section.tsx @@ -5,6 +5,7 @@ import Image from "next/image"; import clsx from "clsx"; import { DateTime } from "luxon"; +import { preRoundMode } from "@/components/_const"; import ICTSCCard from "@/components/card"; import MarkdownPreview from "@/components/markdown-preview"; import useAnswers from "@/hooks/answer"; @@ -30,8 +31,12 @@ function AnswerListSection({ problem }: AnswerSectionProps) { 提出日時 問題コード 問題 - 得点 - チェック済み + {preRoundMode && ( + <> + 得点 + チェック済み + + )} @@ -58,10 +63,16 @@ function AnswerListSection({ problem }: AnswerSectionProps) { {createdAt.toFormat("yyyy-MM-dd HH:mm:ss")} {problem?.code} {problem?.title} - {answer?.point ?? "--"} pt - - {answer.point != null ? "○" : "採点中"} - + {!preRoundMode && ( + <> + + {answer?.point ?? "--"} pt + + + {answer.point != null ? "○" : "採点中"} + {" "} + + )} @@ -109,7 +118,7 @@ function AnswerListSection({ problem }: AnswerSectionProps) {
{DateTime.fromISO(selectedAnswer.created_at).toFormat( - "yyyy-MM-dd HH:mm:ss" + "yyyy-MM-dd HH:mm:ss", )}
@@ -120,7 +129,7 @@ function AnswerListSection({ problem }: AnswerSectionProps) { onClick={() => setIsPreviewAnswer(false)} className={clsx( `tab tab-lifted`, - !isPreviewAnswer && "tab-active" + !isPreviewAnswer && "tab-active", )} > Markdown @@ -130,7 +139,7 @@ function AnswerListSection({ problem }: AnswerSectionProps) { onClick={() => setIsPreviewAnswer(true)} className={clsx( `tab tab-lifted`, - isPreviewAnswer && "tab-active" + isPreviewAnswer && "tab-active", )} > Preview diff --git a/frontend/octavio/app/problems/[problemId]/_components/multiple-answer-form.tsx b/frontend/octavio/app/problems/[problemId]/_components/multiple-answer-form.tsx index a0a54f69..ee57f76c 100644 --- a/frontend/octavio/app/problems/[problemId]/_components/multiple-answer-form.tsx +++ b/frontend/octavio/app/problems/[problemId]/_components/multiple-answer-form.tsx @@ -154,7 +154,7 @@ function MultipleAnswerForm({ code }: { code: string }) { type="button" className="btn btn-primary mt-4" onClick={sendButton} - value="提出確認" + value="提出" /> problem.type === "normal", + ); + const multipleProblems = problems.filter( + (problem) => problem.type === "multiple", + ); + return ( <> @@ -64,13 +71,32 @@ function Problems() { おしらせ一覧へ→ -
    - {problems.map((problem) => ( -
  • - -
  • - ))} -
+ {normalProblems.length > 0 && ( + <> +

実技問題

+
    + {normalProblems.map((problem) => ( +
  • + +
  • + ))} +
+ + )} + {multipleProblems.length > 0 && ( + <> +

+ 選択問題 +

+
    + {multipleProblems.map((problem) => ( +
  • + +
  • + ))} +
+ + )} ); diff --git a/frontend/octavio/app/ranking/layout.tsx b/frontend/octavio/app/ranking/layout.tsx index ac968e38..3d5cc766 100644 --- a/frontend/octavio/app/ranking/layout.tsx +++ b/frontend/octavio/app/ranking/layout.tsx @@ -1,7 +1,9 @@ import React from "react"; import { Metadata } from "next"; +import { notFound } from "next/navigation"; +import { preRoundMode } from "@/components/_const"; import ICTSCTitle from "@/components/title"; const title = "ランキング"; @@ -11,6 +13,10 @@ export const metadata: Metadata = { }; export default function Layout({ children }: { children: React.ReactNode }) { + if (preRoundMode) { + return notFound(); + } + return ( <> diff --git a/frontend/octavio/app/scoring/page.tsx b/frontend/octavio/app/scoring/page.tsx index 756e7ea3..7a7b9235 100644 --- a/frontend/octavio/app/scoring/page.tsx +++ b/frontend/octavio/app/scoring/page.tsx @@ -49,7 +49,6 @@ function Index() { ポイント 採点基準ポイント 前提問題 - 著者 @@ -93,7 +92,6 @@ function Index() { {prob.point} {prob.solved_criterion} {prob.previous_problem_id} - {prob.author_id === user?.id ? "自分" : ""} ))} diff --git a/frontend/octavio/app/users/page.tsx b/frontend/octavio/app/users/page.tsx index 9a1b4b36..3634aee0 100644 --- a/frontend/octavio/app/users/page.tsx +++ b/frontend/octavio/app/users/page.tsx @@ -85,6 +85,7 @@ function Page() { 名前 チーム名 + 所属 自己紹介 @@ -131,6 +132,9 @@ function Page() { {userGroup.name} + + {userGroup.organization} + {member.profile?.self_introduction} diff --git a/frontend/octavio/components/_const.ts b/frontend/octavio/components/_const.ts index 266c5e3b..101ec43a 100644 --- a/frontend/octavio/components/_const.ts +++ b/frontend/octavio/components/_const.ts @@ -2,9 +2,10 @@ const replaceN = (str: string) => str.replace(/\\n/g, "\n"); export const apiUrl = process.env.NEXT_PUBLIC_API_URL; export const site = process.env.NEXT_PUBLIC_SITE_NAME ?? ""; -export const rule = replaceN(process.env.RULE ?? ""); +export const rule = replaceN(process.env.NEXT_PUBLIC_RULE ?? ""); export const shortRule = replaceN(process.env.NEXT_PUBLIC_SHORT_RULE ?? ""); export const recreateRule = replaceN( - process.env.NEXT_PUBLIC_RECREATE_RULE ?? "" + process.env.NEXT_PUBLIC_RECREATE_RULE ?? "", ); export const answerLimit = process.env.NEXT_PUBLIC_ANSWER_LIMIT; +export const preRoundMode = process.env.NEXT_PUBLIC_PRE_ROUND_MODE === "true"; diff --git a/frontend/octavio/components/markdown-preview.tsx b/frontend/octavio/components/markdown-preview.tsx index 57c2df65..c51c585f 100644 --- a/frontend/octavio/components/markdown-preview.tsx +++ b/frontend/octavio/components/markdown-preview.tsx @@ -101,7 +101,7 @@ function MarkdownPreview({ className, content }: Props) { count += 1; return ` -
+
${radioButtons.join( "", )}
@@ -126,7 +126,7 @@ function MarkdownPreview({ className, content }: Props) { count += 1; return ` -
+
${checkboxes.join( "", )}
diff --git a/frontend/octavio/components/navbar.tsx b/frontend/octavio/components/navbar.tsx index fb78b176..5b727e26 100644 --- a/frontend/octavio/components/navbar.tsx +++ b/frontend/octavio/components/navbar.tsx @@ -5,6 +5,7 @@ import { useRouter } from "next/navigation"; import { mutate } from "swr"; +import { preRoundMode } from "@/components/_const"; import useAuth from "@/hooks/auth"; function ICTSCNavBar() { @@ -43,15 +44,17 @@ function ICTSCNavBar() { )} -
  • - 順位 -
  • + {!preRoundMode && ( +
  • + 順位 +
  • + )} {user !== null && ( <>
  • 参加者
  • - {user.user_group.is_full_access && !user.is_read_only && ( + {user?.user_group.is_full_access && !user.is_read_only && (
  • 採点
  • @@ -65,14 +68,14 @@ function ICTSCNavBar() { ) : ( // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
  • -
    {user.display_name}
    +
    {user?.display_name}
    • -
      チーム: {user.user_group.name}
      +
      チーム: {user?.user_group.name}
    • プロフィール diff --git a/frontend/octavio/components/problem-card.tsx b/frontend/octavio/components/problem-card.tsx index 4b6e7ac7..082c84a4 100644 --- a/frontend/octavio/components/problem-card.tsx +++ b/frontend/octavio/components/problem-card.tsx @@ -2,6 +2,7 @@ import Link from "next/link"; import clsx from "clsx"; +import { preRoundMode } from "@/components/_const"; import { Problem } from "@/types/Problem"; type Props = { @@ -10,13 +11,21 @@ type Props = { function ProblemCard({ problem }: Props) { let problemText = ""; - if ( - problem.current_point >= (problem.solved_criterion ?? problem.current_point) - ) { - problemText = "font-bold text-gray-500"; - } - if (problem.current_point === problem.point) { - problemText = "font-bold text-amber-500"; + + if (preRoundMode) { + if (problem.is_answered) { + problemText = "font-bold text-amber-500"; + } + } else { + if ( + problem.current_point >= + (problem.solved_criterion ?? problem.current_point) + ) { + problemText = "font-bold text-gray-500"; + } + if (problem.current_point === problem.point) { + problemText = "font-bold text-amber-500"; + } } return ( @@ -32,7 +41,9 @@ function ProblemCard({ problem }: Props) {
  • - {problem.current_point}/{problem.point}pt + {preRoundMode + ? `${problem.point}pt` + : `${problem.current_point}/${problem.point}pt`}
    問題文へ→
    diff --git a/frontend/octavio/hooks/auth.ts b/frontend/octavio/hooks/auth.ts index ca66d92a..59fcd13a 100644 --- a/frontend/octavio/hooks/auth.ts +++ b/frontend/octavio/hooks/auth.ts @@ -3,6 +3,7 @@ import useSWR from "swr"; import useApi from "@/hooks/api"; import { PutProfileRequest } from "@/types/PutProfileRequest"; import { SignInRequest } from "@/types/SignInRequest"; +import { User } from "@/types/User"; import { AuthSelfResult, SignUpRequest } from "@/types/_api"; const useAuth = () => { @@ -10,7 +11,14 @@ const useAuth = () => { const fetcher = (url: string) => client.get(url); - const { data, mutate, isLoading } = useSWR("auth/self", fetcher); + const { data, mutate, isLoading, error } = useSWR("auth/self", fetcher); + + let user: User | null; + if (error) { + user = null; + } else { + user = data?.data?.user ?? null; + } const signUp = async (request: SignUpRequest) => client.post("users", request); @@ -21,7 +29,7 @@ const useAuth = () => { client.put(`users/${userId}`, request); return { - user: data?.data?.user ?? null, + user, signUp, signIn, logout, diff --git a/frontend/octavio/hooks/problems.ts b/frontend/octavio/hooks/problems.ts index 0520d746..6d623213 100644 --- a/frontend/octavio/hooks/problems.ts +++ b/frontend/octavio/hooks/problems.ts @@ -1,6 +1,7 @@ import useSWR from "swr"; import useApi from "@/hooks/api"; +import { Problem } from "@/types/Problem"; import { ProblemResult } from "@/types/_api"; const useProblems = () => { @@ -8,10 +9,17 @@ const useProblems = () => { const fetcher = (url: string) => client.get(url); - const { data, mutate, isLoading } = useSWR("problems", fetcher); + const { data, mutate, isLoading, error } = useSWR("problems", fetcher); + + let problems: Problem[]; + if (error) { + problems = []; + } else { + problems = data?.data?.problems ?? []; + } return { - problems: data?.data?.problems ?? [], + problems, mutate, isLoading, }; diff --git a/frontend/octavio/hooks/ranking.ts b/frontend/octavio/hooks/ranking.ts index c712a889..d2fd533d 100644 --- a/frontend/octavio/hooks/ranking.ts +++ b/frontend/octavio/hooks/ranking.ts @@ -1,5 +1,6 @@ import useSWR from "swr"; +import { preRoundMode } from "@/components/_const"; import useApi from "@/hooks/api"; import { RankingResult } from "@/types/_api"; @@ -9,7 +10,7 @@ const useRanking = () => { /* c8 ignore next */ const fetcher = (url: string) => client.get(url); - const { data, isLoading } = useSWR(`ranking`, fetcher); + const { data, isLoading } = useSWR(preRoundMode ? null : `ranking`, fetcher); return { ranking: data?.data?.ranking ?? null, diff --git a/frontend/octavio/types/Problem.ts b/frontend/octavio/types/Problem.ts index 261a4cf9..7c772207 100644 --- a/frontend/octavio/types/Problem.ts +++ b/frontend/octavio/types/Problem.ts @@ -9,11 +9,11 @@ export interface Problem { point: number; solved_criterion: number | null; previous_problem_id: string | null; - author_id: string; unchecked: number | null; unchecked_near_overdue: number | null; unchecked_overdue: number | null; current_point: number; + is_answered: boolean; is_solved: boolean; } @@ -44,10 +44,10 @@ export const testProblem: Problem = { point: 100, solved_criterion: 150, previous_problem_id: null, - author_id: "1", unchecked: null, unchecked_near_overdue: null, unchecked_overdue: null, current_point: 100, + is_answered: false, is_solved: false, };