Files
plane/web/ee/components/common/input/description-input.tsx
Prateek Shourya 98fee2ac1d refactor: move web utils to packages (#3353)
* refactor: move web utils to packages

* fix: build and lint errors

* chore: update drag handle plugin

* chore: update table cell type to fix build errors

* fix: build errors

* chore: sync few changes

* feat: add pi base url to constants

* chore: update all util imports in ee folder

* chore: refactor web utils imports

* chore: update imports

* fix: build errors

* fix: build errors

* fix: update utils import

* chore: minor fixes related to duplicate assets imports

* chore: update duplicate assets service

* [WEB-4316] chore: new endpoints to download an asset (#7207)

* chore: new endpoints to download an asset

* chore: add exception handling

* [WEB-4323] refactor: Analytics refactor (#7213)

* chore: updated label for epics

* chore: improved export logic

* refactor: move csvConfig to export.ts and clean up export logic

* refactor: remove unused CSV export logic from WorkItemsInsightTable component

* refactor: streamline data handling in InsightTable component for improved rendering

* feat: add translation for "No. of {entity}" and update priority chart y-axis label to use new translation

* refactor: cleaned up some component and added utilitites

* feat: add "at_risk" translation to multiple languages in translations.json files

* refactor: update TrendPiece component to use new status variants for analytics

* fix: adjust TrendPiece component logic for on-track and off-track status

* refactor: use nullish coalescing operator for yAxis.dx in line and scatter charts

* feat: add "at_risk" translation to various languages in translations.json files

* feat: add "no_of" translation to various languages in translations.json files

* feat: update "at_risk" translation in Ukrainian, Vietnamese, and Chinese locales in translations.json files

* refactor: rename insightsFields to ANALYTICS_INSIGHTS_FIELDS and update analytics tab import to use getAnalyticsTabs function

* feat: update AnalyticsWrapper to use i18n for titles and add new translation for "no_of" in Russian

* fix: update yAxis labels and offsets in various charts to use new translation key and improve layout

* feat: define AnalyticsTab interface and refactor getAnalyticsTabs function for improved type safety

* fix: update AnalyticsTab interface to use TAnalyticsTabsBase for improved type safety

* fix: add whitespace-nowrap class to TableHead for improved header layout in DataTable component

* [WEB-4311]  fix: membership data handling and state reversal on error (#7205)

* [WEB-4231] Pie chart tooltip #7192

* fix: build errors

* fix: utils imports

* chore: fix build errors

* yarn lock file update

---------

Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
Co-authored-by: JayashTripathy <76092296+JayashTripathy@users.noreply.github.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2025-06-16 17:26:28 +05:30

181 lines
5.8 KiB
TypeScript

"use client";
import { FC, useCallback, useEffect, useState } from "react";
import debounce from "lodash/debounce";
import { observer } from "mobx-react";
import { Controller, useForm } from "react-hook-form";
// plane imports
import { EditorReadOnlyRefApi, EditorRefApi } from "@plane/editor";
import { useTranslation } from "@plane/i18n";
import { TNameDescriptionLoader } from "@plane/types";
import { EFileAssetType } from "@plane/types/src/enums";
import { Loader } from "@plane/ui";
// components
import { RichTextEditor, RichTextReadOnlyEditor } from "@/components/editor";
// helpers
import { getDescriptionPlaceholderI18n } from "@plane/utils";
// hooks
import { useEditorAsset, useWorkspace } from "@/hooks/store";
// plane web services
import { WorkspaceService } from "@/plane-web/services";
const workspaceService = new WorkspaceService();
interface IFormData {
id: string;
description_html: string;
}
export type DescriptionInputProps = {
editorReadOnlyRef?: React.RefObject<EditorReadOnlyRefApi>;
editorRef?: React.RefObject<EditorRefApi>;
workspaceSlug: string;
projectId?: string;
itemId: string;
initialValue: string | undefined;
onSubmit: (value: string) => Promise<void>;
fileAssetType: EFileAssetType;
placeholder?: string | ((isFocused: boolean, value: string) => string);
setIsSubmitting: (initialValue: TNameDescriptionLoader) => void;
swrDescription?: string | null | undefined;
containerClassName?: string;
disabled?: boolean;
};
export const DescriptionInput: FC<DescriptionInputProps> = observer((props) => {
const {
editorReadOnlyRef,
editorRef,
workspaceSlug,
projectId,
itemId,
initialValue,
onSubmit,
fileAssetType,
placeholder,
setIsSubmitting,
swrDescription,
containerClassName,
disabled,
} = props;
// states
const [localDescription, setLocalDescription] = useState({
id: itemId,
description_html: initialValue,
});
// store hooks
const { getWorkspaceBySlug } = useWorkspace();
const { uploadEditorAsset } = useEditorAsset();
// derived values
const workspaceDetails = getWorkspaceBySlug(workspaceSlug);
// translation
const { t } = useTranslation();
// form info
const { handleSubmit, reset, control } = useForm<IFormData>({
defaultValues: {
id: itemId,
description_html: initialValue || "",
},
});
// handlers
const handleDescriptionFormSubmit = useCallback(
async (formData: IFormData) => {
await onSubmit(formData.description_html ?? "<p></p>");
},
[onSubmit]
);
// reset form values
useEffect(() => {
if (!itemId) return;
reset({
id: itemId,
description_html: initialValue === "" ? "<p></p>" : initialValue,
});
setLocalDescription({
id: itemId,
description_html: initialValue === "" ? "<p></p>" : initialValue,
});
}, [initialValue, itemId, reset]);
// ADDING handleDescriptionFormSubmit TO DEPENDENCY ARRAY PRODUCES ADVERSE EFFECTS
// TODO: Verify the exhaustive-deps warning
// eslint-disable-next-line react-hooks/exhaustive-deps
const debouncedFormSave = useCallback(
debounce(async () => {
handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting("submitted"));
}, 1500),
[handleSubmit, itemId]
);
return (
<>
{localDescription.description_html ? (
<Controller
name="description_html"
control={control}
render={({ field: { onChange } }) =>
!disabled ? (
<RichTextEditor
ref={editorRef}
id={itemId}
initialValue={localDescription.description_html ?? "<p></p>"}
value={swrDescription ?? null}
workspaceSlug={workspaceSlug}
workspaceId={workspaceDetails?.id ?? ""}
projectId={projectId}
dragDropEnabled
onChange={(_description: object, description_html: string) => {
setIsSubmitting("submitting");
onChange(description_html);
debouncedFormSave();
}}
placeholder={
placeholder ? placeholder : (isFocused, value) => t(getDescriptionPlaceholderI18n(isFocused, value))
}
searchMentionCallback={async (payload) =>
await workspaceService.searchEntity(workspaceSlug?.toString() ?? "", {
...payload,
project_id: projectId?.toString() ?? "",
})
}
containerClassName={containerClassName}
uploadFile={async (blockId, file) => {
try {
const { asset_id } = await uploadEditorAsset({
blockId,
data: {
entity_identifier: itemId,
entity_type: fileAssetType,
},
file,
projectId,
workspaceSlug,
});
return asset_id;
} catch (error) {
console.log("Error in uploading asset:", error);
throw new Error("Asset upload failed. Please try again later.");
}
}}
/>
) : (
<RichTextReadOnlyEditor
ref={editorReadOnlyRef}
id={itemId}
initialValue={localDescription.description_html ?? ""}
containerClassName={containerClassName}
workspaceId={workspaceDetails?.id ?? ""}
workspaceSlug={workspaceSlug}
projectId={projectId}
/>
)
}
/>
) : (
<Loader>
<Loader.Item height="150px" />
</Loader>
)}
</>
);
});