From ccf8cece77f1b0a8499de8b144db1505b550b96f Mon Sep 17 00:00:00 2001 From: nakaterm <104970808+nakaterm@users.noreply.github.com> Date: Sat, 22 Nov 2025 17:57:48 +0900 Subject: [PATCH 01/14] =?UTF-8?q?=E5=9B=BA=E5=AE=9A=E3=83=90=E3=83=BC?= =?UTF-8?q?=E3=81=AE=E6=8C=99=E5=8B=95=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/Project.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/src/pages/Project.tsx b/client/src/pages/Project.tsx index c6ac92e..813668d 100644 --- a/client/src/pages/Project.tsx +++ b/client/src/pages/Project.tsx @@ -275,7 +275,7 @@ export default function ProjectPage() {

{project ? `${project.name} の編集` : "イベントの作成"}

-
+
)} -
+
ホームに戻る From 530020b7b70f8dd883e2b46732bc5b43e5f77d0b Mon Sep 17 00:00:00 2001 From: nakaterm <104970808+nakaterm@users.noreply.github.com> Date: Sat, 22 Nov 2025 18:48:50 +0900 Subject: [PATCH 02/14] =?UTF-8?q?=E5=8F=82=E5=8A=A0=E5=BD=A2=E6=85=8B?= =?UTF-8?q?=E9=81=B8=E6=8A=9E=20UI=20=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/eventId/Submission.tsx | 52 +++++++++++++++++++------ 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/client/src/pages/eventId/Submission.tsx b/client/src/pages/eventId/Submission.tsx index d09e118..a422e60 100644 --- a/client/src/pages/eventId/Submission.tsx +++ b/client/src/pages/eventId/Submission.tsx @@ -19,6 +19,17 @@ const client = hc(API_ENDPOINT); export type EditingSlot = Pick; +const hexToRgb = (hex: string): { r: number; g: number; b: number } | null => { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result + ? { + r: Number.parseInt(result[1], 16), + g: Number.parseInt(result[2], 16), + b: Number.parseInt(result[3], 16), + } + : null; +}; + export default function SubmissionPage() { const { eventId: projectId } = useParams<{ eventId: string }>(); const [project, setProject] = useState(null); @@ -227,18 +238,35 @@ export default function SubmissionPage() { {editMode && project.participationOptions.length > 1 && selectedParticipationOptionId !== null && (
- 参加形態を選択 - + 参加形態を選択 +
+ {project.participationOptions.map((opt) => { + const rgb = hexToRgb(opt.color); + const lightBg = rgb + ? `rgba(${rgb.r * 0.2 + 255 * 0.8}, ${rgb.g * 0.2 + 255 * 0.8}, ${rgb.b * 0.2 + 255 * 0.8}, 1)` + : undefined; + + return ( + + ); + })} +
)} Date: Sat, 22 Nov 2025 18:49:31 +0900 Subject: [PATCH 03/14] =?UTF-8?q?=E3=82=AA=E3=83=97=E3=82=B7=E3=83=A7?= =?UTF-8?q?=E3=83=B3=E3=81=AE=E9=81=B8=E6=8A=9E=E3=82=92=20RHF=20=E3=81=A7?= =?UTF-8?q?=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/Project.tsx | 139 ++++++++++++----------------------- 1 file changed, 47 insertions(+), 92 deletions(-) diff --git a/client/src/pages/Project.tsx b/client/src/pages/Project.tsx index 813668d..c53224e 100644 --- a/client/src/pages/Project.tsx +++ b/client/src/pages/Project.tsx @@ -1,7 +1,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import dayjs from "dayjs"; import { hc } from "hono/client"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { useFieldArray, useForm } from "react-hook-form"; import { HiClipboardCheck, @@ -80,11 +80,6 @@ export default function ProjectPage() { const [copied, setCopied] = useState(false); const [isInfoExpanded, setIsInfoExpanded] = useState(!eventId); // 新規作成時は展開、編集時は折りたたみ - const [participationOptions, setParticipationOptions] = useState<{ id: string; label: string; color: string }[]>([]); - const [initialParticipationOptions, setInitialParticipationOptions] = useState< - { id: string; label: string; color: string }[] - >([]); - const { register, handleSubmit, @@ -108,11 +103,19 @@ export default function ProjectPage() { trigger("name"); }; - const { fields, replace } = useFieldArray({ + const { fields: allowedRangeFields, replace } = useFieldArray({ control, name: "allowedRanges", }); + const { + fields: participationFields, + append: appendParticipation, + remove: removeParticipation, + } = useFieldArray({ + control, + name: "participationOptions", + }); useEffect(() => { if (!eventId) return; if (!project) return; @@ -127,15 +130,12 @@ export default function ProjectPage() { endTime: dayjs(project.allowedRanges[0].endTime).format("HH:mm"), }, ], + participationOptions: project.participationOptions.map((opt) => ({ + id: opt.id, + label: opt.label, + color: opt.color, + })), }); - // 参加形態の初期化 - const initialOptions = project.participationOptions.map((opt) => ({ - id: opt.id, - label: opt.label, - color: opt.color, - })); - setParticipationOptions(initialOptions); - setInitialParticipationOptions(initialOptions); }, [eventId, project, reset]); // 送信処理 @@ -158,13 +158,11 @@ export default function ProjectPage() { startDate: startDateTime, endDate: endDateTime, allowedRanges: rangeWithDateTime ?? [], - participationOptions: participationOptions - .filter((opt) => opt.label.trim()) // 空のラベルは除外 - .map((opt) => ({ - id: opt.id, - label: opt.label.trim(), - color: opt.color, - })), + participationOptions: (data.participationOptions ?? []).map((opt) => ({ + id: opt.id, + label: opt.label.trim(), + color: opt.color, + })), } satisfies z.infer; if (!project) { @@ -228,33 +226,6 @@ export default function ProjectPage() { } }, [loading, project, isHost, eventId, navigate]); - // 参加形態の変更を検知 TODO: 実装の改善、rhf での管理 - const hasParticipationOptionsChanged = useMemo(() => { - if (!eventId) return false; // 新規作成の場合は参加形態の変更を検知しない - - // 数が違う場合 - if (participationOptions.length !== initialParticipationOptions.length) return true; - - // 各要素を比較 - for (let i = 0; i < participationOptions.length; i++) { - const current = participationOptions[i]; - const initial = initialParticipationOptions.find((opt) => opt.id === current.id); - - // IDが見つからない(新規追加された) - if (!initial) return true; - - // label または color が変更された - if (current.label !== initial.label || current.color !== initial.color) return true; - } - - // 削除された要素がないかチェック - for (const initial of initialParticipationOptions) { - if (!participationOptions.find((opt) => opt.id === initial.id)) return true; - } - - return false; - }, [participationOptions, initialParticipationOptions, eventId]); - return ( <>
@@ -367,12 +338,12 @@ export default function ProjectPage() {
{ replace([ { - startTime: fields[0].startTime, - endTime: `${e.target.value}:${fields[0].endTime.split(":")[1]}`, + startTime: allowedRangeFields[0].startTime, + endTime: `${e.target.value}:${allowedRangeFields[0].endTime.split(":")[1]}`, }, ]); }} @@ -436,12 +407,12 @@ export default function ProjectPage() { { - const newOptions = [...participationOptions]; - newOptions[index].color = e.target.value; - setParticipationOptions(newOptions); - }} + {...register(`participationOptions.${index}.color`)} + defaultValue={field.color} className="h-10 w-10 cursor-pointer rounded border-0" /> { - const newOptions = [...participationOptions]; - newOptions[index].label = e.target.value; - setParticipationOptions(newOptions); - }} + {...register(`participationOptions.${index}.label`)} + defaultValue={field.label} placeholder="参加形態名(例:対面、オンライン)" className="input input-bordered flex-1 text-base" />
From 79e2c6d5090a6c919bd1ba2635eb48bc703fcb4d Mon Sep 17 00:00:00 2001 From: nakaterm <104970808+nakaterm@users.noreply.github.com> Date: Sat, 22 Nov 2025 18:49:46 +0900 Subject: [PATCH 04/14] =?UTF-8?q?=E3=82=AB=E3=83=A9=E3=83=BC=E3=83=91?= =?UTF-8?q?=E3=83=AC=E3=83=83=E3=83=88=E3=82=92=E8=AA=BF=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/colors.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/common/colors.ts b/common/colors.ts index eff2ec5..39b3d60 100644 --- a/common/colors.ts +++ b/common/colors.ts @@ -1,12 +1,9 @@ export const PREDEFINED_COLORS = [ - "#4ECDC4", // 青緑 - "#45B7D1", // 水色 - "#96CEB4", // 緑 - "#FFEAA7", // 黄 - "#DDA0DD", // 紫 - "#87CEEB", // スカイブルー - "#9B59B6", // 濃い紫 - "#3498DB", // 青 + "#FF9500", // オレンジ + "#10B981", // 緑 + "#3B82F6", // 青 + "#A855F7", // 紫 + "#EC4899", // ピンク ]; export const DEFAULT_PARTICIPATION_OPTION = { From 364629ac909c2c4b5f6defb2c853c22e28666d40 Mon Sep 17 00:00:00 2001 From: nakaterm <104970808+nakaterm@users.noreply.github.com> Date: Sat, 22 Nov 2025 18:58:42 +0900 Subject: [PATCH 05/14] =?UTF-8?q?=E3=83=A9=E3=83=99=E3=83=AB=E7=A9=BA?= =?UTF-8?q?=E6=AC=84=E6=99=82=E3=81=AE=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC?= =?UTF-8?q?=E3=82=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/Project.tsx | 61 +++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/client/src/pages/Project.tsx b/client/src/pages/Project.tsx index c53224e..6606011 100644 --- a/client/src/pages/Project.tsx +++ b/client/src/pages/Project.tsx @@ -96,6 +96,7 @@ export default function ProjectPage() { startDate: eventId ? "" : dayjs().format("YYYY-MM-DD"), endDate: eventId ? "" : dayjs().add(6, "day").format("YYYY-MM-DD"), allowedRanges: [{ startTime: "00:00", endTime: "23:45" }], + participationOptions: [], }, }); @@ -444,28 +445,44 @@ export default function ProjectPage() {

{participationFields.map((field, index) => ( -
- - - - +
+
+ + + { + // 値を変更していない場合でも空ならエラー表示させるため手動で検証 + trigger(`participationOptions.${index}.label` as const); + }} + /> + +
+ {errors.participationOptions?.[index]?.label && ( +

+ {errors.participationOptions[index]?.label?.message as string} +

+ )} + {errors.participationOptions?.[index]?.color && ( +

+ {errors.participationOptions[index]?.color?.message as string} +

+ )}
))} From 33a5e8c18d11237f8f6921856366af9c3aec74f5 Mon Sep 17 00:00:00 2001 From: nakaterm <104970808+nakaterm@users.noreply.github.com> Date: Sat, 22 Nov 2025 19:08:38 +0900 Subject: [PATCH 06/14] =?UTF-8?q?=E3=83=A9=E3=83=99=E3=83=AB=E5=89=8A?= =?UTF-8?q?=E9=99=A4=E5=A4=B1=E6=95=97=E6=99=82=E3=81=AE=E3=82=A8=E3=83=A9?= =?UTF-8?q?=E3=83=BC=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/Project.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/client/src/pages/Project.tsx b/client/src/pages/Project.tsx index 6606011..5148232 100644 --- a/client/src/pages/Project.tsx +++ b/client/src/pages/Project.tsx @@ -206,11 +206,22 @@ export default function ProjectPage() { }); setTimeout(() => setToast(null), 3000); } else { + let errorMessage = "更新に失敗しました。"; + try { + const data = await res.json(); + if (data && typeof data.message === "string" && data.message.trim()) { + errorMessage = data.message.trim(); + } else if (res.status === 403) { + errorMessage = "権限がありません。"; + } + } catch (_) { + if (res.status === 403) errorMessage = "権限がありません。"; + } setToast({ - message: res.status === 403 ? "権限がありません。" : "更新に失敗しました。", + message: errorMessage, variant: "error", }); - setTimeout(() => setToast(null), 3000); + setTimeout(() => setToast(null), 4000); } } }; From f33c4cb0281361b9a95a378fb2e5eeb2b7a903a9 Mon Sep 17 00:00:00 2001 From: nakaterm <104970808+nakaterm@users.noreply.github.com> Date: Sun, 23 Nov 2025 02:16:33 +0900 Subject: [PATCH 07/14] =?UTF-8?q?=E7=AE=A1=E7=90=86=E3=83=9C=E3=82=BF?= =?UTF-8?q?=E3=83=B3=E5=89=8A=E9=99=A4=E3=80=81=E7=B7=A8=E9=9B=86=E7=94=BB?= =?UTF-8?q?=E9=9D=A2=E3=81=8B=E3=82=89=E3=83=97=E3=83=AD=E3=82=B8=E3=82=A7?= =?UTF-8?q?=E3=82=AF=E3=83=88=E3=83=9A=E3=83=BC=E3=82=B8=E3=81=AB=E6=88=BB?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/Home.tsx | 45 +++++++++++++----------------------- client/src/pages/Project.tsx | 12 +++++++--- 2 files changed, 25 insertions(+), 32 deletions(-) diff --git a/client/src/pages/Home.tsx b/client/src/pages/Home.tsx index 4dbaac0..c9628a6 100644 --- a/client/src/pages/Home.tsx +++ b/client/src/pages/Home.tsx @@ -1,6 +1,6 @@ import { hc } from "hono/client"; import { useEffect, useState } from "react"; -import { HiOutlineCalendar, HiOutlineCog, HiOutlinePlus, HiOutlineUser, HiOutlineUsers } from "react-icons/hi"; +import { HiOutlineCalendar, HiOutlinePlus, HiOutlineUser, HiOutlineUsers } from "react-icons/hi"; import { NavLink } from "react-router"; import type { AppType } from "../../../server/src/main"; import Header from "../components/Header"; @@ -112,34 +112,21 @@ function ProjectCard({ project }: { project: BriefProject }) { aria-label={`「${project.name}」の詳細を見る`} >
-
-
-

{project.name}

- - {project.isHost ? ( - <> - - 主催者 - - ) : ( - <> - - 参加者 - - )} - -
- - {project.isHost && ( - e.stopPropagation()} - className="btn btn-ghost btn-sm px-3 py-1 text-gray-500 transition-all hover:bg-gray-100 hover:text-gray-700" - > - - 管理 - - )} +
+

{project.name}

+ + {project.isHost ? ( + <> + + 主催者 + + ) : ( + <> + + 参加者 + + )} +
diff --git a/client/src/pages/Project.tsx b/client/src/pages/Project.tsx index 5148232..ac02ba0 100644 --- a/client/src/pages/Project.tsx +++ b/client/src/pages/Project.tsx @@ -557,9 +557,15 @@ export default function ProjectPage() { )}
- - ホームに戻る - + {eventId ? ( + + 日程調整に戻る + + ) : ( + + ホームに戻る + + )} From ba695845d7fcfdceb1e1d870c0d662f40b72e0cc Mon Sep 17 00:00:00 2001 From: nakaterm <104970808+nakaterm@users.noreply.github.com> Date: Sun, 23 Nov 2025 03:05:12 +0900 Subject: [PATCH 08/14] =?UTF-8?q?=E3=83=87=E3=83=95=E3=82=A9=E3=83=AB?= =?UTF-8?q?=E3=83=88=E3=81=AE=20allowedRange=20=E3=82=92=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/Project.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/pages/Project.tsx b/client/src/pages/Project.tsx index ac02ba0..b8ce265 100644 --- a/client/src/pages/Project.tsx +++ b/client/src/pages/Project.tsx @@ -95,7 +95,7 @@ export default function ProjectPage() { description: "", startDate: eventId ? "" : dayjs().format("YYYY-MM-DD"), endDate: eventId ? "" : dayjs().add(6, "day").format("YYYY-MM-DD"), - allowedRanges: [{ startTime: "00:00", endTime: "23:45" }], + allowedRanges: [{ startTime: "08:00", endTime: "23:00" }], participationOptions: [], }, }); From eb84f306ede89e3585ca63cd7e5088d1823d9c6d Mon Sep 17 00:00:00 2001 From: nakaterm <104970808+nakaterm@users.noreply.github.com> Date: Sun, 23 Nov 2025 04:21:02 +0900 Subject: [PATCH 09/14] =?UTF-8?q?=E5=8F=82=E5=8A=A0=E5=BD=A2=E6=85=8B?= =?UTF-8?q?=E5=89=8A=E9=99=A4=E3=83=9C=E3=82=BF=E3=83=B3=E3=81=AE=E9=9D=9E?= =?UTF-8?q?=E6=B4=BB=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/Project.tsx | 92 ++++++++++++++++++++---------------- 1 file changed, 52 insertions(+), 40 deletions(-) diff --git a/client/src/pages/Project.tsx b/client/src/pages/Project.tsx index b8ce265..0fe14e1 100644 --- a/client/src/pages/Project.tsx +++ b/client/src/pages/Project.tsx @@ -115,6 +115,7 @@ export default function ProjectPage() { remove: removeParticipation, } = useFieldArray({ control, + keyName: "fieldId", // RHF 内部のキーの名称。デフォルトの id だと participationOptions の id と衝突するため変更 name: "participationOptions", }); useEffect(() => { @@ -455,47 +456,58 @@ export default function ProjectPage() { 参加形態を設定すると、参加者は「対面」「オンライン」などの形態を選んで日程を登録できます。

- {participationFields.map((field, index) => ( -
-
- - - { - // 値を変更していない場合でも空ならエラー表示させるため手動で検証 - trigger(`participationOptions.${index}.label` as const); - }} - /> - + {participationFields.map((field, index) => { + const hasSlots = project?.guests.some((guest) => + guest.slots.some((slot) => slot.participationOptionId === field.id), + ); + return ( +
+
+ + + { + // 値を変更していない場合でも空ならエラー表示させるため手動で検証 + trigger(`participationOptions.${index}.label` as const); + }} + /> +
+ +
+
+ {errors.participationOptions?.[index]?.label && ( +

+ {errors.participationOptions[index]?.label?.message as string} +

+ )} + {errors.participationOptions?.[index]?.color && ( +

+ {errors.participationOptions[index]?.color?.message as string} +

+ )}
- {errors.participationOptions?.[index]?.label && ( -

- {errors.participationOptions[index]?.label?.message as string} -

- )} - {errors.participationOptions?.[index]?.color && ( -

- {errors.participationOptions[index]?.color?.message as string} -

- )} -
- ))} + ); + })}
- {!project || (project && project.guests.length === 0) ? ( - <> -
- setIsInfoExpanded(e.target.checked)} - /> -
- - 開始日・終了日/時間帯について -
-
-

- イツヒマでは、主催者側で候補日程を設定せずに日程調整します。 -
- ここでは、参加者の日程を知りたい日付の範囲と時間帯の範囲を設定してください。 -
- 詳しくは、 - - 使い方ページ - - をご覧ください。 -

-
-
-
-
- - - {errors.startDate &&

{errors.startDate.message}

} -
-
- - + setIsInfoExpanded(e.target.checked)} /> +
+ + 開始日・終了日/時間帯について +
+
+

