Skip to content

✨ サークル内イベントカレンダーと運営用のイベント登録画面を追加 - #300

Open
Tivo0921 wants to merge 3 commits into
mainfrom
feature/event-calendar
Open

Tivo0921 wants to merge 3 commits into
mainfrom
feature/event-calendar

Conversation

@Tivo0921

Copy link
Copy Markdown
Contributor

closes #174

概要

一般メンバーが見られるサークル内イベントのカレンダーページと、運営がイベントを登録するダッシュボードを追加します。

  • /internal/events — 月間カレンダー。?m=YYYY-MM で月移動、日付クリックでその日のイベントに絞り込み。右側に「これからの予定」
  • /internal/admin/events — 運営がイベントを追加・編集・削除するフォーム(管理者ページにもリンクを追加)

mini LT の開催回をここに取り込む件は #299 に切り出しました。データ側には source: "manual" | "mini-lt"sourceRef (週 ID) を先に用意してあります。

変更点

データ

  • types/event.ts — 既存の Event (date 文字列だけ) を CircleEvent に定義し直し。startAt / endAt (Timestamp)、allDaylocationsourcesourceRefcreatedBy
    • events コレクションはこれまで読むだけで書き込み経路が無かったので、既存データの移行は不要
  • lib/events.ts — 月間取得・これからの予定・管理用一覧・CRUD・zod 入力検証
    • 月間取得は startAt を前月頭から絞ってメモリ側で重なり判定 (前月から続く長いイベントを拾いつつ、複合インデックスを増やさない)
  • lib/events-format.ts — 日時は日本時間固定。datetime-local ⇄ ISO、月グリッド、"9月20日(土) 19:00〜21:00" 形式の表記など
    • 22:00〜翌 0:00 のような時刻付きイベントは翌日に載せない。終日イベントは最終日を含む
    • 今年以外の日付には年を付ける

API

UI

  • components/events/event-calendar.tsx — 月グリッド (日曜始まり、sm 未満は点、以上はタイトル入りチップ、4 件以上は "+N件")
  • components/events/event-item.tsx — 一覧カード (カレンダー・これからの予定・管理画面で共通)
  • components/events/event-manager.tsx — 運営用フォーム。「終日」を切り替えると日付入力に変わり、入力済みの日付は保持
  • components/event-list.tsx は削除

テスト

  • lib/events.test.ts — 日時変換・月グリッド・日付範囲・入力検証・CRUD・月間/これから/管理用の取得 (12 件)

動作確認

ローカル (エミュレータ) でブラウザ確認済み:

  • カレンダーの月移動、今日のハイライト、複数日イベントのまたぎ表示、同日 5 件の "+2件" 畳み、日付クリックでの絞り込み
  • 管理画面での追加 → 編集 (終日切り替え) → 更新

注意

  • lib/events.test.tsbeforeEach は既存の blogs.test.ts と同じく全コレクションを消します。just dev で立てた 8080 のエミュレータに対して vitest を直接回すと開発データが消えます

🤖 Generated with Claude Code

https://claude.ai/code/session_01Gtw14tqkjpEogwKBR6Bcd8

- /internal/events を月間カレンダーに作り直し、これからの予定も並べる
- /internal/admin/events で運営がイベントを追加・編集・削除できるようにする
- events コレクションを CircleEvent (startAt/endAt/allDay/location/source) に定義し直す
  source と sourceRef は mini LT 取り込み (#299) 用に先に確保
- 日時は日本時間固定で扱う (lib/events-format.ts)
- 運営 API 共通の認可 authorizeAdminApi を追加 (退会済みセッションも弾く)

closes #174

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gtw14tqkjpEogwKBR6Bcd8
@Tivo0921
Tivo0921 requested a review from Shion1305 September 12, 2026 15:25
@github-actions

Copy link
Copy Markdown

Deployed on https://dev-pr-300-birbz3fm6q-an.a.run.app

@Shion1305 Shion1305 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ひとまず、気になったポイントを指摘しました。
(All reviews are authored manually by @Shion1305 )

Comment thread app/api/events/[id]/route.ts Outdated
Comment on lines +9 to +10
const authorized = await authorizeAdminApi();
if ("response" in authorized) return authorized.response;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

以下で行ったReviewと同様に実装を修正してください。
#284 (review)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adminAPIとしての認可というよりかはNewsを編集する権限があるかとしての認可という実装にしたい
(現状Adminかどうかが編集権限であるという実装であるとしても、責務上の切り分けとして)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#284 と同じ形に揃えました(a60bf5c979c891)。

  • isEventEditor(session): booleanlib/event-auth.ts)+ EVENT_API_RESPONSESlib/events-response.ts
  • 「管理者か」ではなく「イベントを編集できるか」の判定として切り出したので、権限の持ち方を変えるときはここだけ差し替えれば済むようにしています
  • lib/event-auth.test.ts を追加(退会済みで admin フラグが残っているケース含む)

