From c7ede31b68d25b053321675427301e7a819dd895 Mon Sep 17 00:00:00 2001 From: Amirhossein Khalili Date: Thu, 18 Jun 2026 22:59:04 +0330 Subject: [PATCH] feat(timesheet): add manual time entry action --- src/locales/en.ts | 6 ++- src/locales/fa.ts | 12 +++--- src/pages/Timesheet.tsx | 83 +++++++++++++++++++++++++++-------------- 3 files changed, 65 insertions(+), 36 deletions(-) diff --git a/src/locales/en.ts b/src/locales/en.ts index 5f631d2..634b6af 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -681,7 +681,8 @@ export const en = { title: "Timesheet", description: (workspaceName: string) => `Track time inside ${workspaceName}`, selectWorkspace: "Please select a workspace first.", - addEntry: "Add Entry", + addEntry: "Add Entry", + addManualEntry: "Add manual entry", startTimer: "Start Timer", stopTimer: "Stop Timer", timerRunning: "Timer Running", @@ -695,7 +696,8 @@ export const en = { emptyStateDescription: "Start the timer or add a manual entry to get started.", noEntriesSearch: "Try adjusting your search query or filters.", emptyDescription: "No description", - createTitle: "Add Time Entry", + createTitle: "Add Time Entry", + manualCreateTitle: "Add Manual Time Entry", startTitle: "Start Timer", editTitle: "Edit Time Entry", createSuccess: "Time entry created successfully.", diff --git a/src/locales/fa.ts b/src/locales/fa.ts index 1251bbd..a4f6ca7 100644 --- a/src/locales/fa.ts +++ b/src/locales/fa.ts @@ -677,9 +677,10 @@ export const fa = { timesheet: { title: "تایم‌شیت", description: (workspaceName: string) => `ثبت زمان در ${workspaceName}`, - selectWorkspace: "لطفاً ابتدا یک ورک‌اسپیس انتخاب کنید.", - addEntry: "افزودن ورودی", - startTimer: "شروع تایمر", + selectWorkspace: "لطفاً ابتدا یک ورک‌اسپیس انتخاب کنید.", + addEntry: "افزودن ورودی", + addManualEntry: "افزودن دستی زمان", + startTimer: "شروع تایمر", stopTimer: "توقف تایمر", timerRunning: "تایمر فعال است", runningLabel: "تایمر فعلی", @@ -692,8 +693,9 @@ export const fa = { emptyDescription: "بدون توضیح", emptyStateDescription: "برای شروع، تایمر را اجرا کنید یا یک ورودی دستی اضافه کنید.", noEntriesSearch: "عبارت جست‌وجو یا فیلترهای خود را تغییر دهید.", - createTitle: "افزودن ورودی زمان", - startTitle: "شروع تایمر", + createTitle: "افزودن ورودی زمان", + manualCreateTitle: "افزودن دستی زمان", + startTitle: "شروع تایمر", editTitle: "ویرایش ورودی زمان", createSuccess: "ورودی زمان با موفقیت ایجاد شد.", startSuccess: "تایمر با موفقیت شروع شد.", diff --git a/src/pages/Timesheet.tsx b/src/pages/Timesheet.tsx index 63b4d30..9a9ffd5 100644 --- a/src/pages/Timesheet.tsx +++ b/src/pages/Timesheet.tsx @@ -402,15 +402,21 @@ const updateGroupedHistoryEntry = ( return merged; }; -const buildEntryFormState = (entry?: TimeEntry | null): EntryFormState => { - if (!entry) { - const now = getLocalDateParts(new Date().toISOString()); - return { - ...EMPTY_FORM, - startDate: now.date, - startTime: now.time, - }; - } +const buildEntryFormState = (entry?: TimeEntry | null): EntryFormState => { + if (!entry) { + const endDate = new Date(); + const startDate = new Date(endDate); + startDate.setHours(startDate.getHours() - 1); + const start = getLocalDateParts(startDate.toISOString()); + const end = getLocalDateParts(endDate.toISOString()); + return { + ...EMPTY_FORM, + startDate: start.date, + startTime: start.time, + endDate: end.date, + endTime: end.time, + }; + } const start = getLocalDateParts(entry.start_time); const end = getLocalDateParts(entry.end_time); @@ -459,7 +465,12 @@ const toggleTagId = (currentTags: string[], tagId: string) => const buildPayloadFromState = ( state: EntryFormState, - options: { includeWorkspace: boolean; workspaceId?: string; messages?: Partial> }, + options: { + includeWorkspace: boolean; + workspaceId?: string; + requireEnd?: boolean; + messages?: Partial>; + }, ): { payload?: TimeEntryPayload; error?: string } => { const messages = { startRequired: "Start date and time are required.", @@ -473,9 +484,12 @@ const buildPayloadFromState = ( return { error: messages.startRequired }; } - let endDateTime: string | null = null; - const hasEndValue = Boolean(state.endDate || state.endTime); - if (hasEndValue) { + let endDateTime: string | null = null; + const hasEndValue = Boolean(state.endDate || state.endTime); + if (options.requireEnd && !hasEndValue) { + return { error: messages.endRequired }; + } + if (hasEndValue) { if (!state.endDate || !state.endTime) { return { error: messages.endRequired }; } @@ -2587,6 +2601,7 @@ export default function Timesheet() { const { payload, error } = buildPayloadFromState(formState, { includeWorkspace: modalMode === "manual", workspaceId: activeWorkspace?.id, + requireEnd: modalMode === "manual", messages: entryValidationMessages, }); @@ -2870,11 +2885,17 @@ export default function Timesheet() {

{t.timesheet?.title || 'Timesheets'}

{t.timesheet?.description(activeWorkspace?.name || "-") || 'Manage your Timesheet'}

- - +
+ + +
+
)} - - - - } + + + } >