+ イツヒマでは、主催者側で候補日程を設定せずに日程調整します。 +
+ ここでは、参加者の日程を知りたい日付の範囲と時間帯の範囲を設定してください。 +
+ 詳しくは、 + + 使い方ページ + + をご覧ください。 +

+
+
+
+
0 ? "tooltip tooltip-top flex-1" : "flex-1"} + data-tip={ + project && project.guests.length > 0 + ? "すでに日程を登録したユーザーがいるため、開始日の編集はできません" + : "" + } + > + + 0 ? "cursor-not-allowed opacity-60" : ""}`} + onFocus={handleFieldFocus} + disabled={!!(project && project.guests.length > 0)} + /> + {errors.startDate &&

{errors.startDate.message}

} +
+
0 ? "tooltip tooltip-top flex-1" : "flex-1"} + data-tip={ + project && project.guests.length > 0 + ? "すでに日程を登録したユーザーがいるため、終了日の編集はできません" + : "" + } + > + + 0 ? "cursor-not-allowed opacity-60" : ""}`} + onFocus={handleFieldFocus} + disabled={!!(project && project.guests.length > 0)} + /> + {errors.endDate &&

{errors.endDate.message}

} +
+
+
+ 時間帯 +
0 ? "tooltip tooltip-top w-full" : "w-full"} + data-tip={ + project && project.guests.length > 0 + ? "すでに日程を登録したユーザーがいるため、時間帯の編集はできません" + : "" + } + > +
+
+ { - replace([ - { - startTime: `${e.target.value}:${allowedRangeFields[0].startTime.split(":")[1]}`, - endTime: allowedRangeFields[0].endTime, - }, - ]); - }} - onFocus={handleFieldFocus} - > - + {Array.from({ length: 24 }, (_, i) => i.toString().padStart(2, "0")).map((h) => ( + - {Array.from({ length: 24 }, (_, i) => i.toString().padStart(2, "0")).map((h) => ( - - ))} - - + -
- -
- +
+ +
+ - + -
+ ))} +
- {errors.allowedRanges && typeof errors.allowedRanges?.message === "string" && ( -

{errors.allowedRanges.message}

- )} -
- - ) : ( -

すでにデータを登録したユーザーがいるため、日時の編集はできません。

- )} +
+
+ {errors.allowedRanges && typeof errors.allowedRanges?.message === "string" && ( +

{errors.allowedRanges.message}

+ )} +
参加形態(任意)

