From b303985f7500d27bafeb0ea698ed38b7f6b7f0c1 Mon Sep 17 00:00:00 2001 From: "k.hiro1818" Date: Thu, 18 Jun 2026 11:40:44 +0000 Subject: [PATCH 1/5] feat(admin): rename vote_management to tasks/grade route Move the admin grading page from /vote_management to /tasks/grade, update navbar links, and remove the now-unused isAdmin prop from problems page since grading is admin-only via the new dedicated route. Co-Authored-By: Claude Sonnet 4.6 --- .../2026-06-17/admin-tasks-grade/plan.md | 182 ++++++++++++++++++ e2e/redirect_after_login.spec.ts | 2 +- e2e/votes.spec.ts | 28 +-- src/lib/components/TaskGradeList.svelte | 19 +- src/lib/components/TaskList.svelte | 18 +- src/lib/constants/navbar-links.ts | 7 +- .../grade}/+page.server.ts | 34 +--- src/routes/(admin)/tasks/grade/+page.svelte | 138 +++++++++++++ .../(admin)/vote_management/+page.svelte | 103 ---------- src/routes/problems/+page.server.ts | 34 ++-- src/routes/problems/+page.svelte | 3 +- 11 files changed, 361 insertions(+), 207 deletions(-) create mode 100644 docs/dev-notes/2026-06-17/admin-tasks-grade/plan.md rename src/routes/(admin)/{vote_management => tasks/grade}/+page.server.ts (51%) create mode 100644 src/routes/(admin)/tasks/grade/+page.svelte delete mode 100644 src/routes/(admin)/vote_management/+page.svelte diff --git a/docs/dev-notes/2026-06-17/admin-tasks-grade/plan.md b/docs/dev-notes/2026-06-17/admin-tasks-grade/plan.md new file mode 100644 index 000000000..e64de9896 --- /dev/null +++ b/docs/dev-notes/2026-06-17/admin-tasks-grade/plan.md @@ -0,0 +1,182 @@ +# 管理者グレード管理画面の整理と使い勝手の向上(vote_management → /tasks/grade) + +## 概要 + +`/vote_management` の機能は基本的にそのままに、ルート名の整理と検索・表示の改善を加えて `/tasks/grade` に移行する。 + +主な変更点: + +- 「投票統計が存在する問題のみ」から「全問題」を対象とした一覧表示に変更 +- 問題名・問題ID・コンテスト名による検索バーを追加(votes ページと同じ動作) +- 表示順を `compareByContestIdAndTaskId`(新しいコンテストが上)に統一 +- 票数列を削除(検索で絞れるため不要) +- DBグレードの編集 UI は現行のまま(常時 ` + GradeLabel + RelativeEvaluationBadge) + 中央値グレード (GradeLabel、null は空白) + +クライアント側: + $state search = '' + sortedTasks = $derived([...data.tasks].sort(compareByContestIdAndTaskId)) + filteredTasks = $derived(filterTasksBySearch(sortedTasks, search, 20)) + search が空なら「問題名・問題ID・出典を入力してください」メッセージ表示 +``` + +再利用コンポーネント/ユーティリティ: + +- `GradeLabel` (`src/lib/components/GradeLabel.svelte`) +- `RelativeEvaluationBadge` (`src/features/votes/components/RelativeEvaluationBadge.svelte`) +- `compareByContestIdAndTaskId` (`src/lib/utils/task.ts`) +- `filterTasksBySearch` (`src/lib/utils/task_filter.ts`) +- `getAllTasksWithVoteInfo` (`src/features/votes/services/vote_statistics.ts`) + +### Phase 2: 旧ページ削除 + ナビゲーション・E2E 更新 + +**削除:** + +- `src/routes/(admin)/vote_management/+page.server.ts` +- `src/routes/(admin)/vote_management/+page.svelte` +- ディレクトリ `src/routes/(admin)/vote_management/` + +**ナビゲーション更新(`src/lib/constants/navbar-links.ts`):** + +- `VOTE_MANAGEMENT_PAGE = '/vote_management'` → `TASKS_GRADE_PAGE = '/tasks/grade'` にリネーム +- `navbarDashboardLinks` のタイトル `投票管理` → `グレード管理`(または適切な名称) +- `navbarDashboardLinks` から `一覧表`(`PROBLEMS_PAGE` へのリンク)を削除(L28: `{ title: '一覧表', path: PROBLEMS_PAGE }`) + +**E2E テスト更新:** + +1. `e2e/votes.spec.ts` + - `VOTE_MANAGEMENT_URL = '/vote_management'` → `/tasks/grade` に変更 + - `describe` ブロックの説明文を更新 + - ページ見出し (`投票管理`) のアサーションを新見出しに合わせて変更 + - 列ヘッダーのアサーション: `票数` 列削除 → 検索バーの存在確認を追加 + +2. `e2e/redirect_after_login.spec.ts` L65 + - `'/vote_management'` → `'/tasks/grade'` に変更 + +### Phase 3: problems ページの変更 + +**`src/lib/components/TaskList.svelte`** + +- L130-141 の `{#if isAdmin}` ブロック全体を削除(`/(admin)/tasks/[task_id]` への「編集」リンク) + +**`src/lib/components/TaskGradeList.svelte`** + +- `isShowTaskList()` が `isAdmin === true` のとき PENDING を表示する設計になっている(L29-38) +- admin も含め PENDING を常に非表示にする。管理者のグレード編集は `/tasks/grade` で行う +- `isShowTaskList` 関数を削除し、`{#if}` を `taskGrade !== TaskGrade.PENDING` に単純化 +- `isAdmin` prop は `TaskList` 側の `{#if isAdmin}` 削除に伴い不要になる可能性があるため合わせて確認 + +## 検証方法 + +1. `pnpm dev` でサーバ起動 +2. 管理者アカウントでログインし `/tasks/grade` にアクセス +3. 検索ワードなし → 結果非表示を確認 +4. 検索ワードあり → 最大 20 件、`compareByContestIdAndTaskId` 順で表示を確認 +5. DBグレード変更 → ページ遷移なしで反映されることを確認 +6. `/vote_management` → 404 になることを確認 +7. `/problems` → `/tasks/[task_id]` リンクが消えていることを確認 +8. `pnpm test:unit` でサービス層のテストが通ることを確認 + +## 調査済み事項 + +### ナビゲーションの `/vote_management` リンク + +**ファイル:** `src/lib/constants/navbar-links.ts` + +```typescript +// L16: 定数 +export const VOTE_MANAGEMENT_PAGE = `/vote_management`; + +// L29: 管理者ダッシュボードナビに使用 +{ title: `投票管理`, path: VOTE_MANAGEMENT_PAGE }, +``` + +**変更内容:** + +- `VOTE_MANAGEMENT_PAGE` → `TASKS_GRADE_PAGE = '/tasks/grade'` にリネーム +- `navbarDashboardLinks` のタイトルを `グレード管理` 等に変更 + +### problems ページの `/tasks/[task_id]` リンク + +**ファイル:** `src/lib/components/TaskList.svelte:133` + +```svelte +{#if isAdmin} + 編集 +{/if} +``` + +`TaskList.svelte` は `problems` ページの「グレード別」タブ(`TaskGradeList` 経由)で使用されている共通コンポーネント。`{#if isAdmin}` ブロック全体を削除することで対応する。 + +> **注意:** `TagForm.svelte:113` にも同様のリンクがあるが、こちらはタグ管理画面のみで使用されるため今回の対象外。 + +## 今回のスコープ外(将来の検討事項) + +- **`src/routes/(admin)/tasks/[task_id]/` の削除**: 現時点で一部利用があるため今回は対象外。リンクが完全になくなったことを確認後に削除する + +- **管理者グレード管理ページへのキャッシュ付与**: 全問題データをロードするため、サーバ側でのレスポンスキャッシュが望ましいが、今回は対象外 +- **グレード別タブの全面的な UI 刷新**: `problems` ページのグレード別タブ全体の再設計は別タスク +- **サーバ側フィルタリングへの移行**: 現状は全問ロード後にクライアント側で検索・絞り込みを行うが、コンテストやグレードでのサーバ側フィルタリングに切り替えることでパフォーマンスを改善できる可能性がある + +## キャッシュ計画との関係(docs/dev-notes/2026-06-13/sveltekit-caching/plan.md) + +`/tasks/grade` ページは admin 限定・低頻度であるため、キャッシュ計画では明示的にスコープ外(L149)とされており、今回のスコープで取り込める項目はない。 + +- **CDN キャッシュ(Phase 3 型)**: admin 認証必須のため共有キャッシュ不可 +- **server-side キャッシュ(Phase 4)**: `cache.ts` 新設が必要な別タスク。`getAllTasksWithVoteInfo` はターゲット候補に挙がっているが今回は対象外 + +ただし **votes 修正方針(A/B)の前提「vote UI 改修の完了」は今回の作業で満たされる**。本 PR マージ後に `docs/dev-notes/2026-06-13/sveltekit-caching/plan.md` の votes 修正方針 A/B の着手可否を再評価すること。 diff --git a/e2e/redirect_after_login.spec.ts b/e2e/redirect_after_login.spec.ts index 3ab908c75..a355f1048 100644 --- a/e2e/redirect_after_login.spec.ts +++ b/e2e/redirect_after_login.spec.ts @@ -60,9 +60,9 @@ test.describe('redirect with redirectTo parameter', () => { '/account_transfer', '/tasks', '/tasks/1', + '/tasks/grade', '/tags', '/tags/1', - '/vote_management', '/workbooks/order', ]; diff --git a/e2e/votes.spec.ts b/e2e/votes.spec.ts index 8cf06b4c5..f416b2b6b 100644 --- a/e2e/votes.spec.ts +++ b/e2e/votes.spec.ts @@ -4,7 +4,7 @@ import { loginAsAdmin, loginAsUser } from './helpers/auth'; const TIMEOUT = 60 * 1000; const VOTES_LIST_URL = '/votes'; -const VOTE_MANAGEMENT_URL = '/vote_management'; +const TASKS_GRADE_URL = '/tasks/grade'; const KNOWN_TASK_ID = 'abc422_a'; // From prisma/tasks.ts seed data const KNOWN_VOTE_DETAIL_URL = '/votes/abc422_a'; // From prisma/tasks.ts seed data @@ -160,18 +160,18 @@ test.describe('vote detail page (/votes/[slug])', () => { }); // --------------------------------------------------------------------------- -// Vote management page (/vote_management) — admin only +// Grade management page (/tasks/grade) — admin only // --------------------------------------------------------------------------- -test.describe('vote management page (/vote_management)', () => { +test.describe('grade management page (/tasks/grade)', () => { test('unauthenticated user is redirected to /login', async ({ page }) => { - await page.goto(VOTE_MANAGEMENT_URL); + await page.goto(TASKS_GRADE_URL); await expect(page).toHaveURL(/\/login/, { timeout: TIMEOUT }); }); test('non-admin user is redirected to /', async ({ page }) => { await loginAsUser(page); - await page.goto(VOTE_MANAGEMENT_URL); + await page.goto(TASKS_GRADE_URL); await expect(page).toHaveURL('/', { timeout: TIMEOUT }); }); @@ -181,25 +181,25 @@ test.describe('vote management page (/vote_management)', () => { }); test('can access the page', async ({ page }) => { - await page.goto(VOTE_MANAGEMENT_URL); - await expect(page).toHaveURL(VOTE_MANAGEMENT_URL, { timeout: TIMEOUT }); - await expect(page.getByRole('heading', { name: '投票管理' })).toBeVisible({ + await page.goto(TASKS_GRADE_URL); + await expect(page).toHaveURL(TASKS_GRADE_URL, { timeout: TIMEOUT }); + await expect(page.getByRole('heading', { name: 'グレード管理' })).toBeVisible({ timeout: TIMEOUT, }); }); - test('sees the vote management table with expected columns', async ({ page }) => { - await page.goto(VOTE_MANAGEMENT_URL); - await expect(page.getByRole('columnheader', { name: '問題' })).toBeVisible({ + test('shows search input and table with expected columns', async ({ page }) => { + await page.goto(TASKS_GRADE_URL); + await expect(page.getByPlaceholder('問題名・問題ID・出典で検索')).toBeVisible({ timeout: TIMEOUT, }); - await expect(page.getByRole('columnheader', { name: 'DBグレード' })).toBeVisible({ + await expect(page.getByRole('columnheader', { name: '問題名' })).toBeVisible({ timeout: TIMEOUT, }); - await expect(page.getByRole('columnheader', { name: '中央値グレード' })).toBeVisible({ + await expect(page.getByRole('columnheader', { name: 'グレード(admin)' })).toBeVisible({ timeout: TIMEOUT, }); - await expect(page.getByRole('columnheader', { name: '票数' })).toBeVisible({ + await expect(page.getByRole('columnheader', { name: 'グレード(ユーザ投票)' })).toBeVisible({ timeout: TIMEOUT, }); }); diff --git a/src/lib/components/TaskGradeList.svelte b/src/lib/components/TaskGradeList.svelte index f8b0d7a6d..dadfbb9c1 100644 --- a/src/lib/components/TaskGradeList.svelte +++ b/src/lib/components/TaskGradeList.svelte @@ -6,11 +6,10 @@ interface Props { taskResults: TaskResults; - isAdmin: boolean; isLoggedIn: boolean; } - let { taskResults, isAdmin, isLoggedIn }: Props = $props(); + let { taskResults, isLoggedIn }: Props = $props(); // TODO: 共通する内容はutilsに移動させる。 const taskResultsForEachGrade = $derived( @@ -25,28 +24,14 @@ const countTasks = (taskGrade: TaskGrade) => { return taskResultsForEachGrade.get(taskGrade)?.length ?? 0; }; - - const isShowTaskList = (isAdmin: boolean, taskGrade: TaskGrade): boolean => { - if (isAdmin) { - return true; - } - - if (taskGrade !== TaskGrade.PENDING) { - return true; - } - - return false; - }; {#each taskGradeValues as taskGrade (taskGrade)} - - {#if countTasks(taskGrade) && isShowTaskList(isAdmin, taskGrade)} + {#if countTasks(taskGrade) && taskGrade !== TaskGrade.PENDING} {/if} diff --git a/src/lib/components/TaskList.svelte b/src/lib/components/TaskList.svelte index 3cf080f56..f0fd84c4f 100644 --- a/src/lib/components/TaskList.svelte +++ b/src/lib/components/TaskList.svelte @@ -1,6 +1,4 @@ + +
+ + +
+ +
+ + + + 問題名 + 出典 + グレード(admin) + グレード(ユーザ投票) + + + {#if search === ''} + + + 問題名・問題ID・出典を入力してください + + + {:else} + {#each filteredTasks as task (task.task_id)} + + + + + {task.title} + + + + + + {addContestNameToTaskIndex(task.contest_id, task.task_table_index)} + + + + {@render adminGradeCell(task)} + + + + {#if task.estimatedGrade} + + {/if} + + + {/each} + + {#if filteredTasks.length === 0} + + + 該当する問題が見つかりませんでした + + + {/if} + {/if} + +
+
+ +{#snippet adminGradeCell(task: TaskWithVoteInfo)} + +
+
+ + +
+ + {#if task.grade !== TaskGrade.PENDING && task.estimatedGrade} +
+ + +
+ {/if} +
+
+{/snippet} diff --git a/src/routes/(admin)/vote_management/+page.svelte b/src/routes/(admin)/vote_management/+page.svelte deleted file mode 100644 index 0b87519b7..000000000 --- a/src/routes/(admin)/vote_management/+page.svelte +++ /dev/null @@ -1,103 +0,0 @@ - - -
- - -

- 集計済み統計一覧(3票以上で暫定グレードが算出されます) -

- - - - 問題 - コンテスト - DBグレード - 中央値グレード - 票数 - - - {#each data.stats as stat (stat.taskId)} - - - - {stat.title} - - - {stat.contestId} - -
-
- - - - {#if stat.dbGrade !== TaskGrade.PENDING && stat.estimatedGrade} -
- - -
- {/if} -
-
- - - - {stat.voteTotal} -
- {/each} - {#if data.stats.length === 0} - - - 集計データがありません - - - {/if} -
-
-
diff --git a/src/routes/problems/+page.server.ts b/src/routes/problems/+page.server.ts index 901f537dc..da60a81b7 100644 --- a/src/routes/problems/+page.server.ts +++ b/src/routes/problems/+page.server.ts @@ -5,7 +5,6 @@ import { zod4 } from 'sveltekit-superforms/adapters'; import * as task_crud from '$lib/services/task_results'; import { getVoteGradeStatistics } from '$features/votes/services/vote_statistics'; import type { TaskResults } from '$lib/types/task'; -import { Roles } from '$lib/types/user'; import { updateTaskResult } from '$lib/actions/update_task_result'; import { voteAbsoluteGrade } from '@/features/votes/actions/vote_actions'; import { voteAbsoluteGradeSchema } from '$features/votes/zod/schema'; @@ -17,7 +16,6 @@ export async function load({ locals, url, setHeaders }) { const params = await url.searchParams; const tagIds: string | null = params.get('tagIds'); - const isAdmin: boolean = session?.user.role === Roles.ADMIN; // TODO: utilに移動させる const isLoggedIn: boolean = session !== null; @@ -41,26 +39,18 @@ export async function load({ locals, url, setHeaders }) { setHeaders({ 'Cache-Control': 'public, max-age=0, s-maxage=300, stale-while-revalidate=600' }); } - if (tagIds != null) { - return { - taskResults: (await task_crud.getTasksWithTagIds( - tagIds, - session?.user.userId, - )) as TaskResults, - voteResults, - isAdmin: isAdmin, - isLoggedIn: isLoggedIn, - isAtCoderVerified: locals.user?.is_validated === true, - }; - } else { - return { - taskResults: (await task_crud.getTaskResults(session?.user.userId)) as TaskResults, - voteResults, - isAdmin: isAdmin, - isLoggedIn: isLoggedIn, - isAtCoderVerified: locals.user?.is_validated === true, - }; - } + const taskResults = ( + tagIds != null + ? await task_crud.getTasksWithTagIds(tagIds, session?.user.userId) + : await task_crud.getTaskResults(session?.user.userId) + ) as TaskResults; + + return { + taskResults, + voteResults, + isLoggedIn: isLoggedIn, + isAtCoderVerified: locals.user?.is_validated === true, + }; } export const actions = { diff --git a/src/routes/problems/+page.svelte b/src/routes/problems/+page.svelte index ecf9aef2a..504e6f13d 100644 --- a/src/routes/problems/+page.svelte +++ b/src/routes/problems/+page.svelte @@ -22,7 +22,6 @@ let taskResults: TaskResults = $derived(data.taskResults.sort(compareByContestIdAndTaskId)); - const isAdmin = $derived(data.isAdmin); const isLoggedIn = $derived(data.isLoggedIn); const isAtCoderVerified = $derived(data.isAtCoderVerified); const voteResults = $derived(data.voteResults); @@ -65,7 +64,7 @@ {/snippet} {#snippet listByGrade()} - + {/snippet} {#snippet gradeGuidelineTable()} From 426259a0ff3be01ddf203ba6fb601af85184a318 Mon Sep 17 00:00:00 2001 From: "k.hiro1818" Date: Thu, 18 Jun 2026 11:47:08 +0000 Subject: [PATCH 2/5] docs(dev-notes): remove completed plan for tasks/grade migration Co-Authored-By: Claude Sonnet 4.6 --- .../2026-06-17/admin-tasks-grade/plan.md | 182 ------------------ 1 file changed, 182 deletions(-) delete mode 100644 docs/dev-notes/2026-06-17/admin-tasks-grade/plan.md diff --git a/docs/dev-notes/2026-06-17/admin-tasks-grade/plan.md b/docs/dev-notes/2026-06-17/admin-tasks-grade/plan.md deleted file mode 100644 index e64de9896..000000000 --- a/docs/dev-notes/2026-06-17/admin-tasks-grade/plan.md +++ /dev/null @@ -1,182 +0,0 @@ -# 管理者グレード管理画面の整理と使い勝手の向上(vote_management → /tasks/grade) - -## 概要 - -`/vote_management` の機能は基本的にそのままに、ルート名の整理と検索・表示の改善を加えて `/tasks/grade` に移行する。 - -主な変更点: - -- 「投票統計が存在する問題のみ」から「全問題」を対象とした一覧表示に変更 -- 問題名・問題ID・コンテスト名による検索バーを追加(votes ページと同じ動作) -- 表示順を `compareByContestIdAndTaskId`(新しいコンテストが上)に統一 -- 票数列を削除(検索で絞れるため不要) -- DBグレードの編集 UI は現行のまま(常時 ` + GradeLabel + RelativeEvaluationBadge) - 中央値グレード (GradeLabel、null は空白) - -クライアント側: - $state search = '' - sortedTasks = $derived([...data.tasks].sort(compareByContestIdAndTaskId)) - filteredTasks = $derived(filterTasksBySearch(sortedTasks, search, 20)) - search が空なら「問題名・問題ID・出典を入力してください」メッセージ表示 -``` - -再利用コンポーネント/ユーティリティ: - -- `GradeLabel` (`src/lib/components/GradeLabel.svelte`) -- `RelativeEvaluationBadge` (`src/features/votes/components/RelativeEvaluationBadge.svelte`) -- `compareByContestIdAndTaskId` (`src/lib/utils/task.ts`) -- `filterTasksBySearch` (`src/lib/utils/task_filter.ts`) -- `getAllTasksWithVoteInfo` (`src/features/votes/services/vote_statistics.ts`) - -### Phase 2: 旧ページ削除 + ナビゲーション・E2E 更新 - -**削除:** - -- `src/routes/(admin)/vote_management/+page.server.ts` -- `src/routes/(admin)/vote_management/+page.svelte` -- ディレクトリ `src/routes/(admin)/vote_management/` - -**ナビゲーション更新(`src/lib/constants/navbar-links.ts`):** - -- `VOTE_MANAGEMENT_PAGE = '/vote_management'` → `TASKS_GRADE_PAGE = '/tasks/grade'` にリネーム -- `navbarDashboardLinks` のタイトル `投票管理` → `グレード管理`(または適切な名称) -- `navbarDashboardLinks` から `一覧表`(`PROBLEMS_PAGE` へのリンク)を削除(L28: `{ title: '一覧表', path: PROBLEMS_PAGE }`) - -**E2E テスト更新:** - -1. `e2e/votes.spec.ts` - - `VOTE_MANAGEMENT_URL = '/vote_management'` → `/tasks/grade` に変更 - - `describe` ブロックの説明文を更新 - - ページ見出し (`投票管理`) のアサーションを新見出しに合わせて変更 - - 列ヘッダーのアサーション: `票数` 列削除 → 検索バーの存在確認を追加 - -2. `e2e/redirect_after_login.spec.ts` L65 - - `'/vote_management'` → `'/tasks/grade'` に変更 - -### Phase 3: problems ページの変更 - -**`src/lib/components/TaskList.svelte`** - -- L130-141 の `{#if isAdmin}` ブロック全体を削除(`/(admin)/tasks/[task_id]` への「編集」リンク) - -**`src/lib/components/TaskGradeList.svelte`** - -- `isShowTaskList()` が `isAdmin === true` のとき PENDING を表示する設計になっている(L29-38) -- admin も含め PENDING を常に非表示にする。管理者のグレード編集は `/tasks/grade` で行う -- `isShowTaskList` 関数を削除し、`{#if}` を `taskGrade !== TaskGrade.PENDING` に単純化 -- `isAdmin` prop は `TaskList` 側の `{#if isAdmin}` 削除に伴い不要になる可能性があるため合わせて確認 - -## 検証方法 - -1. `pnpm dev` でサーバ起動 -2. 管理者アカウントでログインし `/tasks/grade` にアクセス -3. 検索ワードなし → 結果非表示を確認 -4. 検索ワードあり → 最大 20 件、`compareByContestIdAndTaskId` 順で表示を確認 -5. DBグレード変更 → ページ遷移なしで反映されることを確認 -6. `/vote_management` → 404 になることを確認 -7. `/problems` → `/tasks/[task_id]` リンクが消えていることを確認 -8. `pnpm test:unit` でサービス層のテストが通ることを確認 - -## 調査済み事項 - -### ナビゲーションの `/vote_management` リンク - -**ファイル:** `src/lib/constants/navbar-links.ts` - -```typescript -// L16: 定数 -export const VOTE_MANAGEMENT_PAGE = `/vote_management`; - -// L29: 管理者ダッシュボードナビに使用 -{ title: `投票管理`, path: VOTE_MANAGEMENT_PAGE }, -``` - -**変更内容:** - -- `VOTE_MANAGEMENT_PAGE` → `TASKS_GRADE_PAGE = '/tasks/grade'` にリネーム -- `navbarDashboardLinks` のタイトルを `グレード管理` 等に変更 - -### problems ページの `/tasks/[task_id]` リンク - -**ファイル:** `src/lib/components/TaskList.svelte:133` - -```svelte -{#if isAdmin} - 編集 -{/if} -``` - -`TaskList.svelte` は `problems` ページの「グレード別」タブ(`TaskGradeList` 経由)で使用されている共通コンポーネント。`{#if isAdmin}` ブロック全体を削除することで対応する。 - -> **注意:** `TagForm.svelte:113` にも同様のリンクがあるが、こちらはタグ管理画面のみで使用されるため今回の対象外。 - -## 今回のスコープ外(将来の検討事項) - -- **`src/routes/(admin)/tasks/[task_id]/` の削除**: 現時点で一部利用があるため今回は対象外。リンクが完全になくなったことを確認後に削除する - -- **管理者グレード管理ページへのキャッシュ付与**: 全問題データをロードするため、サーバ側でのレスポンスキャッシュが望ましいが、今回は対象外 -- **グレード別タブの全面的な UI 刷新**: `problems` ページのグレード別タブ全体の再設計は別タスク -- **サーバ側フィルタリングへの移行**: 現状は全問ロード後にクライアント側で検索・絞り込みを行うが、コンテストやグレードでのサーバ側フィルタリングに切り替えることでパフォーマンスを改善できる可能性がある - -## キャッシュ計画との関係(docs/dev-notes/2026-06-13/sveltekit-caching/plan.md) - -`/tasks/grade` ページは admin 限定・低頻度であるため、キャッシュ計画では明示的にスコープ外(L149)とされており、今回のスコープで取り込める項目はない。 - -- **CDN キャッシュ(Phase 3 型)**: admin 認証必須のため共有キャッシュ不可 -- **server-side キャッシュ(Phase 4)**: `cache.ts` 新設が必要な別タスク。`getAllTasksWithVoteInfo` はターゲット候補に挙がっているが今回は対象外 - -ただし **votes 修正方針(A/B)の前提「vote UI 改修の完了」は今回の作業で満たされる**。本 PR マージ後に `docs/dev-notes/2026-06-13/sveltekit-caching/plan.md` の votes 修正方針 A/B の着手可否を再評価すること。 From 4ebc5307aadd27fa59c0aafe18488f52d5221881 Mon Sep 17 00:00:00 2001 From: "k.hiro1818" Date: Thu, 18 Jun 2026 11:56:24 +0000 Subject: [PATCH 3/5] fix(admin): improve accessibility on tasks/grade page Co-Authored-By: Claude Sonnet 4.6 --- src/routes/(admin)/tasks/grade/+page.svelte | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/routes/(admin)/tasks/grade/+page.svelte b/src/routes/(admin)/tasks/grade/+page.svelte index afd1dbd66..c499e330b 100644 --- a/src/routes/(admin)/tasks/grade/+page.svelte +++ b/src/routes/(admin)/tasks/grade/+page.svelte @@ -36,15 +36,15 @@
- +
- 問題名 - 出典 - グレード(admin) - グレード(ユーザ投票) + 問題名 + 出典 + グレード(admin) + グレード(ユーザ投票) {#if search === ''} @@ -86,15 +86,13 @@ {/if} - {/each} - - {#if filteredTasks.length === 0} + {:else} 該当する問題が見つかりませんでした - {/if} + {/each} {/if}
@@ -107,6 +105,7 @@