[WEB-4677] improvement: add defaultOpen property to CustomSearchSelect (#7576)

* [WEB-4677] improvement: add defaultOpen property to CustomSearchSelect

* improvement: add utils to format time duration

* improvement: added initializing state for project store

* improvement: minor changes in automations page
This commit is contained in:
Prateek Shourya 2025-08-12 19:37:53 +05:30 committed by GitHub
parent 5629a4d4b6
commit 34c6047d80
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 74 additions and 7 deletions

View file

@ -15,10 +15,14 @@ import { PageHead } from "@/components/core";
// hooks
import { SettingsContentWrapper, SettingsHeading } from "@/components/settings";
import { useProject, useUserPermissions } from "@/hooks/store";
// plane web imports
import { CustomAutomationsRoot } from "@/plane-web/components/automations/root";
const AutomationSettingsPage = observer(() => {
// router
const { workspaceSlug, projectId } = useParams();
const { workspaceSlug: workspaceSlugParam, projectId: projectIdParam } = useParams();
const workspaceSlug = workspaceSlugParam?.toString();
const projectId = projectIdParam?.toString();
// store hooks
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
const { currentProjectDetails: projectDetails, updateProject } = useProject();
@ -48,7 +52,7 @@ const AutomationSettingsPage = observer(() => {
}
return (
<SettingsContentWrapper>
<SettingsContentWrapper size="lg">
<PageHead title={pageTitle} />
<section className={`w-full ${canPerformProjectAdminActions ? "" : "opacity-60"}`}>
<SettingsHeading
@ -58,6 +62,7 @@ const AutomationSettingsPage = observer(() => {
<AutoArchiveAutomation handleChange={handleChange} />
<AutoCloseAutomation handleChange={handleChange} />
</section>
<CustomAutomationsRoot projectId={projectId} workspaceSlug={workspaceSlug} />
</SettingsContentWrapper>
);
});

View file

@ -0,0 +1,10 @@
"use client";
import React, { FC } from "react";
export type TCustomAutomationsRootProps = {
projectId: string;
workspaceSlug: string;
};
export const CustomAutomationsRoot: FC<TCustomAutomationsRootProps> = () => <></>;

View file

@ -84,7 +84,7 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
handleClose={() => setmonthModal(false)}
handleChange={handleChange}
/>
<div className="flex flex-col gap-4 border-b border-custom-border-200 py-6">
<div className="flex flex-col gap-4 py-6">
<div className="flex items-center justify-between">
<div className="flex items-start gap-3">
<div className="flex items-center justify-center rounded bg-custom-background-90 p-3">

View file

@ -25,6 +25,7 @@ export interface IProjectStore {
projectMap: Record<string, TProject>; // projectId: project info
projectAnalyticsCountMap: Record<string, TProjectAnalyticsCount>; // projectId: project analytics count
// computed
isInitializingProjects: boolean;
filteredProjectIds: string[] | undefined;
workspaceProjectIds: string[] | undefined;
archivedProjectIds: string[] | undefined;
@ -101,6 +102,7 @@ export class ProjectStore implements IProjectStore {
openCollapsibleSection: observable.ref,
lastCollapsibleAction: observable.ref,
// computed
isInitializingProjects: computed,
filteredProjectIds: computed,
workspaceProjectIds: computed,
archivedProjectIds: computed,
@ -138,6 +140,13 @@ export class ProjectStore implements IProjectStore {
this.stateService = new ProjectStateService();
}
/**
* @description returns true if projects are still initializing
*/
get isInitializingProjects() {
return this.loader === "init-loader";
}
/**
* @description returns filtered projects based on filters and search query
*/

View file

@ -6,9 +6,9 @@ import { usePopper } from "react-popper";
// plane imports
import { useOutsideClickDetector } from "@plane/hooks";
// local imports
import { cn } from "../utils";
import { useDropdownKeyDown } from "../hooks/use-dropdown-key-down";
import { Tooltip } from "../tooltip";
import { cn } from "../utils";
import { ICustomSearchSelectProps } from "./helper";
export const CustomSearchSelect = (props: ICustomSearchSelectProps) => {
@ -34,12 +34,13 @@ export const CustomSearchSelect = (props: ICustomSearchSelectProps) => {
value,
tabIndex,
noResultsMessage = "No matches found",
defaultOpen = false,
} = props;
const [query, setQuery] = useState("");
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
const [isOpen, setIsOpen] = useState(false);
const [isOpen, setIsOpen] = useState(defaultOpen);
// refs
const dropdownRef = useRef<HTMLDivElement | null>(null);

View file

@ -19,6 +19,7 @@ export interface IDropdownProps {
placement?: Placement;
tabIndex?: number;
useCaptureForOutsideClick?: boolean;
defaultOpen?: boolean;
}
export interface IPortalProps {

View file

@ -24,7 +24,7 @@ export const renderFormattedDate = (
try {
// Format the date in the format provided or default format (MMM dd, yyyy)
formattedDate = format(parsedDate, formatToken);
} catch (e) {
} catch (_e) {
// Format the date in format (MMM dd, yyyy) in case of any error
formattedDate = format(parsedDate, "MMM dd, yyyy");
}
@ -287,7 +287,7 @@ export const getDate = (date: string | Date | undefined | null): Date | undefine
if (!isNumber(year) || !isNumber(month) || !isNumber(day)) return;
return new Date(year, month - 1, day);
} catch (e) {
} catch (_e) {
return undefined;
}
};
@ -531,3 +531,44 @@ export const formatDateRange = (
return "";
};
// Duration Helpers
/**
* @returns {string} formatted duration in human readable format
* @description Converts seconds to human readable duration format (e.g., "1 hr 20 min 5 sec")
* @param {number} seconds - The duration in seconds
* @example formatDuration(3665) // "1 hr 1 min 5 sec"
* @example formatDuration(125) // "2 min 5 sec"
* @example formatDuration(45) // "45 sec"
*/
export const formatDuration = (seconds: number | undefined | null): string => {
// Return "N/A" if seconds is not a valid number
if (!isNumber(seconds) || seconds === null || seconds === undefined || seconds < 0) {
return "N/A";
}
// Round to nearest second
const totalSeconds = Math.round(seconds);
// Calculate hours, minutes, and seconds
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const remainingSeconds = totalSeconds % 60;
// Build the formatted string
const parts: string[] = [];
if (hours > 0) {
parts.push(`${hours} hr${hours !== 1 ? "" : ""}`); // Always use "hr" for consistency
}
if (minutes > 0) {
parts.push(`${minutes} min`);
}
if (remainingSeconds > 0 || parts.length === 0) {
parts.push(`${remainingSeconds} sec`);
}
return parts.join(" ");
};