From 53ce1814aa8d7866a3604892ea480452fe1ae157 Mon Sep 17 00:00:00 2001 From: nakaterm <104970808+nakaterm@users.noreply.github.com> Date: Sun, 23 Nov 2025 04:41:08 +0900 Subject: [PATCH 11/14] =?UTF-8?q?=E5=89=8A=E9=99=A4=E3=83=9C=E3=82=BF?= =?UTF-8?q?=E3=83=B3=E3=81=AE=E3=82=B9=E3=82=BF=E3=82=A4=E3=83=AB=E3=82=92?= =?UTF-8?q?=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/Project.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/src/pages/Project.tsx b/client/src/pages/Project.tsx index 710c751..392b63f 100644 --- a/client/src/pages/Project.tsx +++ b/client/src/pages/Project.tsx @@ -9,6 +9,7 @@ import { HiInformationCircle, HiOutlineCheckCircle, HiOutlineExclamationCircle, + HiOutlineTrash, } from "react-icons/hi"; import { NavLink, useNavigate, useParams } from "react-router"; import type { z } from "zod"; @@ -510,7 +511,7 @@ export default function ProjectPage() { className={`btn btn-ghost btn-sm text-error ${hasSlots ? "cursor-not-allowed opacity-40" : ""}`} disabled={hasSlots} > - 削除 +

@@ -550,7 +551,7 @@ export default function ProjectPage() {
From 42eb87cdb7c4e6fcddff9e7a9e4361f9cce3ab29 Mon Sep 17 00:00:00 2001 From: nakaterm <104970808+nakaterm@users.noreply.github.com> Date: Sun, 23 Nov 2025 05:09:47 +0900 Subject: [PATCH 12/14] =?UTF-8?q?=E5=8F=82=E5=8A=A0=E5=BD=A2=E6=85=8B?= =?UTF-8?q?=E3=82=92=E8=A8=AD=E5=AE=9A=E3=81=97=E3=81=AA=E3=81=84=E5=A0=B4?= =?UTF-8?q?=E5=90=88=E3=81=AE=E3=83=95=E3=83=AD=E3=83=BC=E3=82=92=E6=95=B4?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/Project.tsx | 168 +++++++++++++++++++--------------- common/validators.ts | 2 +- server/src/routes/projects.ts | 24 +---- 3 files changed, 101 insertions(+), 93 deletions(-) diff --git a/client/src/pages/Project.tsx b/client/src/pages/Project.tsx index 392b63f..86331ce 100644 --- a/client/src/pages/Project.tsx +++ b/client/src/pages/Project.tsx @@ -13,7 +13,7 @@ import { } from "react-icons/hi"; import { NavLink, useNavigate, useParams } from "react-router"; import type { z } from "zod"; -import { generateDistinctColor } from "../../../common/colors"; +import { DEFAULT_PARTICIPATION_OPTION, generateDistinctColor } from "../../../common/colors"; import { editReqSchema, projectReqSchema } from "../../../common/validators"; import type { AppType } from "../../../server/src/main"; import Header from "../components/Header"; @@ -80,6 +80,7 @@ export default function ProjectPage() { const [copied, setCopied] = useState(false); const [isInfoExpanded, setIsInfoExpanded] = useState(!eventId); // 新規作成時は展開、編集時は折りたたみ + const [isParticipationExpanded, setIsParticipationExpanded] = useState(!!eventId); // 新規作成時は折りたたみ、編集時は展開 const { register, @@ -97,7 +98,15 @@ export default function ProjectPage() { startDate: eventId ? "" : dayjs().format("YYYY-MM-DD"), endDate: eventId ? "" : dayjs().add(6, "day").format("YYYY-MM-DD"), allowedRanges: [{ startTime: "08:00", endTime: "23:00" }], - participationOptions: [], + participationOptions: eventId + ? [] + : [ + { + id: crypto.randomUUID(), + label: DEFAULT_PARTICIPATION_OPTION.label, + color: DEFAULT_PARTICIPATION_OPTION.color, + }, + ], }, }); @@ -470,80 +479,93 @@ export default function ProjectPage() {

{errors.allowedRanges.message}

)} -
- 参加形態(任意) -

- 参加形態を設定すると、参加者は「対面」「オンライン」などの形態を選んで日程を登録できます。 -

+
+ setIsParticipationExpanded(e.target.checked)} + /> +
参加形態の設定 (任意)
+
+
+

