From eb41c8528d34396f7a47d3c4ee7b2e0adaf7f2ee Mon Sep 17 00:00:00 2001 From: Amirhossein Khalili Date: Sun, 24 May 2026 11:17:55 +0330 Subject: [PATCH] refactor(auth): replace escaped persian digits --- src/pages/Timesheet.tsx | 186 ++++++++++++++++++++-------------------- src/pages/auth/utils.ts | 2 +- 2 files changed, 94 insertions(+), 94 deletions(-) diff --git a/src/pages/Timesheet.tsx b/src/pages/Timesheet.tsx index b8d2634..6cdf8fd 100644 --- a/src/pages/Timesheet.tsx +++ b/src/pages/Timesheet.tsx @@ -1,12 +1,12 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { useSearchParams } from "react-router-dom"; -import { Banknote, CalendarDays, Check, ChevronDown, Clock3, DollarSign, MoreVertical, Pencil, Play, Plus, Search, Square, Tag as TagIcon, Trash2 } from "lucide-react"; +import { Banknote, CalendarDays, Check, ChevronDown, Clock3, DollarSign, MoreVertical, Pencil, Play, Plus, Search, Square, Tag as TagIcon, Trash2 } from "lucide-react"; import { toast } from "sonner"; -import { getProjects, type Project } from "../api/projects"; -import { getMyWorkspaceRates, type MyWorkspaceRatesResponse } from "../api/rates"; -import { +import { getProjects, type Project } from "../api/projects"; +import { getMyWorkspaceRates, type MyWorkspaceRatesResponse } from "../api/rates"; +import { createTimeEntry, deleteTimeEntry, getTimeEntries, @@ -19,10 +19,10 @@ import { } from "../api/timeEntries"; import { getTags, type Tag } from "../api/tags"; import { Modal } from "../components/Modal"; -import EmptyStateCard from "../components/EmptyStateCard"; -import { InfiniteScroll } from "../components/InfiniteScroll"; -import { WorkspaceRatesPanel } from "../components/rates/WorkspaceRatesPanel"; -import TimesheetFilterBar, { type TimeEntryFilters } from "../components/timesheet/TimesheetFilterBar"; +import EmptyStateCard from "../components/EmptyStateCard"; +import { InfiniteScroll } from "../components/InfiniteScroll"; +import { WorkspaceRatesPanel } from "../components/rates/WorkspaceRatesPanel"; +import TimesheetFilterBar, { type TimeEntryFilters } from "../components/timesheet/TimesheetFilterBar"; import JalaliDatePicker from "../components/ui/JalaliDatePicker"; import { Button } from "../components/ui/button"; import { Input } from "../components/ui/input"; @@ -1262,10 +1262,10 @@ function EntryEditorFields({
onChange({ description: event.target.value })} - placeholder={t.timesheet?.descriptionPlaceholder || "What are you working on?"} - className="h-12 w-[200px] 2xl:w-[400px] shrink-0 rounded-none border-0 bg-transparent px-0 pe-3 text-sm shadow-none placeholder:text-slate-300 focus-visible:ring-0 focus-visible:ring-offset-0 truncate dark:bg-transparent dark:text-slate-100 dark:placeholder:text-slate-600" + value={state.description} + onChange={(event) => onChange({ description: event.target.value })} + placeholder={t.timesheet?.descriptionPlaceholder || "What are you working on?"} + className="h-12 w-[200px] 2xl:w-[400px] shrink-0 rounded-none border-0 bg-transparent px-0 pe-3 text-sm shadow-none placeholder:text-slate-300 focus-visible:ring-0 focus-visible:ring-offset-0 truncate dark:bg-transparent dark:text-slate-100 dark:placeholder:text-slate-600" /> @@ -1344,11 +1344,11 @@ function EntryEditorFields({ - onChange({ description: event.target.value })} - placeholder={t.timesheet?.descriptionPlaceholder || "What are you working on?"} - className={compact ? "h-9 px-2 text-xs placeholder:text-slate-300 dark:placeholder:text-slate-600" : "placeholder:text-slate-300 dark:placeholder:text-slate-600"} + onChange({ description: event.target.value })} + placeholder={t.timesheet?.descriptionPlaceholder || "What are you working on?"} + className={compact ? "h-9 px-2 text-xs placeholder:text-slate-300 dark:placeholder:text-slate-600" : "placeholder:text-slate-300 dark:placeholder:text-slate-600"} />
@@ -1837,7 +1837,7 @@ function MobileRecordedEntryCard({

{project && ( - {"\u2022"} + {"•"} {project.isDeleted ? buildDeletedProjectLabel(project.name, deletedProjectLabel) : project.name} @@ -2017,13 +2017,13 @@ function TimesheetSkeleton({ loadingLabel }: { loadingLabel: string }) { ); } -export default function Timesheet() { - const { t, lang } = useTranslation(); - const { activeWorkspace } = useWorkspace(); - const [isRatesPanelOpen, setIsRatesPanelOpen] = useState(false); - const [isRatesPanelLoading, setIsRatesPanelLoading] = useState(false); - const [myRates, setMyRates] = useState(null); - const [searchParams, setSearchParams] = useSearchParams(); +export default function Timesheet() { + const { t, lang } = useTranslation(); + const { activeWorkspace } = useWorkspace(); + const [isRatesPanelOpen, setIsRatesPanelOpen] = useState(false); + const [isRatesPanelLoading, setIsRatesPanelLoading] = useState(false); + const [myRates, setMyRates] = useState(null); + const [searchParams, setSearchParams] = useSearchParams(); const isRtl = lang === "fa"; const extendedTimesheet = (t.timesheet as { deleteTitle?: string; @@ -2163,17 +2163,17 @@ export default function Timesheet() { void loadOptions(); }, [activeWorkspace?.id, t.timesheet?.optionsError]); - useEffect(() => { - setGroupedHistory([]); - setNextOffset(0); - setHasMoreHistory(false); - }, [activeWorkspace?.id]); - - useEffect(() => { - setIsRatesPanelOpen(false); - setIsRatesPanelLoading(false); - setMyRates(null); - }, [activeWorkspace?.id]); + useEffect(() => { + setGroupedHistory([]); + setNextOffset(0); + setHasMoreHistory(false); + }, [activeWorkspace?.id]); + + useEffect(() => { + setIsRatesPanelOpen(false); + setIsRatesPanelLoading(false); + setMyRates(null); + }, [activeWorkspace?.id]); useEffect(() => { const timeoutId = window.setTimeout(() => { @@ -2606,29 +2606,29 @@ export default function Timesheet() { setDebouncedSearchQuery(""); }, [setSearchParams]); - const handleLoadMore = useCallback(() => { - if (!hasMoreHistory || nextOffset === null || isLoadingMore) return; - void loadHistory({ offset: nextOffset, append: true }); - }, [hasMoreHistory, isLoadingMore, loadHistory, nextOffset]); - - const openRatesPanel = useCallback(async () => { - if (!activeWorkspace?.id) return; - - setIsRatesPanelOpen(true); - if (myRates || isRatesPanelLoading) return; - - try { - setIsRatesPanelLoading(true); - const response = await getMyWorkspaceRates(activeWorkspace.id); - setMyRates(response); - } catch (error) { - console.error(error); - toast.error(t.rates?.projectSaveError || "Failed to load rates."); - setIsRatesPanelOpen(false); - } finally { - setIsRatesPanelLoading(false); - } - }, [activeWorkspace?.id, isRatesPanelLoading, myRates, t.rates?.projectSaveError]); + const handleLoadMore = useCallback(() => { + if (!hasMoreHistory || nextOffset === null || isLoadingMore) return; + void loadHistory({ offset: nextOffset, append: true }); + }, [hasMoreHistory, isLoadingMore, loadHistory, nextOffset]); + + const openRatesPanel = useCallback(async () => { + if (!activeWorkspace?.id) return; + + setIsRatesPanelOpen(true); + if (myRates || isRatesPanelLoading) return; + + try { + setIsRatesPanelLoading(true); + const response = await getMyWorkspaceRates(activeWorkspace.id); + setMyRates(response); + } catch (error) { + console.error(error); + toast.error(t.rates?.projectSaveError || "Failed to load rates."); + setIsRatesPanelOpen(false); + } finally { + setIsRatesPanelLoading(false); + } + }, [activeWorkspace?.id, isRatesPanelLoading, myRates, t.rates?.projectSaveError]); const handleDiscardTimerDraft = useCallback(async () => { if (!discardTimerModal.entry || isDiscardingTimer) return; @@ -2658,16 +2658,16 @@ export default function Timesheet() { return (
-
-
-

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

-

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

-
- -
+
+
+

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

+

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

+
+ +
setTimerDraft((current) => ({ ...current, description: event.target.value }))} - disabled={isStartingTimer} - className="h-12 rounded-none border-0 bg-transparent px-5 text-sm shadow-none placeholder:text-slate-300 focus-visible:ring-0 focus-visible:ring-offset-0 dark:bg-transparent dark:placeholder:text-slate-600" + value={timerDraft.description} + placeholder={t.timesheet?.descriptionPlaceholder || "What are you working on?"} + onChange={(event) => setTimerDraft((current) => ({ ...current, description: event.target.value }))} + disabled={isStartingTimer} + className="h-12 rounded-none border-0 bg-transparent px-5 text-sm shadow-none placeholder:text-slate-300 focus-visible:ring-0 focus-visible:ring-offset-0 dark:bg-transparent dark:placeholder:text-slate-600" />
@@ -2785,11 +2785,11 @@ export default function Timesheet() { >
setTimerDraft((current) => ({ ...current, description: event.target.value }))} - disabled={isStartingTimer} - className="h-10 border-slate-200 bg-slate-50 text-sm placeholder:text-slate-300 dark:border-slate-700 dark:bg-slate-900 dark:placeholder:text-slate-600" + value={timerDraft.description} + placeholder={t.timesheet?.descriptionPlaceholder || "What are you working on?"} + onChange={(event) => setTimerDraft((current) => ({ ...current, description: event.target.value }))} + disabled={isStartingTimer} + className="h-10 border-slate-200 bg-slate-50 text-sm placeholder:text-slate-300 dark:border-slate-700 dark:bg-slate-900 dark:placeholder:text-slate-600" />
@@ -3100,8 +3100,8 @@ export default function Timesheet() { )} - {discardTimerModal.entry && ( -
- - )} - - setIsRatesPanelOpen(false)} - /> -
- ); -} + + )} + + setIsRatesPanelOpen(false)} + /> +
+ ); +} diff --git a/src/pages/auth/utils.ts b/src/pages/auth/utils.ts index fa8339d..fae9597 100644 --- a/src/pages/auth/utils.ts +++ b/src/pages/auth/utils.ts @@ -3,7 +3,7 @@ import { toast } from "sonner" import { ApiError } from "../../api/client" import { setSessionTokens } from "../../lib/session" -const PERSIAN_DIGITS = ["\u06f0", "\u06f1", "\u06f2", "\u06f3", "\u06f4", "\u06f5", "\u06f6", "\u06f7", "\u06f8", "\u06f9"] +const PERSIAN_DIGITS = ["۰", "۱", "۲", "۳", "۴", "۵", "۶", "۷", "۸", "۹"] export const localizeDigits = (value: string, isRtl: boolean) => isRtl ? value.replace(/\d/g, (digit) => PERSIAN_DIGITS[Number.parseInt(digit, 10)] ?? digit) : value