Comment thread app/api/events/route.ts
}

const month =
new URL(request.url).searchParams.get("month") ?? todayJstKey().slice(0, 7);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todayJstKey 周りの実装、 #236 と被ってしまうので、一旦 #236 がMergeされるまでHoldして欲しいです

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

了解です。#236 がマージされたら、todayJstKey 周りを lib/date.tsgetJstToday / getDateParts / ISODateString に載せ替える形でリベースします。それまでこの PR は Hold で。

export function EventCalendar({ month, events, today }: EventCalendarProps) {
const [selected, setSelected] = useState<string | null>(null);
const days = useMemo(() => monthGridDays(month), [month]);
const thisMonth = today.slice(0, 7);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#236 (comment)
slice 使う実装甘いよねって話になって、 #236 でここら辺のモジュール実装導入されるのでそれまでお待ち 🙏

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

了解です。日付キーの slice はここ以外にも lib/events-format.ts に何か所かあるので、#236getDateParts / ISODateString が入ったらまとめて置き換えます(それまで Hold)。

Comment thread types/event.ts Outdated
* 日時はすべて日本時間で入力・表示する。保存は UTC の Timestamp なので、
* 表示側は必ず Asia/Tokyo で整形する (lib/events-format.ts)。
*/
export interface CircleEvent {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

サークルは和製英語で サークル ≠ circle です
Eventを変えたいなら LumosEvent とか CludEvent とかかな

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LumosEvent に改名しました(979c891)。LumosEventInput も同様です。

Comment thread components/events/event-manager.tsx Outdated
Comment on lines +108 to +117
const startAt = jstLocalToIso(form.start);
if (!form.title.trim() || !startAt) {
setError("タイトルと開始日時を入力してください");
return;
}
const endAt = form.end ? jstLocalToIso(form.end) : null;
if (form.end && !endAt) {
setError("終了日時の形式が正しくありません");
return;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

バリデーションロジックについてはzodを使った実装にして欲しいです

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lib/events-form.ts に zod スキーマ(eventFormSchema)としてまとめました(979c891)。

  • 必須・日時の形式・「終了は開始より後」を refine / superRefine で検証し、handleSubmitsafeParse して最初の issue を出すだけにしています
  • 文言はサーバー側の parseEventInput と同じ EVENT_ERROR_RESPONSES を使うので、どちらで弾かれても同じ表示になります
  • lib/events-form.test.ts を追加(5 件)

@Shion1305 Shion1305 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ひとまず、気になったポイントを指摘しました。
(All reviews are authored manually by @Shion1305 )

#284 のレビュー指摘に合わせて、authorizeAdminApi() を isAdminSession(session) に置き換え、
401 / 403 / success などの定型レスポンスを EVENT_API_RESPONSES にまとめる

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gtw14tqkjpEogwKBR6Bcd8
@github-actions

Copy link
Copy Markdown

Deployed on https://dev-pr-300-birbz3fm6q-an.a.run.app

- CircleEvent → LumosEvent (サークル ≠ circle)
- 認可を「管理者か」ではなく「イベントを編集できるか」として lib/event-auth.ts の isEventEditor に
- 管理フォームの検証を lib/events-form.ts の zod スキーマにまとめ、文言をサーバー側と揃える

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gtw14tqkjpEogwKBR6Bcd8
@Tivo0921
Tivo0921 requested a review from Shion1305 September 13, 2026 12:51
@github-actions

Copy link
Copy Markdown

Deployed on https://dev-pr-300-birbz3fm6q-an.a.run.app

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[TODO] サークル内イベントカレンダーの実装

2 participants