-
(onProjectChange ? onProjectChange(projectId) : onChange({ projectId }))}
- placeholder={t.timesheet?.projectLabel || "Project"}
- portalOwnerId={portalOwnerId}
- className="min-w-0 max-w-fit flex-1"
- />
+ (onProjectChange ? onProjectChange(projectId) : onChange({ projectId }))}
+ placeholder={t.timesheet?.projectLabel || "Project"}
+ searchPlaceholder={t.timesheet?.searchProjectsLabel || t.projects?.searchPlaceholder || "Search projects..."}
+ emptyLabel={t.timesheet?.noProjectsFoundLabel || "No projects found."}
+ portalOwnerId={portalOwnerId}
+ className="min-w-0 max-w-fit flex-1"
+ />
{selectedProject?.client?.name && (
@@ -1307,21 +1343,28 @@ function EntryEditorFields({
/>
-
-
-
+
+
+ onChange({ projectId: String(value) })}
+ options={[
+ { value: "", label: t.timesheet?.noProject || "No project" },
+ ...projects.map((project) => ({
+ value: project.id,
+ label: project.name,
+ searchText: project.client?.name || "",
+ })),
+ ]}
+ placeholder={t.timesheet?.noProject || "No project"}
+ searchPlaceholder={t.timesheet?.searchProjectsLabel || t.projects?.searchPlaceholder || "Search projects..."}
+ emptyLabel={t.timesheet?.noProjectsFoundLabel || "No projects found."}
+ className="w-full"
+ buttonClassName={compact ? "w-full h-9 px-2 text-xs" : "w-full"}
+ />
+
@@ -1893,23 +1936,29 @@ export default function Timesheet() {
hideFiltersLabel?: string;
applyFiltersLabel?: string;
clientFilterPrefix?: string;
- projectFilterPrefix?: string;
- tagFilterPrefix?: string;
- fromFilterPrefix?: string;
- toFilterPrefix?: string;
- restartConfirmMessage?: string;
- deletedProjectLabel?: string;
- deletedTagLabel?: string;
- }) || {};
+ projectFilterPrefix?: string;
+ tagFilterPrefix?: string;
+ fromFilterPrefix?: string;
+ toFilterPrefix?: string;
+ restartConfirmMessage?: string;
+ discardConfirmMessage?: string;
+ deletedProjectLabel?: string;
+ deletedTagLabel?: string;
+ searchTagsLabel?: string;
+ noTagsFoundLabel?: string;
+ searchProjectsLabel?: string;
+ noProjectsFoundLabel?: string;
+ }) || {};
const [projects, setProjects] = useState
([]);
const [tags, setTags] = useState([]);
- const [groupedHistory, setGroupedHistory] = useState([]);
- const [activeRunningEntry, setActiveRunningEntry] = useState(null);
- const [isLoading, setIsLoading] = useState(false);
- const [isLoadingMore, setIsLoadingMore] = useState(false);
- const [searchQuery, setSearchQuery] = useState("");
- const [filters, setFilters] = useState(DEFAULT_ENTRY_FILTERS);
+ const [groupedHistory, setGroupedHistory] = useState([]);
+ const [activeRunningEntry, setActiveRunningEntry] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+ const [isLoadingMore, setIsLoadingMore] = useState(false);
+ const [searchQuery, setSearchQuery] = useState("");
+ const [debouncedSearchQuery, setDebouncedSearchQuery] = useState("");
+ const [filters, setFilters] = useState(DEFAULT_ENTRY_FILTERS);
const [hasMoreHistory, setHasMoreHistory] = useState(false);
const [nextOffset, setNextOffset] = useState(0);
const [limit] = useState(20);
@@ -1977,35 +2026,44 @@ export default function Timesheet() {
const loadOptions = async () => {
try {
- const [projectsData, tagsData] = await Promise.all([
- getProjects(activeWorkspace.id, { limit: 100, offset: 0, ordering: "name" }),
- getTags(activeWorkspace.id, { limit: 100, offset: 0, ordering: "name" }),
- ]);
-
- setProjects(projectsData.results || []);
- setTags(tagsData.results || []);
- } catch (error) {
- console.error(error);
- toast.error(t.timesheet?.optionsError || "Failed to load projects and tags");
- }
+ const [projectsData, tagsData] = await Promise.all([
+ getProjects(activeWorkspace.id, { limit: 100, offset: 0, ordering: "name" }),
+ getTags(activeWorkspace.id, { limit: 100, offset: 0, ordering: "name" }),
+ ]);
+
+ setProjects((projectsData.results || []).filter((project: Project) => !project.is_archived));
+ setTags(tagsData.results || []);
+ } catch (error) {
+ console.error(error);
+ toast.error(t.timesheet?.optionsError || "Failed to load projects and tags");
+ }
};
void loadOptions();
}, [activeWorkspace?.id, t.timesheet?.optionsError]);
- useEffect(() => {
- setSearchQuery("");
- setFilters(DEFAULT_ENTRY_FILTERS);
- setGroupedHistory([]);
- setNextOffset(0);
- setHasMoreHistory(false);
- }, [activeWorkspace?.id]);
-
- useEffect(() => {
- setGroupedHistory([]);
- setNextOffset(0);
- setHasMoreHistory(false);
- }, [filters, searchQuery]);
+ useEffect(() => {
+ setSearchQuery("");
+ setDebouncedSearchQuery("");
+ setFilters(DEFAULT_ENTRY_FILTERS);
+ setGroupedHistory([]);
+ setNextOffset(0);
+ setHasMoreHistory(false);
+ }, [activeWorkspace?.id]);
+
+ useEffect(() => {
+ const timeoutId = window.setTimeout(() => {
+ setDebouncedSearchQuery(searchQuery);
+ }, 350);
+
+ return () => window.clearTimeout(timeoutId);
+ }, [searchQuery]);
+
+ useEffect(() => {
+ setGroupedHistory([]);
+ setNextOffset(0);
+ setHasMoreHistory(false);
+ }, [debouncedSearchQuery, filters]);
useEffect(() => {
if (!filters.clientId || !filters.projectId) return;
@@ -2028,11 +2086,11 @@ export default function Timesheet() {
} else {
setIsLoading(true);
}
- const params: TimeEntryListParams = {
- limit,
- offset,
- search: searchQuery,
- status: "ended",
+ const params: TimeEntryListParams = {
+ limit,
+ offset,
+ search: debouncedSearchQuery,
+ status: "ended",
project: filters.projectId || undefined,
client: filters.clientId || undefined,
tags: filters.tagIds,
@@ -2053,7 +2111,7 @@ export default function Timesheet() {
setIsLoading(false);
}
}
- }, [activeWorkspace?.id, filters, limit, searchQuery, t.timesheet?.fetchError]);
+ }, [activeWorkspace?.id, debouncedSearchQuery, filters, limit, t.timesheet?.fetchError]);
const loadRunningEntry = useCallback(async () => {
if (!activeWorkspace?.id) {
@@ -2074,15 +2132,15 @@ export default function Timesheet() {
}
}, [activeWorkspace?.id]);
- useEffect(() => {
- if (!activeWorkspace?.id) return;
-
- const timeoutId = window.setTimeout(() => {
- void loadHistory();
- }, 250);
-
- return () => window.clearTimeout(timeoutId);
- }, [activeWorkspace?.id, limit, loadHistory, filters, searchQuery]);
+ useEffect(() => {
+ if (!activeWorkspace?.id) return;
+
+ const timeoutId = window.setTimeout(() => {
+ void loadHistory();
+ }, 250);
+
+ return () => window.clearTimeout(timeoutId);
+ }, [activeWorkspace?.id, debouncedSearchQuery, limit, loadHistory, filters]);
useEffect(() => {
void loadRunningEntry();
@@ -2373,15 +2431,15 @@ export default function Timesheet() {
}
}, []);
- const handleApplyFilters = useCallback((nextSearchQuery: string, nextFilters: TimeEntryFilters) => {
- setSearchQuery(nextSearchQuery);
- setFilters(nextFilters);
- }, []);
-
- const handleClearFilters = useCallback(() => {
- setSearchQuery("");
- setFilters(DEFAULT_ENTRY_FILTERS);
- }, []);
+ const handleApplyFilters = useCallback((nextFilters: TimeEntryFilters) => {
+ setFilters(nextFilters);
+ }, []);
+
+ const handleClearFilters = useCallback(() => {
+ setSearchQuery("");
+ setDebouncedSearchQuery("");
+ setFilters(DEFAULT_ENTRY_FILTERS);
+ }, []);
const handleLoadMore = useCallback(() => {
if (!hasMoreHistory || nextOffset === null || isLoadingMore) return;
@@ -2439,20 +2497,26 @@ export default function Timesheet() {
/>
-
-
+
+ setTimerDraft((current) => ({ ...current, projectId: String(value) }))}
+ options={[
+ { value: "", label: t.timesheet?.projectLabel || "Project" },
+ ...runningTimerProjects.map((project) => ({
+ value: project.id,
+ label: project.name,
+ searchText: project.client?.name || "",
+ })),
+ ]}
+ placeholder={t.timesheet?.projectLabel || "Project"}
+ searchPlaceholder={extendedTimesheet.searchProjectsLabel || t.projects?.searchPlaceholder || "Search projects..."}
+ emptyLabel={extendedTimesheet.noProjectsFoundLabel || "No projects found."}
+ className="min-w-[190px] max-w-[220px]"
+ buttonClassName="h-12 w-full rounded-none border-0 bg-transparent px-3 text-sm text-sky-600 shadow-none outline-none dark:bg-transparent dark:text-sky-400 focus:ring-0 focus-visible:ring-0 focus-visible:ring-offset-0"
+ disabled={isStartingTimer}
+ />
+
-
-
@@ -2760,10 +2833,10 @@ export default function Timesheet() {
{deleteModal.entry && (
-
@@ -2777,9 +2850,9 @@ export default function Timesheet() {
}
>
-
- {extendedTimesheet.deleteConfirmMessage || "Are you sure you want to delete this time entry?"}
-
+
+ {extendedTimesheet.deleteConfirmMessage || t.timesheet?.deleteConfirmMessage || "Are you sure you want to delete this time entry?"}
+
{deleteModal.entry.description || t.timesheet?.emptyDescription || "No description"}
@@ -2811,9 +2884,9 @@ export default function Timesheet() {
}
>
-
- {(extendedTimesheet.restartConfirmMessage || "Start a new running timer from this entry?")}
-
+
+ {extendedTimesheet.restartConfirmMessage || t.timesheet?.restartConfirmMessage || "Start a new running timer from this entry?"}
+
{restartModal.entry.description || t.timesheet?.emptyDescription || "No description"}
@@ -2845,9 +2918,9 @@ export default function Timesheet() {
}
>
-
- {extendedTimesheet.deleteConfirmMessage || "Are you sure you want to delete this time entry?"}
-
+
+ {extendedTimesheet.discardConfirmMessage || t.timesheet?.discardConfirmMessage || "Are you sure you want to discard this running timer?"}
+
{discardTimerModal.entry.description || t.timesheet?.emptyDescription || "No description"}