[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:
parent
5629a4d4b6
commit
34c6047d80
7 changed files with 74 additions and 7 deletions
|
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
10
apps/web/ce/components/automations/root.tsx
Normal file
10
apps/web/ce/components/automations/root.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
"use client";
|
||||
|
||||
import React, { FC } from "react";
|
||||
|
||||
export type TCustomAutomationsRootProps = {
|
||||
projectId: string;
|
||||
workspaceSlug: string;
|
||||
};
|
||||
|
||||
export const CustomAutomationsRoot: FC<TCustomAutomationsRootProps> = () => <></>;
|
||||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export interface IDropdownProps {
|
|||
placement?: Placement;
|
||||
tabIndex?: number;
|
||||
useCaptureForOutsideClick?: boolean;
|
||||
defaultOpen?: boolean;
|
||||
}
|
||||
|
||||
export interface IPortalProps {
|
||||
|
|
|
|||
|
|
@ -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(" ");
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue