From 43b35951fe7c96a9d653fd4f19802bd314dfec19 Mon Sep 17 00:00:00 2001 From: nicole-nxn Date: Wed, 29 Apr 2026 11:16:02 +1000 Subject: [PATCH 01/19] clean up notification --- .../src/feature/task-add-edit/task-form.tsx | 37 ++---- .../util/create-notification-from-alert.ts | 33 ------ .../util/get-task-notification.ts | 111 ++++++++++++++++++ .../shared/models/notification-task-dto.ts | 4 - .../src/shared/util/schedule-task-reminder.ts | 25 ---- 5 files changed, 122 insertions(+), 88 deletions(-) delete mode 100644 blotztask-mobile/src/feature/task-add-edit/util/create-notification-from-alert.ts create mode 100644 blotztask-mobile/src/feature/task-add-edit/util/get-task-notification.ts delete mode 100644 blotztask-mobile/src/shared/models/notification-task-dto.ts delete mode 100644 blotztask-mobile/src/shared/util/schedule-task-reminder.ts diff --git a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx index d5aa5f6df..ed0f6a73f 100644 --- a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx +++ b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx @@ -13,15 +13,10 @@ import { useAllLabels } from "@/shared/hooks/useAllLabels"; import { EventTab } from "./components/event-tab"; import { AlertSelect } from "./components/alert-select"; import { DeadlineSection } from "./components/deadline-section"; -import { createNotificationFromAlert } from "./util/create-notification-from-alert"; -import { - buildTaskTimePayload, - calculateAlertSeconds, - calculateAlertTime, -} from "./util/time-convertion"; +import { getTaskNotification } from "./util/get-task-notification"; +import { buildTaskTimePayload, calculateAlertSeconds } from "./util/time-convertion"; import { combineDateTime } from "./util/combine-date-time"; import { TaskUpsertDTO } from "@/shared/models/task-upsert-dto"; -import { cancelNotification } from "@/shared/util/cancel-notification"; import { convertToDateTimeOffset } from "@/shared/util/convert-to-datetimeoffset"; import { useUserPreferencesQuery } from "../settings/hooks/useUserPreferencesQuery"; import LoadingScreen from "@/shared/components/loading-screen"; @@ -30,7 +25,7 @@ import Animated from "react-native-reanimated"; import { MotionAnimations } from "@/shared/constants/animations/motion"; import { theme } from "@/shared/constants/theme"; -type TaskFormProps = +export type TaskFormProps = | { mode: "create"; dto?: undefined; @@ -96,13 +91,6 @@ const TaskForm = ({ mode, dto, onSubmit }: TaskFormProps) => { } const handleFormSubmit = async (data: TaskFormField) => { - // If editing and the existing alert is still scheduled in the future, cancel the old notification first - if (mode === "edit" && dto?.alertTime && new Date(dto?.alertTime) > new Date()) { - await cancelNotification({ - notificationId: dto?.notificationId, - }); - } - const { startTime, endTime, timeType } = buildTaskTimePayload( data.startDate, data.startTime, @@ -110,17 +98,14 @@ const TaskForm = ({ mode, dto, onSubmit }: TaskFormProps) => { isActiveTab === "reminder" ? data.startTime : data.endTime, ); - let notificationId: string | null = null; - let alertTime = undefined; - if (userPreferences?.upcomingNotification) { - notificationId = - (await createNotificationFromAlert({ - startTime, - alert: data.alert, - title: data.title, - })) ?? null; - alertTime = calculateAlertTime(startTime!, data.alert); - } + const { notificationId, alertTime } = await getTaskNotification({ + mode, + dto, + upcomingNotification: userPreferences?.upcomingNotification, + startTime: startTime!, + alert: data.alert, + title: data.title, + }); const deadline = data.isDeadline ? combineDateTime(data.deadlineDate, data.deadlineTime) : null; diff --git a/blotztask-mobile/src/feature/task-add-edit/util/create-notification-from-alert.ts b/blotztask-mobile/src/feature/task-add-edit/util/create-notification-from-alert.ts deleted file mode 100644 index 4aa20d5e1..000000000 --- a/blotztask-mobile/src/feature/task-add-edit/util/create-notification-from-alert.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { NotificationTaskDTO } from "@/shared/models/notification-task-dto"; -import { scheduleTaskReminder } from "@/shared/util/schedule-task-reminder"; - -export async function createNotificationFromAlert({ - startTime, - alert, - title, -}: { - startTime?: Date | null; - alert?: number | null; - title: string; -}) { - if (!startTime || alert == null) { - return null; - } - const notificationTime = new Date(startTime.getTime() - alert * 1000); - - if (notificationTime <= new Date()) { - return null; - } - - const notificationTask: NotificationTaskDTO = { - title: title, - alertTime: notificationTime, - }; - - try { - const notificationId = await scheduleTaskReminder(notificationTask); - return notificationId; - } catch { - return null; - } -} diff --git a/blotztask-mobile/src/feature/task-add-edit/util/get-task-notification.ts b/blotztask-mobile/src/feature/task-add-edit/util/get-task-notification.ts new file mode 100644 index 000000000..f80ac9eb1 --- /dev/null +++ b/blotztask-mobile/src/feature/task-add-edit/util/get-task-notification.ts @@ -0,0 +1,111 @@ +import { TaskUpsertDTO } from "@/shared/models/task-upsert-dto"; +import TaskFormField from "../models/task-form-schema"; +import { cancelNotification } from "@/shared/util/cancel-notification"; +import { calculateAlertTime } from "./time-convertion"; +import { TaskFormProps } from "../task-form"; +import * as Notifications from "expo-notifications"; +import uuid from "react-native-uuid"; + +type NotificationPayload = { + alertTime: Date | null; + notificationId: string | null; +}; +interface NotificationTaskDTO { + title: string; + alertTime: Date; +} + +export const getTaskNotification = async ({ + mode, + dto, + upcomingNotification, + startTime, + alert, + title, +}: { + mode: TaskFormProps["mode"]; + dto?: TaskUpsertDTO; + upcomingNotification?: boolean; + startTime: Date; + alert: TaskFormField["alert"]; + title: string; +}): Promise => { + // Replace the old scheduled notification before creating a new one during edits. + if (mode === "edit" && dto?.alertTime && new Date(dto.alertTime) > new Date()) { + await cancelNotification({ + notificationId: dto.notificationId, + }); + } + + if (!upcomingNotification) { + return { + notificationId: null, + alertTime: null, + }; + } + + const notificationId = + (await createNotificationFromAlert({ + startTime, + alert, + title, + })) ?? null; + + return { + notificationId, + alertTime: calculateAlertTime(startTime, alert), + }; +}; + +async function createNotificationFromAlert({ + startTime, + alert, + title, +}: { + startTime?: Date | null; + alert?: number | null; + title: string; +}) { + if (!startTime || alert == null) { + return null; + } + const notificationTime = new Date(startTime.getTime() - alert * 1000); + + if (notificationTime <= new Date()) { + return null; + } + + const notificationTask: NotificationTaskDTO = { + title: title, + alertTime: notificationTime, + }; + + try { + const notificationId = await scheduleTaskReminder(notificationTask); + return notificationId; + } catch { + return null; + } +} + +async function scheduleTaskReminder(task: NotificationTaskDTO) { + if (!task.alertTime) return; + + const notificationId = await Notifications.scheduleNotificationAsync({ + content: { + title: "⏰ Task Reminder", + body: task.title, + data: { + id: uuid.v4().toString(), + }, + categoryIdentifier: "task-reminder", + }, + + trigger: { + type: Notifications.SchedulableTriggerInputTypes.DATE, + date: new Date(task.alertTime), + }, + }); + + return notificationId; +} diff --git a/blotztask-mobile/src/shared/models/notification-task-dto.ts b/blotztask-mobile/src/shared/models/notification-task-dto.ts deleted file mode 100644 index 571d9e3ff..000000000 --- a/blotztask-mobile/src/shared/models/notification-task-dto.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface NotificationTaskDTO { - title: string; - alertTime: Date; -} diff --git a/blotztask-mobile/src/shared/util/schedule-task-reminder.ts b/blotztask-mobile/src/shared/util/schedule-task-reminder.ts deleted file mode 100644 index 2bcde346d..000000000 --- a/blotztask-mobile/src/shared/util/schedule-task-reminder.ts +++ /dev/null @@ -1,25 +0,0 @@ -import * as Notifications from "expo-notifications"; -import { NotificationTaskDTO } from "../models/notification-task-dto"; -import uuid from "react-native-uuid"; - -export async function scheduleTaskReminder(task: NotificationTaskDTO) { - if (!task.alertTime) return; - - const notificationId = await Notifications.scheduleNotificationAsync({ - content: { - title: "⏰ Task Reminder", - body: task.title, - data: { - id: uuid.v4().toString(), - }, - categoryIdentifier: "task-reminder", - }, - - trigger: { - type: Notifications.SchedulableTriggerInputTypes.DATE, - date: new Date(task.alertTime), - }, - }); - - return notificationId; -} From 65cc4aba366745fe15296b714a257f2e99ede27e Mon Sep 17 00:00:00 2001 From: nicole-nxn Date: Wed, 29 Apr 2026 11:22:04 +1000 Subject: [PATCH 02/19] fix code --- .../src/feature/task-add-edit/task-form.tsx | 4 +- .../util/get-task-notification.ts | 90 +++++++------------ 2 files changed, 32 insertions(+), 62 deletions(-) diff --git a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx index ed0f6a73f..f1218af2f 100644 --- a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx +++ b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx @@ -50,9 +50,7 @@ const TaskForm = ({ mode, dto, onSubmit }: TaskFormProps) => { const initialAlertTime = calculateAlertSeconds(dto?.startTime, dto?.alertTime); - const defaultAlert = userPreferences?.upcomingNotification - ? (initialAlertTime ?? 300) - : (initialAlertTime ?? null); + const defaultAlert = initialAlertTime ?? (userPreferences?.upcomingNotification ? 300 : null); const now = new Date(); const oneHourLater = new Date(now.getTime() + 3600000); diff --git a/blotztask-mobile/src/feature/task-add-edit/util/get-task-notification.ts b/blotztask-mobile/src/feature/task-add-edit/util/get-task-notification.ts index f80ac9eb1..fcdf906aa 100644 --- a/blotztask-mobile/src/feature/task-add-edit/util/get-task-notification.ts +++ b/blotztask-mobile/src/feature/task-add-edit/util/get-task-notification.ts @@ -1,19 +1,13 @@ import { TaskUpsertDTO } from "@/shared/models/task-upsert-dto"; -import TaskFormField from "../models/task-form-schema"; +import type { TaskFormField } from "../models/task-form-schema"; import { cancelNotification } from "@/shared/util/cancel-notification"; import { calculateAlertTime } from "./time-convertion"; -import { TaskFormProps } from "../task-form"; import * as Notifications from "expo-notifications"; -import uuid from "react-native-uuid"; type NotificationPayload = { alertTime: Date | null; notificationId: string | null; }; -interface NotificationTaskDTO { - title: string; - alertTime: Date; -} export const getTaskNotification = async ({ mode, @@ -23,7 +17,7 @@ export const getTaskNotification = async ({ alert, title, }: { - mode: TaskFormProps["mode"]; + mode: "create" | "edit"; dto?: TaskUpsertDTO; upcomingNotification?: boolean; startTime: Date; @@ -44,68 +38,46 @@ export const getTaskNotification = async ({ }; } - const notificationId = - (await createNotificationFromAlert({ - startTime, - alert, - title, - })) ?? null; + const alertTime = calculateAlertTime(startTime, alert); + + if (!alertTime || alertTime <= new Date()) { + return { + notificationId: null, + alertTime: null, + }; + } + + const notificationId = await scheduleTaskReminder({ + title, + alertTime, + }); return { notificationId, - alertTime: calculateAlertTime(startTime, alert), + alertTime: notificationId ? alertTime : null, }; }; -async function createNotificationFromAlert({ - startTime, - alert, +async function scheduleTaskReminder({ title, + alertTime, }: { - startTime?: Date | null; - alert?: number | null; title: string; -}) { - if (!startTime || alert == null) { - return null; - } - const notificationTime = new Date(startTime.getTime() - alert * 1000); - - if (notificationTime <= new Date()) { - return null; - } - - const notificationTask: NotificationTaskDTO = { - title: title, - alertTime: notificationTime, - }; - + alertTime: Date; +}): Promise { try { - const notificationId = await scheduleTaskReminder(notificationTask); - return notificationId; + return await Notifications.scheduleNotificationAsync({ + content: { + title: "⏰ Task Reminder", + body: title, + categoryIdentifier: "task-reminder", + }, + trigger: { + type: Notifications.SchedulableTriggerInputTypes.DATE, + date: alertTime, + }, + }); } catch { return null; } } - -async function scheduleTaskReminder(task: NotificationTaskDTO) { - if (!task.alertTime) return; - - const notificationId = await Notifications.scheduleNotificationAsync({ - content: { - title: "⏰ Task Reminder", - body: task.title, - data: { - id: uuid.v4().toString(), - }, - categoryIdentifier: "task-reminder", - }, - - trigger: { - type: Notifications.SchedulableTriggerInputTypes.DATE, - date: new Date(task.alertTime), - }, - }); - - return notificationId; -} From d4f56ec74198507a7eebd81ccf6c81667b46d2fc Mon Sep 17 00:00:00 2001 From: nicole-nxn Date: Wed, 29 Apr 2026 11:26:44 +1000 Subject: [PATCH 03/19] fix form --- .../src/feature/task-add-edit/task-form.tsx | 73 +++++++++---------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx index f1218af2f..4a8b8ea01 100644 --- a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx +++ b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx @@ -38,47 +38,44 @@ export type TaskFormProps = }; const TaskForm = ({ mode, dto, onSubmit }: TaskFormProps) => { - const hasEventTimes = - dto?.timeType === 1 || (dto?.startTime && dto?.endTime && dto.startTime !== dto.endTime); - const initialTab: SegmentButtonValue = mode === "edit" && hasEventTimes ? "event" : "reminder"; + // Queries const { userPreferences, isUserPreferencesLoading } = useUserPreferencesQuery(); - const { t } = useTranslation("tasks"); - - const [isActiveTab, setIsActiveTab] = useState(initialTab); - const { labels = [], isLoading } = useAllLabels(); + const { t } = useTranslation("tasks"); - const initialAlertTime = calculateAlertSeconds(dto?.startTime, dto?.alertTime); - - const defaultAlert = initialAlertTime ?? (userPreferences?.upcomingNotification ? 300 : null); - + // Variables const now = new Date(); const oneHourLater = new Date(now.getTime() + 3600000); - const initialDueAt = dto?.dueAt ? new Date(dto.dueAt) : null; - - const initialStartDate = dto?.startTime ? new Date(dto.startTime) : now; - const initialStartTime = dto?.startTime ? new Date(dto.startTime) : now; - const initialEndDate = dto?.endTime ? new Date(dto.endTime) : oneHourLater; - const initialEndTime = dto?.endTime ? new Date(dto.endTime) : oneHourLater; - - const defaultValues: TaskFormField = { - title: dto?.title ?? "", - description: dto?.description ?? "", - labelId: dto?.labelId ?? null, - startDate: initialStartDate, - startTime: initialStartTime, - endDate: initialTab === "reminder" ? initialStartDate : initialEndDate, - endTime: initialTab === "reminder" ? initialStartTime : initialEndTime, - alert: defaultAlert, - isDeadline: dto?.isDeadline ?? !!initialDueAt, - deadlineDate: initialDueAt ?? oneHourLater, - deadlineTime: initialDueAt ?? oneHourLater, - }; + const dtoStartTime = dto?.startTime ? new Date(dto.startTime) : now; + const dtoEndTime = dto?.endTime ? new Date(dto.endTime) : oneHourLater; + const dueAt = dto?.dueAt ? new Date(dto.dueAt) : null; + // Derived values + const hasEventTimes = + dto?.timeType === 1 || (dto?.startTime && dto?.endTime && dto.startTime !== dto.endTime); + const initialTab: SegmentButtonValue = mode === "edit" && hasEventTimes ? "event" : "reminder"; + const initialAlert = calculateAlertSeconds(dto?.startTime, dto?.alertTime); + const defaultAlert = initialAlert ?? (userPreferences?.upcomingNotification ? 300 : null); + + const [isActiveTab, setIsActiveTab] = useState(initialTab); + + // Form const form = useForm({ resolver: zodResolver(taskFormSchema), mode: "onChange", - defaultValues: defaultValues, + defaultValues: { + title: dto?.title ?? "", + description: dto?.description ?? "", + labelId: dto?.labelId ?? null, + startDate: dtoStartTime, + startTime: dtoStartTime, + endDate: initialTab === "reminder" ? dtoStartTime : dtoEndTime, + endTime: initialTab === "reminder" ? dtoStartTime : dtoEndTime, + alert: defaultAlert, + isDeadline: dto?.isDeadline ?? !!dueAt, + deadlineDate: dueAt ?? oneHourLater, + deadlineTime: dueAt ?? oneHourLater, + }, }); const { handleSubmit, formState, control, setValue, clearErrors, trigger, getValues } = form; @@ -88,12 +85,14 @@ const TaskForm = ({ mode, dto, onSubmit }: TaskFormProps) => { return ; } + // Handlers const handleFormSubmit = async (data: TaskFormField) => { + const isReminderTab = isActiveTab === "reminder"; const { startTime, endTime, timeType } = buildTaskTimePayload( data.startDate, data.startTime, - isActiveTab === "reminder" ? data.startDate : data.endDate, - isActiveTab === "reminder" ? data.startTime : data.endTime, + isReminderTab ? data.startDate : data.endDate, + isReminderTab ? data.startTime : data.endTime, ); const { notificationId, alertTime } = await getTaskNotification({ @@ -124,12 +123,12 @@ const TaskForm = ({ mode, dto, onSubmit }: TaskFormProps) => { }; const handleTabChange = (next: SegmentButtonValue) => { - setIsActiveTab(next); - clearErrors(["endDate", "endTime"]); - const startDate = getValues("startDate"); const startTime = getValues("startTime"); + setIsActiveTab(next); + clearErrors(["endDate", "endTime"]); + if (next === "reminder") { setValue("endDate", startDate, { shouldValidate: false }); setValue("endTime", startTime, { shouldValidate: false }); From 016434a877643f58e5df46c2e2d971aafa87b679 Mon Sep 17 00:00:00 2001 From: nicole-nxn Date: Wed, 29 Apr 2026 11:36:42 +1000 Subject: [PATCH 04/19] extract default --- .../src/feature/task-add-edit/task-form.tsx | 34 +++---------- .../util/get-task-form-defaults.ts | 50 +++++++++++++++++++ 2 files changed, 58 insertions(+), 26 deletions(-) create mode 100644 blotztask-mobile/src/feature/task-add-edit/util/get-task-form-defaults.ts diff --git a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx index 4a8b8ea01..f94cb578a 100644 --- a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx +++ b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx @@ -13,8 +13,9 @@ import { useAllLabels } from "@/shared/hooks/useAllLabels"; import { EventTab } from "./components/event-tab"; import { AlertSelect } from "./components/alert-select"; import { DeadlineSection } from "./components/deadline-section"; +import { getTaskFormDefaults } from "./util/get-task-form-defaults"; import { getTaskNotification } from "./util/get-task-notification"; -import { buildTaskTimePayload, calculateAlertSeconds } from "./util/time-convertion"; +import { buildTaskTimePayload } from "./util/time-convertion"; import { combineDateTime } from "./util/combine-date-time"; import { TaskUpsertDTO } from "@/shared/models/task-upsert-dto"; import { convertToDateTimeOffset } from "@/shared/util/convert-to-datetimeoffset"; @@ -43,19 +44,12 @@ const TaskForm = ({ mode, dto, onSubmit }: TaskFormProps) => { const { labels = [], isLoading } = useAllLabels(); const { t } = useTranslation("tasks"); - // Variables - const now = new Date(); - const oneHourLater = new Date(now.getTime() + 3600000); - const dtoStartTime = dto?.startTime ? new Date(dto.startTime) : now; - const dtoEndTime = dto?.endTime ? new Date(dto.endTime) : oneHourLater; - const dueAt = dto?.dueAt ? new Date(dto.dueAt) : null; - // Derived values - const hasEventTimes = - dto?.timeType === 1 || (dto?.startTime && dto?.endTime && dto.startTime !== dto.endTime); - const initialTab: SegmentButtonValue = mode === "edit" && hasEventTimes ? "event" : "reminder"; - const initialAlert = calculateAlertSeconds(dto?.startTime, dto?.alertTime); - const defaultAlert = initialAlert ?? (userPreferences?.upcomingNotification ? 300 : null); + const { initialTab, defaultValues } = getTaskFormDefaults({ + mode, + dto, + upcomingNotification: userPreferences?.upcomingNotification, + }); const [isActiveTab, setIsActiveTab] = useState(initialTab); @@ -63,19 +57,7 @@ const TaskForm = ({ mode, dto, onSubmit }: TaskFormProps) => { const form = useForm({ resolver: zodResolver(taskFormSchema), mode: "onChange", - defaultValues: { - title: dto?.title ?? "", - description: dto?.description ?? "", - labelId: dto?.labelId ?? null, - startDate: dtoStartTime, - startTime: dtoStartTime, - endDate: initialTab === "reminder" ? dtoStartTime : dtoEndTime, - endTime: initialTab === "reminder" ? dtoStartTime : dtoEndTime, - alert: defaultAlert, - isDeadline: dto?.isDeadline ?? !!dueAt, - deadlineDate: dueAt ?? oneHourLater, - deadlineTime: dueAt ?? oneHourLater, - }, + defaultValues, }); const { handleSubmit, formState, control, setValue, clearErrors, trigger, getValues } = form; diff --git a/blotztask-mobile/src/feature/task-add-edit/util/get-task-form-defaults.ts b/blotztask-mobile/src/feature/task-add-edit/util/get-task-form-defaults.ts new file mode 100644 index 000000000..e18aadc8a --- /dev/null +++ b/blotztask-mobile/src/feature/task-add-edit/util/get-task-form-defaults.ts @@ -0,0 +1,50 @@ +import type { TaskUpsertDTO } from "@/shared/models/task-upsert-dto"; +import type { TaskFormField } from "../models/task-form-schema"; +import type { SegmentButtonValue } from "../models/segment-button-value"; +import { calculateAlertSeconds } from "./time-convertion"; + +type GetTaskFormDefaultsParams = { + mode: "create" | "edit"; + dto?: TaskUpsertDTO; + upcomingNotification?: boolean; +}; + +type TaskFormDefaults = { + initialTab: SegmentButtonValue; + defaultValues: TaskFormField; +}; + +export const getTaskFormDefaults = ({ + mode, + dto, + upcomingNotification, +}: GetTaskFormDefaultsParams): TaskFormDefaults => { + const now = new Date(); + const oneHourLater = new Date(now.getTime() + 3600000); + const dtoStartTime = dto?.startTime ? new Date(dto.startTime) : now; + const dtoEndTime = dto?.endTime ? new Date(dto.endTime) : oneHourLater; + const dueAt = dto?.dueAt ? new Date(dto.dueAt) : null; + + const hasEventTimes = + dto?.timeType === 1 || (dto?.startTime && dto?.endTime && dto.startTime !== dto.endTime); + const initialTab: SegmentButtonValue = mode === "edit" && hasEventTimes ? "event" : "reminder"; + const initialAlert = calculateAlertSeconds(dto?.startTime, dto?.alertTime); + const defaultAlert = initialAlert ?? (upcomingNotification ? 300 : null); + + return { + initialTab, + defaultValues: { + title: dto?.title ?? "", + description: dto?.description ?? "", + labelId: dto?.labelId ?? null, + startDate: dtoStartTime, + startTime: dtoStartTime, + endDate: initialTab === "reminder" ? dtoStartTime : dtoEndTime, + endTime: initialTab === "reminder" ? dtoStartTime : dtoEndTime, + alert: defaultAlert, + isDeadline: dto?.isDeadline ?? !!dueAt, + deadlineDate: dueAt ?? oneHourLater, + deadlineTime: dueAt ?? oneHourLater, + }, + }; +}; From dc5d577b0e706ef14e03031d7d3690ed6dfc0923 Mon Sep 17 00:00:00 2001 From: nicole-nxn Date: Wed, 29 Apr 2026 18:26:12 +1000 Subject: [PATCH 05/19] move cancel notification --- blotztask-mobile/src/feature/calendar/components/task-card.tsx | 2 +- .../task-add-edit}/util/cancel-notification.ts | 0 .../src/feature/task-add-edit/util/get-task-notification.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename blotztask-mobile/src/{shared => feature/task-add-edit}/util/cancel-notification.ts (100%) diff --git a/blotztask-mobile/src/feature/calendar/components/task-card.tsx b/blotztask-mobile/src/feature/calendar/components/task-card.tsx index d64361fc4..0500f9f8a 100644 --- a/blotztask-mobile/src/feature/calendar/components/task-card.tsx +++ b/blotztask-mobile/src/feature/calendar/components/task-card.tsx @@ -14,7 +14,7 @@ import { router } from "expo-router"; import useTaskMutations from "@/shared/hooks/useTaskMutations"; import { useRecurringTaskMutations } from "../hooks/useRecurringTaskMutations"; import { useSubtaskMutations } from "@/feature/task-details/hooks/useSubtaskMutations"; -import { cancelNotification } from "@/shared/util/cancel-notification"; +import { cancelNotification } from "@/feature/task-add-edit/util/cancel-notification"; import { formatDateRange } from "../util/format-date-range"; import { AnimatedChevron } from "@/shared/components/chevron"; import { SubtaskProgressBar } from "./subtask-progress-bar"; diff --git a/blotztask-mobile/src/shared/util/cancel-notification.ts b/blotztask-mobile/src/feature/task-add-edit/util/cancel-notification.ts similarity index 100% rename from blotztask-mobile/src/shared/util/cancel-notification.ts rename to blotztask-mobile/src/feature/task-add-edit/util/cancel-notification.ts diff --git a/blotztask-mobile/src/feature/task-add-edit/util/get-task-notification.ts b/blotztask-mobile/src/feature/task-add-edit/util/get-task-notification.ts index fcdf906aa..4759c262e 100644 --- a/blotztask-mobile/src/feature/task-add-edit/util/get-task-notification.ts +++ b/blotztask-mobile/src/feature/task-add-edit/util/get-task-notification.ts @@ -1,6 +1,6 @@ import { TaskUpsertDTO } from "@/shared/models/task-upsert-dto"; import type { TaskFormField } from "../models/task-form-schema"; -import { cancelNotification } from "@/shared/util/cancel-notification"; +import { cancelNotification } from "@/feature/task-add-edit/util/cancel-notification"; import { calculateAlertTime } from "./time-convertion"; import * as Notifications from "expo-notifications"; From 18d967d4a65030969fa08491bc29e798934841bf Mon Sep 17 00:00:00 2001 From: nicole-nxn Date: Wed, 29 Apr 2026 18:26:52 +1000 Subject: [PATCH 06/19] m --- blotztask-mobile/src/feature/calendar/components/task-card.tsx | 2 +- .../src/feature/task-add-edit/util/get-task-notification.ts | 2 +- .../task-add-edit => shared}/util/cancel-notification.ts | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename blotztask-mobile/src/{feature/task-add-edit => shared}/util/cancel-notification.ts (100%) diff --git a/blotztask-mobile/src/feature/calendar/components/task-card.tsx b/blotztask-mobile/src/feature/calendar/components/task-card.tsx index 0500f9f8a..d64361fc4 100644 --- a/blotztask-mobile/src/feature/calendar/components/task-card.tsx +++ b/blotztask-mobile/src/feature/calendar/components/task-card.tsx @@ -14,7 +14,7 @@ import { router } from "expo-router"; import useTaskMutations from "@/shared/hooks/useTaskMutations"; import { useRecurringTaskMutations } from "../hooks/useRecurringTaskMutations"; import { useSubtaskMutations } from "@/feature/task-details/hooks/useSubtaskMutations"; -import { cancelNotification } from "@/feature/task-add-edit/util/cancel-notification"; +import { cancelNotification } from "@/shared/util/cancel-notification"; import { formatDateRange } from "../util/format-date-range"; import { AnimatedChevron } from "@/shared/components/chevron"; import { SubtaskProgressBar } from "./subtask-progress-bar"; diff --git a/blotztask-mobile/src/feature/task-add-edit/util/get-task-notification.ts b/blotztask-mobile/src/feature/task-add-edit/util/get-task-notification.ts index 4759c262e..fcdf906aa 100644 --- a/blotztask-mobile/src/feature/task-add-edit/util/get-task-notification.ts +++ b/blotztask-mobile/src/feature/task-add-edit/util/get-task-notification.ts @@ -1,6 +1,6 @@ import { TaskUpsertDTO } from "@/shared/models/task-upsert-dto"; import type { TaskFormField } from "../models/task-form-schema"; -import { cancelNotification } from "@/feature/task-add-edit/util/cancel-notification"; +import { cancelNotification } from "@/shared/util/cancel-notification"; import { calculateAlertTime } from "./time-convertion"; import * as Notifications from "expo-notifications"; diff --git a/blotztask-mobile/src/feature/task-add-edit/util/cancel-notification.ts b/blotztask-mobile/src/shared/util/cancel-notification.ts similarity index 100% rename from blotztask-mobile/src/feature/task-add-edit/util/cancel-notification.ts rename to blotztask-mobile/src/shared/util/cancel-notification.ts From 2bf69d1f917026a03fe1b9164c76086946154794 Mon Sep 17 00:00:00 2001 From: nicole-nxn Date: Wed, 29 Apr 2026 18:31:16 +1000 Subject: [PATCH 07/19] fix --- .../feature/calendar/components/task-card.tsx | 7 +++++-- .../src/feature/task-add-edit/task-form.tsx | 17 ++++++----------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/blotztask-mobile/src/feature/calendar/components/task-card.tsx b/blotztask-mobile/src/feature/calendar/components/task-card.tsx index d64361fc4..06915d312 100644 --- a/blotztask-mobile/src/feature/calendar/components/task-card.tsx +++ b/blotztask-mobile/src/feature/calendar/components/task-card.tsx @@ -21,6 +21,7 @@ import { SubtaskProgressBar } from "./subtask-progress-bar"; import SubtaskList from "./subtask-list"; import { TaskCardRightActions } from "./task-card-right-actions"; import { TaskCardLeftActions } from "./task-card-left-actions"; +import { theme } from "@/shared/constants/theme"; // Props interface TaskCardProps { @@ -41,7 +42,6 @@ const TaskCard = ({ task, deleteTask, isDeleting, selectedDay, onOpenMode }: Tas const { breakDownAndReplaceSubtasks, isBreakingDownAndReplacingSubtasks } = useSubtaskMutations(); // Derived values - const labelColor = task.label?.color ?? "#D1D1D6"; const hasSubtasks = !!task.subtasks?.length; const timePeriod = formatDateRange({ startTime: task.startTime, @@ -139,7 +139,10 @@ const TaskCard = ({ task, deleteTask, isDeleting, selectedDay, onOpenMode }: Tas /> {/* Vertical label colour bar */} - + {/* DDL Tag */} {task.isDeadline && ( diff --git a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx index f94cb578a..0f9d31b37 100644 --- a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx +++ b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx @@ -26,17 +26,12 @@ import Animated from "react-native-reanimated"; import { MotionAnimations } from "@/shared/constants/animations/motion"; import { theme } from "@/shared/constants/theme"; -export type TaskFormProps = - | { - mode: "create"; - dto?: undefined; - onSubmit: (data: TaskUpsertDTO) => void; - } - | { - mode: "edit"; - dto: TaskUpsertDTO; - onSubmit: (data: TaskUpsertDTO) => void; - }; +export type TaskFormProps = { + onSubmit: (data: TaskUpsertDTO) => void; +} & ( + | { mode: "create"; dto?: undefined } + | { mode: "edit"; dto: TaskUpsertDTO } +); const TaskForm = ({ mode, dto, onSubmit }: TaskFormProps) => { // Queries From ba78ad2b4bd7a095293426a1a40afa3786e79b70 Mon Sep 17 00:00:00 2001 From: nicole-nxn Date: Wed, 29 Apr 2026 18:40:02 +1000 Subject: [PATCH 08/19] getTaskNotification --- .../src/feature/task-add-edit/task-form.tsx | 15 +++---- .../util/get-task-notification.ts | 41 +++---------------- 2 files changed, 12 insertions(+), 44 deletions(-) diff --git a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx index 0f9d31b37..5b0918c8e 100644 --- a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx +++ b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx @@ -15,7 +15,7 @@ import { AlertSelect } from "./components/alert-select"; import { DeadlineSection } from "./components/deadline-section"; import { getTaskFormDefaults } from "./util/get-task-form-defaults"; import { getTaskNotification } from "./util/get-task-notification"; -import { buildTaskTimePayload } from "./util/time-convertion"; +import { buildTaskTimePayload, calculateAlertTime } from "./util/time-convertion"; import { combineDateTime } from "./util/combine-date-time"; import { TaskUpsertDTO } from "@/shared/models/task-upsert-dto"; import { convertToDateTimeOffset } from "@/shared/util/convert-to-datetimeoffset"; @@ -28,10 +28,7 @@ import { theme } from "@/shared/constants/theme"; export type TaskFormProps = { onSubmit: (data: TaskUpsertDTO) => void; -} & ( - | { mode: "create"; dto?: undefined } - | { mode: "edit"; dto: TaskUpsertDTO } -); +} & ({ mode: "create"; dto?: undefined } | { mode: "edit"; dto: TaskUpsertDTO }); const TaskForm = ({ mode, dto, onSubmit }: TaskFormProps) => { // Queries @@ -72,12 +69,12 @@ const TaskForm = ({ mode, dto, onSubmit }: TaskFormProps) => { isReminderTab ? data.startTime : data.endTime, ); - const { notificationId, alertTime } = await getTaskNotification({ + const newAlertTime = calculateAlertTime(startTime!, data.alert); + const notificationId = await getTaskNotification({ mode, dto, upcomingNotification: userPreferences?.upcomingNotification, - startTime: startTime!, - alert: data.alert, + newAlertTime, title: data.title, }); @@ -90,7 +87,7 @@ const TaskForm = ({ mode, dto, onSubmit }: TaskFormProps) => { endTime: convertToDateTimeOffset(endTime!), labelId: data.labelId ?? undefined, timeType, - alertTime: alertTime ? convertToDateTimeOffset(alertTime) : undefined, + alertTime: notificationId && newAlertTime ? convertToDateTimeOffset(newAlertTime) : undefined, notificationId, isDeadline: data.isDeadline, dueAt: deadline ? convertToDateTimeOffset(deadline) : undefined, diff --git a/blotztask-mobile/src/feature/task-add-edit/util/get-task-notification.ts b/blotztask-mobile/src/feature/task-add-edit/util/get-task-notification.ts index fcdf906aa..8c8053764 100644 --- a/blotztask-mobile/src/feature/task-add-edit/util/get-task-notification.ts +++ b/blotztask-mobile/src/feature/task-add-edit/util/get-task-notification.ts @@ -1,29 +1,20 @@ import { TaskUpsertDTO } from "@/shared/models/task-upsert-dto"; -import type { TaskFormField } from "../models/task-form-schema"; import { cancelNotification } from "@/shared/util/cancel-notification"; -import { calculateAlertTime } from "./time-convertion"; import * as Notifications from "expo-notifications"; -type NotificationPayload = { - alertTime: Date | null; - notificationId: string | null; -}; - export const getTaskNotification = async ({ mode, dto, upcomingNotification, - startTime, - alert, + newAlertTime, title, }: { mode: "create" | "edit"; dto?: TaskUpsertDTO; upcomingNotification?: boolean; - startTime: Date; - alert: TaskFormField["alert"]; + newAlertTime: Date | null; title: string; -}): Promise => { +}): Promise => { // Replace the old scheduled notification before creating a new one during edits. if (mode === "edit" && dto?.alertTime && new Date(dto.alertTime) > new Date()) { await cancelNotification({ @@ -31,31 +22,11 @@ export const getTaskNotification = async ({ }); } - if (!upcomingNotification) { - return { - notificationId: null, - alertTime: null, - }; - } - - const alertTime = calculateAlertTime(startTime, alert); - - if (!alertTime || alertTime <= new Date()) { - return { - notificationId: null, - alertTime: null, - }; + if (!upcomingNotification || !newAlertTime || newAlertTime <= new Date()) { + return null; } - const notificationId = await scheduleTaskReminder({ - title, - alertTime, - }); - - return { - notificationId, - alertTime: notificationId ? alertTime : null, - }; + return scheduleTaskReminder({ title, alertTime: newAlertTime }); }; async function scheduleTaskReminder({ From acff5a272ef7a65d94b590ebabd967df56dcc8e6 Mon Sep 17 00:00:00 2001 From: nicole-nxn Date: Wed, 29 Apr 2026 18:41:52 +1000 Subject: [PATCH 09/19] update var --- .../src/feature/task-add-edit/task-form.tsx | 2 +- .../task-add-edit/util/get-task-notification.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx index 5b0918c8e..973b72a6c 100644 --- a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx +++ b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx @@ -75,7 +75,7 @@ const TaskForm = ({ mode, dto, onSubmit }: TaskFormProps) => { dto, upcomingNotification: userPreferences?.upcomingNotification, newAlertTime, - title: data.title, + newTaskTitle: data.title, }); const deadline = data.isDeadline ? combineDateTime(data.deadlineDate, data.deadlineTime) : null; diff --git a/blotztask-mobile/src/feature/task-add-edit/util/get-task-notification.ts b/blotztask-mobile/src/feature/task-add-edit/util/get-task-notification.ts index 8c8053764..436e8b739 100644 --- a/blotztask-mobile/src/feature/task-add-edit/util/get-task-notification.ts +++ b/blotztask-mobile/src/feature/task-add-edit/util/get-task-notification.ts @@ -7,13 +7,13 @@ export const getTaskNotification = async ({ dto, upcomingNotification, newAlertTime, - title, + newTaskTitle, }: { mode: "create" | "edit"; dto?: TaskUpsertDTO; upcomingNotification?: boolean; newAlertTime: Date | null; - title: string; + newTaskTitle: string; }): Promise => { // Replace the old scheduled notification before creating a new one during edits. if (mode === "edit" && dto?.alertTime && new Date(dto.alertTime) > new Date()) { @@ -26,21 +26,21 @@ export const getTaskNotification = async ({ return null; } - return scheduleTaskReminder({ title, alertTime: newAlertTime }); + return scheduleTaskReminder({ newTaskTitle, alertTime: newAlertTime }); }; async function scheduleTaskReminder({ - title, + newTaskTitle, alertTime, }: { - title: string; + newTaskTitle: string; alertTime: Date; }): Promise { try { return await Notifications.scheduleNotificationAsync({ content: { title: "⏰ Task Reminder", - body: title, + body: newTaskTitle, categoryIdentifier: "task-reminder", }, trigger: { From adb7d296046e52c7234ff8b9f65b82c2a89cad9d Mon Sep 17 00:00:00 2001 From: nicole-nxn Date: Wed, 29 Apr 2026 22:40:35 +1000 Subject: [PATCH 10/19] fix type --- .../task-add-edit/components/deadline-section.tsx | 2 +- .../task-add-edit/components/segment-toggle.tsx | 12 ++++++------ .../task-add-edit/models/segment-button-value.ts | 5 ++++- .../src/feature/task-add-edit/task-form.tsx | 10 +++++----- .../task-add-edit/util/get-task-form-defaults.ts | 11 +++++------ 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/blotztask-mobile/src/feature/task-add-edit/components/deadline-section.tsx b/blotztask-mobile/src/feature/task-add-edit/components/deadline-section.tsx index cb9ea3c52..a9261f1b2 100644 --- a/blotztask-mobile/src/feature/task-add-edit/components/deadline-section.tsx +++ b/blotztask-mobile/src/feature/task-add-edit/components/deadline-section.tsx @@ -148,7 +148,7 @@ export const DeadlineSection = ({ control, getValues, isActiveTab }: DeadlineSec deadlineDate={deadlineDate ? format(deadlineDate, "yyyy-MM-dd") : undefined} eventStartDate={startDate ? format(startDate, "yyyy-MM-dd") : undefined} eventEndDate={ - isActiveTab === "event" && endDate + isActiveTab === SegmentButtonValue.Event && endDate ? format(endDate, "yyyy-MM-dd") : startDate ? format(startDate, "yyyy-MM-dd") diff --git a/blotztask-mobile/src/feature/task-add-edit/components/segment-toggle.tsx b/blotztask-mobile/src/feature/task-add-edit/components/segment-toggle.tsx index b0116d4b4..c22a48a6c 100644 --- a/blotztask-mobile/src/feature/task-add-edit/components/segment-toggle.tsx +++ b/blotztask-mobile/src/feature/task-add-edit/components/segment-toggle.tsx @@ -14,12 +14,12 @@ const AnimatedPressable = Animated.createAnimatedComponent(Pressable); export function SegmentToggle({ value, setValue }: Props) { const { t } = useTranslation("tasks"); - const tabPositionX = useSharedValue(value === "reminder" ? 0 : 224 / 2); + const tabPositionX = useSharedValue(value === SegmentButtonValue.Reminder ? 0 : 224 / 2); const [containerWidth, setContainerWidth] = React.useState(224); const isInitialMount = React.useRef(true); React.useEffect(() => { if (containerWidth > 0) { - onTabMovingAnimation(value === "reminder" ? 0 : 1, !isInitialMount.current); + onTabMovingAnimation(value === SegmentButtonValue.Reminder ? 0 : 1, !isInitialMount.current); isInitialMount.current = false; } }, [value, containerWidth]); @@ -47,13 +47,13 @@ export function SegmentToggle({ value, setValue }: Props) { { - setValue("reminder"); + setValue(SegmentButtonValue.Reminder); onTabMovingAnimation(0); }} > {t("form.reminder")} @@ -64,13 +64,13 @@ export function SegmentToggle({ value, setValue }: Props) { { - setValue("event"); + setValue(SegmentButtonValue.Event); onTabMovingAnimation(1); }} > {t("form.event")} diff --git a/blotztask-mobile/src/feature/task-add-edit/models/segment-button-value.ts b/blotztask-mobile/src/feature/task-add-edit/models/segment-button-value.ts index a2d3cf872..7c6a63e05 100644 --- a/blotztask-mobile/src/feature/task-add-edit/models/segment-button-value.ts +++ b/blotztask-mobile/src/feature/task-add-edit/models/segment-button-value.ts @@ -1 +1,4 @@ -export type SegmentButtonValue = "reminder" | "event"; +export enum SegmentButtonValue { + Reminder = "reminder", + Event = "event", +} diff --git a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx index 973b72a6c..d3258bd85 100644 --- a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx +++ b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx @@ -61,7 +61,7 @@ const TaskForm = ({ mode, dto, onSubmit }: TaskFormProps) => { // Handlers const handleFormSubmit = async (data: TaskFormField) => { - const isReminderTab = isActiveTab === "reminder"; + const isReminderTab = isActiveTab === SegmentButtonValue.Reminder; const { startTime, endTime, timeType } = buildTaskTimePayload( data.startDate, data.startTime, @@ -103,7 +103,7 @@ const TaskForm = ({ mode, dto, onSubmit }: TaskFormProps) => { setIsActiveTab(next); clearErrors(["endDate", "endTime"]); - if (next === "reminder") { + if (next === SegmentButtonValue.Reminder) { setValue("endDate", startDate, { shouldValidate: false }); setValue("endTime", startTime, { shouldValidate: false }); clearErrors(["endDate", "endTime"]); @@ -160,15 +160,15 @@ const TaskForm = ({ mode, dto, onSubmit }: TaskFormProps) => { - {isActiveTab === "event" && formState.errors.endTime && ( + {isActiveTab === SegmentButtonValue.Event && formState.errors.endTime && ( {t(formState.errors.endTime.message || "")} )} - {isActiveTab === "reminder" && ( + {isActiveTab === SegmentButtonValue.Reminder && ( )} - {isActiveTab === "event" && ( + {isActiveTab === SegmentButtonValue.Event && ( Date: Wed, 29 Apr 2026 22:42:59 +1000 Subject: [PATCH 11/19] remove var --- blotztask-mobile/src/feature/task-add-edit/task-form.tsx | 1 - .../src/feature/task-add-edit/util/get-task-form-defaults.ts | 2 -- 2 files changed, 3 deletions(-) diff --git a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx index d3258bd85..fd9240cf0 100644 --- a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx +++ b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx @@ -38,7 +38,6 @@ const TaskForm = ({ mode, dto, onSubmit }: TaskFormProps) => { // Derived values const { initialTab, defaultValues } = getTaskFormDefaults({ - mode, dto, upcomingNotification: userPreferences?.upcomingNotification, }); diff --git a/blotztask-mobile/src/feature/task-add-edit/util/get-task-form-defaults.ts b/blotztask-mobile/src/feature/task-add-edit/util/get-task-form-defaults.ts index 1cb022cff..de3b80729 100644 --- a/blotztask-mobile/src/feature/task-add-edit/util/get-task-form-defaults.ts +++ b/blotztask-mobile/src/feature/task-add-edit/util/get-task-form-defaults.ts @@ -4,7 +4,6 @@ import { SegmentButtonValue } from "../models/segment-button-value"; import { calculateAlertSeconds } from "./time-convertion"; type GetTaskFormDefaultsParams = { - mode: "create" | "edit"; dto?: TaskUpsertDTO; upcomingNotification?: boolean; }; @@ -15,7 +14,6 @@ type TaskFormDefaults = { }; export const getTaskFormDefaults = ({ - mode, dto, upcomingNotification, }: GetTaskFormDefaultsParams): TaskFormDefaults => { From bff46891f73cdcd525300f3b719ae0228009a991 Mon Sep 17 00:00:00 2001 From: nicole-nxn Date: Thu, 30 Apr 2026 10:02:11 +1000 Subject: [PATCH 12/19] fix form --- .../task-add-edit/models/task-form-schema.ts | 4 +- .../src/feature/task-add-edit/task-form.tsx | 15 +++-- .../util/get-task-form-defaults.ts | 64 ++++++++++++------- .../src/shared/models/base-task-dto.ts | 6 +- 4 files changed, 55 insertions(+), 34 deletions(-) diff --git a/blotztask-mobile/src/feature/task-add-edit/models/task-form-schema.ts b/blotztask-mobile/src/feature/task-add-edit/models/task-form-schema.ts index ae9f28661..86245c358 100644 --- a/blotztask-mobile/src/feature/task-add-edit/models/task-form-schema.ts +++ b/blotztask-mobile/src/feature/task-add-edit/models/task-form-schema.ts @@ -13,8 +13,8 @@ export const taskFormSchema = z labelId: z.number().nullable(), alert: z.number().nullable(), isDeadline: z.boolean(), - deadlineDate: z.date(), - deadlineTime: z.date(), + deadlineDate: z.date().nullable(), + deadlineTime: z.date().nullable(), }) .refine( (data) => { diff --git a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx index fd9240cf0..cc51cdb73 100644 --- a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx +++ b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx @@ -25,6 +25,7 @@ import { useTranslation } from "react-i18next"; import Animated from "react-native-reanimated"; import { MotionAnimations } from "@/shared/constants/animations/motion"; import { theme } from "@/shared/constants/theme"; +import { addHours } from "date-fns"; export type TaskFormProps = { onSubmit: (data: TaskUpsertDTO) => void; @@ -39,7 +40,7 @@ const TaskForm = ({ mode, dto, onSubmit }: TaskFormProps) => { // Derived values const { initialTab, defaultValues } = getTaskFormDefaults({ dto, - upcomingNotification: userPreferences?.upcomingNotification, + allowNotification: userPreferences?.upcomingNotification, }); const [isActiveTab, setIsActiveTab] = useState(initialTab); @@ -109,12 +110,12 @@ const TaskForm = ({ mode, dto, onSubmit }: TaskFormProps) => { return; } - const start = new Date(); - const oneHourLater = new Date(start.getTime() + 3600000); - setValue("startDate", start); - setValue("startTime", start); - setValue("endDate", oneHourLater); - setValue("endTime", oneHourLater); + const oneHourLater = addHours(new Date(), 1); + const twoHoursLater = addHours(new Date(), 2); + setValue("startDate", oneHourLater); + setValue("startTime", oneHourLater); + setValue("endDate", twoHoursLater); + setValue("endTime", twoHoursLater); trigger("endTime"); }; diff --git a/blotztask-mobile/src/feature/task-add-edit/util/get-task-form-defaults.ts b/blotztask-mobile/src/feature/task-add-edit/util/get-task-form-defaults.ts index de3b80729..a6d5fc466 100644 --- a/blotztask-mobile/src/feature/task-add-edit/util/get-task-form-defaults.ts +++ b/blotztask-mobile/src/feature/task-add-edit/util/get-task-form-defaults.ts @@ -1,11 +1,13 @@ +import { addHours } from "date-fns"; import type { TaskUpsertDTO } from "@/shared/models/task-upsert-dto"; -import type { TaskFormField } from "../models/task-form-schema"; import { SegmentButtonValue } from "../models/segment-button-value"; +import type { TaskFormField } from "../models/task-form-schema"; import { calculateAlertSeconds } from "./time-convertion"; +import { TaskTimeType } from "@/shared/models/base-task-dto"; type GetTaskFormDefaultsParams = { dto?: TaskUpsertDTO; - upcomingNotification?: boolean; + allowNotification?: boolean; }; type TaskFormDefaults = { @@ -15,33 +17,51 @@ type TaskFormDefaults = { export const getTaskFormDefaults = ({ dto, - upcomingNotification, + allowNotification, }: GetTaskFormDefaultsParams): TaskFormDefaults => { const now = new Date(); - const oneHourLater = new Date(now.getTime() + 3600000); - const dtoStartTime = dto?.startTime ? new Date(dto.startTime) : now; - const dtoEndTime = dto?.endTime ? new Date(dto.endTime) : oneHourLater; - const dueAt = dto?.dueAt ? new Date(dto.dueAt) : null; + const oneHourLater = addHours(now, 1); + const twoHoursLater = addHours(now, 2); + + if (!dto) { + return { + initialTab: SegmentButtonValue.Reminder, + defaultValues: { + title: "", + description: "", + labelId: null, + startDate: oneHourLater, + startTime: oneHourLater, + endDate: twoHoursLater, + endTime: twoHoursLater, + alert: allowNotification ? 300 : null, + isDeadline: false, + deadlineDate: null, + deadlineTime: null, + }, + }; + } + + const dueAt = dto.dueAt ? new Date(dto.dueAt) : null; + const isEvent = dto.timeType === TaskTimeType.Range; - const hasEventTimes = dto?.timeType === 1; - const initialTab = hasEventTimes ? SegmentButtonValue.Event : SegmentButtonValue.Reminder; - const initialAlert = calculateAlertSeconds(dto?.startTime, dto?.alertTime); - const defaultAlert = initialAlert ?? (upcomingNotification ? 300 : null); + const initialAlert = calculateAlertSeconds(dto.startTime, dto.alertTime); + const defaultAlert = initialAlert ?? (allowNotification ? 300 : null); return { - initialTab, + initialTab: isEvent ? SegmentButtonValue.Event : SegmentButtonValue.Reminder, defaultValues: { - title: dto?.title ?? "", - description: dto?.description ?? "", - labelId: dto?.labelId ?? null, - startDate: dtoStartTime, - startTime: dtoStartTime, - endDate: initialTab === SegmentButtonValue.Reminder ? dtoStartTime : dtoEndTime, - endTime: initialTab === SegmentButtonValue.Reminder ? dtoStartTime : dtoEndTime, + title: dto.title, + description: dto.description ?? "", + labelId: dto.labelId ?? null, + startDate: new Date(dto.startTime), + startTime: new Date(dto.startTime), + endDate: new Date(dto.endTime), + endTime: new Date(dto.endTime), alert: defaultAlert, - isDeadline: dto?.isDeadline ?? !!dueAt, - deadlineDate: dueAt ?? oneHourLater, - deadlineTime: dueAt ?? oneHourLater, + isDeadline: dto.isDeadline, + deadlineDate: dueAt, + deadlineTime: dueAt, }, }; }; diff --git a/blotztask-mobile/src/shared/models/base-task-dto.ts b/blotztask-mobile/src/shared/models/base-task-dto.ts index 434d3a159..d28c053d7 100644 --- a/blotztask-mobile/src/shared/models/base-task-dto.ts +++ b/blotztask-mobile/src/shared/models/base-task-dto.ts @@ -1,6 +1,6 @@ export enum TaskTimeType { - Single = 0, - Range = 1, + Single = "SingleTime", + Range = "RangeTime", } export interface BaseTaskDTO { @@ -8,7 +8,7 @@ export interface BaseTaskDTO { startTime: string; endTime: string; description?: string; - timeType: TaskTimeType | null; + timeType: TaskTimeType; notificationId: string | null; alertTime?: string; isDeadline: boolean; From 58728463f883f370cb38f49f79c202fc803c5f92 Mon Sep 17 00:00:00 2001 From: nicole-nxn Date: Thu, 30 Apr 2026 10:15:14 +1000 Subject: [PATCH 13/19] update set --- .../src/feature/task-add-edit/components/event-tab.tsx | 2 -- blotztask-mobile/src/feature/task-add-edit/task-form.tsx | 7 +------ 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/blotztask-mobile/src/feature/task-add-edit/components/event-tab.tsx b/blotztask-mobile/src/feature/task-add-edit/components/event-tab.tsx index 7fa023a8e..62043e7d0 100644 --- a/blotztask-mobile/src/feature/task-add-edit/components/event-tab.tsx +++ b/blotztask-mobile/src/feature/task-add-edit/components/event-tab.tsx @@ -24,12 +24,10 @@ export const EventTab = ({ control, trigger, clearErrors, - setValue, }: { control: Control; trigger?: UseFormTrigger; clearErrors?: UseFormClearErrors; - setValue: (name: keyof TaskFormField, value: any) => void; }) => { const validateRange = (sd: Date, st: Date, ed: Date, et: Date) => { const start = combineDateTime(sd, st); diff --git a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx index cc51cdb73..f7c59a7ac 100644 --- a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx +++ b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx @@ -169,12 +169,7 @@ const TaskForm = ({ mode, dto, onSubmit }: TaskFormProps) => { )} {isActiveTab === SegmentButtonValue.Event && ( - + )} From b4414c5a6321a5183f4a43ad06b848feeca14c07 Mon Sep 17 00:00:00 2001 From: nicole-nxn Date: Thu, 30 Apr 2026 10:20:27 +1000 Subject: [PATCH 14/19] f --- blotztask-mobile/src/feature/task-add-edit/task-form.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx index f7c59a7ac..5c16d3661 100644 --- a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx +++ b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx @@ -116,7 +116,6 @@ const TaskForm = ({ mode, dto, onSubmit }: TaskFormProps) => { setValue("startTime", oneHourLater); setValue("endDate", twoHoursLater); setValue("endTime", twoHoursLater); - trigger("endTime"); }; return ( From 697571f16260779c67d2017d5fdac2bb505193d6 Mon Sep 17 00:00:00 2001 From: nicole-nxn Date: Thu, 30 Apr 2026 10:23:05 +1000 Subject: [PATCH 15/19] fix form --- .../task-add-edit/components/event-tab.tsx | 56 +++++-------------- 1 file changed, 15 insertions(+), 41 deletions(-) diff --git a/blotztask-mobile/src/feature/task-add-edit/components/event-tab.tsx b/blotztask-mobile/src/feature/task-add-edit/components/event-tab.tsx index 62043e7d0..f1b223bbe 100644 --- a/blotztask-mobile/src/feature/task-add-edit/components/event-tab.tsx +++ b/blotztask-mobile/src/feature/task-add-edit/components/event-tab.tsx @@ -11,7 +11,13 @@ import { isEqual, } from "date-fns"; import { zhCN, enUS } from "date-fns/locale"; -import { Control, UseFormClearErrors, UseFormTrigger, useController } from "react-hook-form"; +import { + Control, + FieldPath, + UseFormClearErrors, + UseFormTrigger, + useController, +} from "react-hook-form"; import { TaskFormField } from "../models/task-form-schema"; import TimePicker from "./time-picker"; import DoubleDatesCalendar from "./double-dates-calendar"; @@ -46,47 +52,15 @@ export const EventTab = ({ "startDate" | "startTime" | "endDate" | "endTime" | null >(null); - const { - field: { value: startDateValue, onChange: startDateOnChange }, - } = useController({ - control, - name: "startDate", - }); - - const { - field: { value: startTimeValue, onChange: startTimeOnChange }, - } = useController({ - control, - name: "startTime", - }); - - const { - field: { value: endDateValue, onChange: endDateOnChange }, - } = useController({ - control, - name: "endDate", - }); - - const { - field: { value: endTimeValue, onChange: endTimeOnChange }, - } = useController({ - control, - name: "endTime", - }); - - const { - field: { value: deadlineDate }, - } = useController({ - control, - name: "deadlineDate", - }); + const useField = >(name: T) => + useController({ control, name }).field; - const { - field: { value: isDdl }, - } = useController({ - control, - name: "isDeadline", - }); + const { value: startDateValue, onChange: startDateOnChange } = useField("startDate"); + const { value: startTimeValue, onChange: startTimeOnChange } = useField("startTime"); + const { value: endDateValue, onChange: endDateOnChange } = useField("endDate"); + const { value: endTimeValue, onChange: endTimeOnChange } = useField("endTime"); + const { value: deadlineDate } = useField("deadlineDate"); + const { value: isDdl } = useField("isDeadline"); const ddlStr = isDdl && deadlineDate ? format(deadlineDate, "yyyy-MM-dd") : undefined; From 1f6dcb19c7d98579a84c616a98f0c465cbe4d20e Mon Sep 17 00:00:00 2001 From: nicole-nxn Date: Thu, 30 Apr 2026 10:28:19 +1000 Subject: [PATCH 16/19] update function --- .../task-add-edit/components/event-tab.tsx | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/blotztask-mobile/src/feature/task-add-edit/components/event-tab.tsx b/blotztask-mobile/src/feature/task-add-edit/components/event-tab.tsx index f1b223bbe..ae1d1a671 100644 --- a/blotztask-mobile/src/feature/task-add-edit/components/event-tab.tsx +++ b/blotztask-mobile/src/feature/task-add-edit/components/event-tab.tsx @@ -65,22 +65,17 @@ export const EventTab = ({ const ddlStr = isDdl && deadlineDate ? format(deadlineDate, "yyyy-MM-dd") : undefined; const handleStartDateChange = (nextDate: Date) => { - const previousSpan = - startDateValue && endDateValue - ? Math.max(differenceInCalendarDays(endDateValue, startDateValue), 0) - : 0; - startDateOnChange(nextDate); - const nextEndDate = - endDateValue && isAfter(nextDate, endDateValue) - ? addDays(nextDate, previousSpan) - : endDateValue; if (endDateValue && isAfter(nextDate, endDateValue)) { + const previousSpan = + startDateValue ? Math.max(differenceInCalendarDays(endDateValue, startDateValue), 0) : 0; + const nextEndDate = addDays(nextDate, previousSpan); endDateOnChange(nextEndDate); + validateRange(nextDate, startTimeValue, nextEndDate, endTimeValue); + } else { + validateRange(nextDate, startTimeValue, endDateValue, endTimeValue); } - - validateRange(nextDate, startTimeValue, nextEndDate, endTimeValue); }; const isDateInvalid = From 04aacec432cfc2868bc4e619155c710a753694a6 Mon Sep 17 00:00:00 2001 From: nicole-nxn Date: Thu, 30 Apr 2026 10:34:40 +1000 Subject: [PATCH 17/19] update event --- .../task-add-edit/components/event-tab.tsx | 44 +++---------------- .../src/feature/task-add-edit/task-form.tsx | 4 +- 2 files changed, 7 insertions(+), 41 deletions(-) diff --git a/blotztask-mobile/src/feature/task-add-edit/components/event-tab.tsx b/blotztask-mobile/src/feature/task-add-edit/components/event-tab.tsx index ae1d1a671..005b7b7dc 100644 --- a/blotztask-mobile/src/feature/task-add-edit/components/event-tab.tsx +++ b/blotztask-mobile/src/feature/task-add-edit/components/event-tab.tsx @@ -8,42 +8,17 @@ import { isAfter, isSameDay, isBefore, - isEqual, } from "date-fns"; import { zhCN, enUS } from "date-fns/locale"; -import { - Control, - FieldPath, - UseFormClearErrors, - UseFormTrigger, - useController, -} from "react-hook-form"; +import { Control, FieldPath, useController } from "react-hook-form"; import { TaskFormField } from "../models/task-form-schema"; import TimePicker from "./time-picker"; import DoubleDatesCalendar from "./double-dates-calendar"; import { useTranslation } from "react-i18next"; import Animated from "react-native-reanimated"; import { MotionAnimations } from "@/shared/constants/animations/motion"; -import { combineDateTime } from "../util/combine-date-time"; - -export const EventTab = ({ - control, - trigger, - clearErrors, -}: { - control: Control; - trigger?: UseFormTrigger; - clearErrors?: UseFormClearErrors; -}) => { - const validateRange = (sd: Date, st: Date, ed: Date, et: Date) => { - const start = combineDateTime(sd, st); - const end = combineDateTime(ed, et); - if (start && end && (isBefore(start, end) || isEqual(start, end))) { - clearErrors?.("endTime"); - } else { - trigger?.("endTime"); - } - }; + +export const EventTab = ({ control }: { control: Control }) => { const { t, i18n } = useTranslation("tasks"); const isChinese = i18n.language === "zh"; const locale = isChinese ? zhCN : enUS; @@ -72,9 +47,6 @@ export const EventTab = ({ startDateValue ? Math.max(differenceInCalendarDays(endDateValue, startDateValue), 0) : 0; const nextEndDate = addDays(nextDate, previousSpan); endDateOnChange(nextEndDate); - validateRange(nextDate, startTimeValue, nextEndDate, endTimeValue); - } else { - validateRange(nextDate, startTimeValue, endDateValue, endTimeValue); } }; @@ -202,10 +174,7 @@ export const EventTab = ({ startDate={startDateValue} endDate={endDateValue} deadlineDate={ddlStr} - setEndDate={(v: Date) => { - endDateOnChange(v); - validateRange(startDateValue, startTimeValue, v, endTimeValue); - }} + setEndDate={(v: Date) => endDateOnChange(v)} current={format( activeSelector === "endDate" ? (endDateValue ?? startDateValue ?? new Date()) @@ -222,10 +191,7 @@ export const EventTab = ({ > { - endTimeOnChange(v); - validateRange(startDateValue, startTimeValue, endDateValue, v); - }} + onChange={(v: Date) => endTimeOnChange(v)} /> )} diff --git a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx index 5c16d3661..847af871a 100644 --- a/blotztask-mobile/src/feature/task-add-edit/task-form.tsx +++ b/blotztask-mobile/src/feature/task-add-edit/task-form.tsx @@ -52,7 +52,7 @@ const TaskForm = ({ mode, dto, onSubmit }: TaskFormProps) => { defaultValues, }); - const { handleSubmit, formState, control, setValue, clearErrors, trigger, getValues } = form; + const { handleSubmit, formState, control, setValue, clearErrors, getValues } = form; const { isSubmitting } = formState; if (isUserPreferencesLoading) { @@ -168,7 +168,7 @@ const TaskForm = ({ mode, dto, onSubmit }: TaskFormProps) => { )} {isActiveTab === SegmentButtonValue.Event && ( - + )} From dca1968a93c886883999fca38b15bed995c7f4da Mon Sep 17 00:00:00 2001 From: nicole-nxn Date: Thu, 30 Apr 2026 11:57:07 +1000 Subject: [PATCH 18/19] fix montly --- .../feature/monthly-calendar/components/day-detail-panel.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blotztask-mobile/src/feature/monthly-calendar/components/day-detail-panel.tsx b/blotztask-mobile/src/feature/monthly-calendar/components/day-detail-panel.tsx index 0024c191b..db21fbb81 100644 --- a/blotztask-mobile/src/feature/monthly-calendar/components/day-detail-panel.tsx +++ b/blotztask-mobile/src/feature/monthly-calendar/components/day-detail-panel.tsx @@ -1,5 +1,5 @@ import { View, Text, ActivityIndicator } from "react-native"; -import { BottomSheetScrollView } from "@gorhom/bottom-sheet"; +import { BottomSheetScrollView } from "@gorhom/bottom-sheet"; import { format, parseISO } from "date-fns"; import useSelectedDayTasks from "@/shared/hooks/useSelectedDayTasks"; import { theme } from "@/shared/constants/theme"; @@ -59,7 +59,7 @@ export const SelectedDayDetailPanel = ({ selectedDay }: { selectedDay: Date }) = {/* Column 3: Title */} - + {task.title} From bcb69088c85838d3464a5e335b9321328ccd7509 Mon Sep 17 00:00:00 2001 From: nicole-nxn Date: Fri, 1 May 2026 23:22:33 +1000 Subject: [PATCH 19/19] comment --- .../src/feature/task-add-edit/util/get-task-form-defaults.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/blotztask-mobile/src/feature/task-add-edit/util/get-task-form-defaults.ts b/blotztask-mobile/src/feature/task-add-edit/util/get-task-form-defaults.ts index a6d5fc466..77fb6f7af 100644 --- a/blotztask-mobile/src/feature/task-add-edit/util/get-task-form-defaults.ts +++ b/blotztask-mobile/src/feature/task-add-edit/util/get-task-form-defaults.ts @@ -23,6 +23,7 @@ export const getTaskFormDefaults = ({ const oneHourLater = addHours(now, 1); const twoHoursLater = addHours(now, 2); + // default values for creating a new task if (!dto) { return { initialTab: SegmentButtonValue.Reminder, @@ -47,7 +48,7 @@ export const getTaskFormDefaults = ({ const initialAlert = calculateAlertSeconds(dto.startTime, dto.alertTime); const defaultAlert = initialAlert ?? (allowNotification ? 300 : null); - + // default values for editing an existing task return { initialTab: isEvent ? SegmentButtonValue.Event : SegmentButtonValue.Reminder, defaultValues: {