+ 参加形態を設定すると、参加者は「対面」「オンライン」などの形態を選んで日程を登録できます。 +

- {participationFields.map((field, index) => { - const hasSlots = project?.guests.some((guest) => - guest.slots.some((slot) => slot.participationOptionId === field.id), - ); - return ( -
-
- - - { - // 値を変更していない場合でも空ならエラー表示させるため手動で検証 - trigger(`participationOptions.${index}.label` as const); - }} - /> -
- + {participationFields.map((field, index) => { + const hasSlots = project?.guests.some((guest) => + guest.slots.some((slot) => slot.participationOptionId === field.id), + ); + const isLastOption = participationFields.length === 1; + const cannotDelete = hasSlots || isLastOption; + const tooltipMessage = hasSlots + ? "すでにこの参加形態の日程が登録されているため、削除できません" + : isLastOption + ? "最低1つの参加形態が必要です" + : ""; + return ( +
+
+ + + { + // 値を変更していない場合でも空ならエラー表示させるため手動で検証 + trigger(`participationOptions.${index}.label` as const); + }} + /> +
+ +
+
+ {errors.participationOptions?.[index]?.label && ( +

+ {errors.participationOptions[index]?.label?.message as string} +

+ )} + {errors.participationOptions?.[index]?.color && ( +

+ {errors.participationOptions[index]?.color?.message as string} +

+ )}
-
- {errors.participationOptions?.[index]?.label && ( -

- {errors.participationOptions[index]?.label?.message as string} -

- )} - {errors.participationOptions?.[index]?.color && ( -

- {errors.participationOptions[index]?.color?.message as string} -

- )} -
- ); - })} + ); + })} - -
+ +
+
+
{project && (
イベントの削除 diff --git a/common/validators.ts b/common/validators.ts index 91ba844..130fbea 100644 --- a/common/validators.ts +++ b/common/validators.ts @@ -65,7 +65,7 @@ const baseProjectReqSchema = z.object({ .refine((ranges) => ranges.every(({ startTime, endTime }) => isQuarterHour(startTime) && isQuarterHour(endTime)), { message: "開始時刻と終了時刻は15分単位で入力してください", }), - participationOptions: z.array(participationOptionCreateSchema).optional(), + participationOptions: z.array(participationOptionCreateSchema).min(1, "参加形態は最低1つ必要です"), }); export const projectReqSchema = baseProjectReqSchema.refine( diff --git a/server/src/routes/projects.ts b/server/src/routes/projects.ts index eefea22..a74c6bc 100644 --- a/server/src/routes/projects.ts +++ b/server/src/routes/projects.ts @@ -1,11 +1,9 @@ -import { randomUUID } from "node:crypto"; import { zValidator } from "@hono/zod-validator"; import dotenv from "dotenv"; import { Hono } from "hono"; import { getSignedCookie, setSignedCookie } from "hono/cookie"; import { nanoid } from "nanoid"; import { z } from "zod"; -import { DEFAULT_PARTICIPATION_OPTION } from "../../../common/colors.js"; import { editReqSchema, projectReqSchema, submitReqSchema } from "../../../common/validators.js"; import { cookieOptions, prisma } from "../main.js"; @@ -25,22 +23,6 @@ const router = new Hono() try { const data = c.req.valid("json"); - // 参加形態の処理(指定がない場合はデフォルトを作成) - const participationOptionsData = - data.participationOptions && data.participationOptions.length > 0 - ? data.participationOptions.map((opt) => ({ - id: opt.id, // フロントエンドで生成された UUID をそのまま使用 - label: opt.label, - color: opt.color, - })) - : [ - { - id: randomUUID(), // デフォルト作成時のみサーバーで生成 - label: DEFAULT_PARTICIPATION_OPTION.label, - color: DEFAULT_PARTICIPATION_OPTION.color, - }, - ]; - const event = await prisma.project.create({ data: { id: nanoid(), @@ -60,7 +42,11 @@ const router = new Hono() }, }, participationOptions: { - create: participationOptionsData, + create: data.participationOptions.map((opt) => ({ + id: opt.id, + label: opt.label, + color: opt.color, + })), }, }, include: { hosts: true, participationOptions: true }, From 001efc6f9c90c5ae6e121afdd84a2b8d357f4818 Mon Sep 17 00:00:00 2001 From: nakaterm <104970808+nakaterm@users.noreply.github.com> Date: Sun, 23 Nov 2025 05:15:34 +0900 Subject: [PATCH 13/14] =?UTF-8?q?=E3=82=AB=E3=83=BC=E3=83=89=E3=81=AE?= =?UTF-8?q?=E6=9E=A0=E7=B7=9A=E3=81=AE=E8=89=B2=E3=81=8C=E6=B6=88=E3=81=88?= =?UTF-8?q?=E3=82=8B=E3=83=90=E3=82=B0=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/Home.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/pages/Home.tsx b/client/src/pages/Home.tsx index c9628a6..79700df 100644 --- a/client/src/pages/Home.tsx +++ b/client/src/pages/Home.tsx @@ -108,7 +108,7 @@ function ProjectCard({ project }: { project: BriefProject }) { return (
From 584a1ed9a3c576446565f31a2b1c5b6241fec51a Mon Sep 17 00:00:00 2001 From: nakaterm <104970808+nakaterm@users.noreply.github.com> Date: Sun, 23 Nov 2025 05:25:45 +0900 Subject: [PATCH 14/14] bump hono and related packages --- package-lock.json | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index f224bda..b3bf946 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1161,9 +1161,10 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.1.tgz", - "integrity": "sha512-h44e5s+ByUriaRIbeS/C74O8v90m0A95luyYQGMF7KEn96KkYMXO7bZAwombzTpjQTU4e0TkU8U1WBIXlwuwtA==", + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.6.tgz", + "integrity": "sha512-Shz/KjlIeAhfiuE93NDKVdZ7HdBVLQAfdbaXEaoAVO3ic9ibRSLGIQGkcBbFyuLr+7/1D5ZCINM8B+6IvXeMtw==", + "license": "MIT", "engines": { "node": ">=18.14.1" }, @@ -1172,9 +1173,10 @@ } }, "node_modules/@hono/zod-validator": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@hono/zod-validator/-/zod-validator-0.7.2.tgz", - "integrity": "sha512-ub5eL/NeZ4eLZawu78JpW/J+dugDAYhwqUIdp9KYScI6PZECij4Hx4UsrthlEUutqDDhPwRI0MscUfNkvn/mqQ==", + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@hono/zod-validator/-/zod-validator-0.7.5.tgz", + "integrity": "sha512-n4l4hutkfYU07PzRUHBOVzUEn38VSfrh+UVE5d0w4lyfWDOEhzxIupqo5iakRiJL44c3vTuFJBvcmUl8b9agIA==", + "license": "MIT", "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.25.0 || ^4.0.0" @@ -2583,9 +2585,10 @@ "license": "MIT" }, "node_modules/hono": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.9.6.tgz", - "integrity": "sha512-doVjXhSFvYZ7y0dNokjwwSahcrAfdz+/BCLvAMa/vHLzjj8+CFyV5xteThGUsKdkaasgN+gF2mUxao+SGLpUeA==", + "version": "4.10.6", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.10.6.tgz", + "integrity": "sha512-BIdolzGpDO9MQ4nu3AUuDwHZZ+KViNm+EZ75Ae55eMXMqLVhDFqEMXxtUe9Qh8hjL+pIna/frs2j6Y2yD5Ua/g==", + "license": "MIT", "engines": { "node": ">=16.9.0" }