@@ -286,8 +286,9 @@ import TextFieldSingleLine from "~/ui/components/TextFieldSingleLine.vue";
import ToggleButton from "~/ui/components/ToggleButton.vue";
import { useSwitch } from "~/ui/hooks/useSwitch";
import { addCoursesByCodes } from "~/ui/store/course";
+import { displayToast } from "~/ui/store/toast";
import { getApplicableYear } from "~/ui/store/year";
-import { deleteElementInArray } from "~/utils";
+import { asyncFilter, deleteElementInArray } from "~/utils";
import type { Schedule } from "~/domain/schedule";
import type { DisplayCourse } from "~/presentation/viewmodels/course";
@@ -469,19 +470,44 @@ const buttonState = computed(() =>
selectedSearchResults.length > 0 ? "default" : "disabled"
);
-const duplicateCourses = ref
([]);
+const duplicatedScheduleCourses = ref([]);
const addCourses = async (warning = true) => {
- duplicateCourses.value = (
- await Promise.all(
- selectedSearchResults.filter(
- ({ schedules }) =>
- !Usecase.checkScheduleDuplicate(ports)(year.value, schedules)
- )
+ const registeredCourse = await Usecase.getRegisteredCoursesByYear(ports)(
+ year.value
+ );
+
+ if (isResultError(registeredCourse)) throw registeredCourse;
+
+ const duplicatedCourses = selectedSearchResults
+ .filter(({ course: { code: selectedCourseCode } }) => {
+ return registeredCourse.some(
+ ({ code: registeredCourseCode }) =>
+ registeredCourseCode === selectedCourseCode
+ );
+ })
+ .map(({ course }) => course);
+
+ if (duplicatedCourses.length > 0) {
+ const text =
+ `以下のコースはすでに登録されているため追加できません。\n` +
+ duplicatedCourses
+ .map(({ code, name }) => `【${code}】${name}`)
+ .join("\n");
+ displayToast(text, { type: "danger" });
+ gtm?.trackEvent({ event: "duplicated-courses-error" });
+ return;
+ }
+
+ duplicatedScheduleCourses.value = await (
+ await asyncFilter(
+ selectedSearchResults,
+ async ({ schedules }) =>
+ !(await Usecase.checkScheduleDuplicate(ports)(year.value, schedules))
)
).map(({ course }) => course);
- if (warning && duplicateCourses.value.length > 0) {
+ if (warning && duplicatedScheduleCourses.value.length > 0) {
openDuplicateModal();
return;
}
diff --git a/src/ui/store/toast.ts b/src/ui/store/toast.ts
index f4ebca35..acfe4545 100644
--- a/src/ui/store/toast.ts
+++ b/src/ui/store/toast.ts
@@ -21,7 +21,7 @@ export const displayToast = (
option?: { displayPeriod?: number; type?: ToastType }
) => {
const id = createId();
- const displayPeriod = option?.displayPeriod ?? 3000;
+ const displayPeriod = option?.displayPeriod ?? text.length * 240; // 250 characters per minute reading speed
const type = option?.type ?? "danger";
toasts.push({ id, text, type });
diff --git a/src/utils.ts b/src/utils.ts
index 611f7616..1ff06ac0 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -133,3 +133,11 @@ export const hasProperty = (
): obj is { [key in P]: T } => {
return hasUnknownProperty(obj, prop) && clarifyPropertyType(obj, prop, fn);
};
+
+export const asyncFilter = async (
+ array: T[],
+ asyncCallback: (arg: T) => Promise
+) => {
+ const mask = await Promise.all(array.map(asyncCallback));
+ return array.filter((_, i) => Boolean(mask[i]));
+};