refactor: display components

This commit is contained in:
Aaryan Khandelwal 2023-09-19 13:07:33 +05:30
parent 14be78564a
commit 7cf263ecd4
16 changed files with 288 additions and 132 deletions

View File

@ -1,17 +1,25 @@
// ui // ui
import { ToggleSwitch } from "components/ui"; import { ToggleSwitch } from "components/ui";
// types // types
import { Props } from "./types"; import { ICustomAttribute } from "types";
type Props = {
attributeDetails: ICustomAttribute;
issueId: string;
projectId: string;
value: boolean;
onChange: (val: boolean) => void;
};
export const CustomCheckboxAttribute: React.FC<Props & { value: boolean }> = ({ export const CustomCheckboxAttribute: React.FC<Props & { value: boolean }> = ({
attributeDetails, attributeDetails,
onChange, onChange,
value, value,
}) => { }) => {
const handleUpdateCheckbox = (val: boolean | string) => onChange(val.toString()); const handleUpdateCheckbox = (val: boolean) => onChange(val);
return ( return (
<div className="bg-custom-background-80 flex items-center gap-2 rounded px-2.5 py-0.5 text-xs"> <div className="text-xs truncate">
{attributeDetails.extra_settings.representation === "toggle_switch" ? ( {attributeDetails.extra_settings.representation === "toggle_switch" ? (
<ToggleSwitch value={value ?? false} onChange={handleUpdateCheckbox} /> <ToggleSwitch value={value ?? false} onChange={handleUpdateCheckbox} />
) : ( ) : (
@ -23,7 +31,6 @@ export const CustomCheckboxAttribute: React.FC<Props & { value: boolean }> = ({
/> />
</div> </div>
)} )}
<span>{attributeDetails.display_name}</span>
</div> </div>
); );
}; };

View File

@ -1,7 +1,15 @@
// react-datepicker // react-datepicker
import ReactDatePicker from "react-datepicker"; import ReactDatePicker from "react-datepicker";
// types // types
import { Props } from "./types"; import { ICustomAttribute } from "types";
type Props = {
attributeDetails: ICustomAttribute;
issueId: string;
projectId: string;
value: Date | undefined;
onChange: (val: Date | null) => void;
};
const DATE_FORMATS: { [key: string]: string } = { const DATE_FORMATS: { [key: string]: string } = {
"MM-DD-YYYY": "MM-dd-yyyy", "MM-DD-YYYY": "MM-dd-yyyy",
@ -14,16 +22,12 @@ const TIME_FORMATS: { [key: string]: string } = {
"24": "HH:mm", "24": "HH:mm",
}; };
export const CustomDateTimeAttribute: React.FC<Props & { value: Date | undefined }> = ({ export const CustomDateTimeAttribute: React.FC<Props> = ({ attributeDetails, onChange, value }) => (
attributeDetails,
onChange,
value,
}) => (
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<ReactDatePicker <ReactDatePicker
selected={value} selected={value}
onChange={onChange} onChange={onChange}
className="bg-custom-background-80 rounded text-xs px-2.5 py-0.5 outline-none" className="bg-custom-background-80 rounded text-xs px-2.5 py-0.5 outline-none truncate"
calendarClassName="!bg-custom-background-80" calendarClassName="!bg-custom-background-80"
dateFormat={`${ dateFormat={`${
attributeDetails.extra_settings.hide_date attributeDetails.extra_settings.hide_date
@ -36,6 +40,7 @@ export const CustomDateTimeAttribute: React.FC<Props & { value: Date | undefined
}`} }`}
showTimeInput={!attributeDetails.extra_settings.hide_time} showTimeInput={!attributeDetails.extra_settings.hide_time}
isClearable={!attributeDetails.is_required} isClearable={!attributeDetails.is_required}
placeholderText={`Enter ${attributeDetails.display_name}`}
/> />
</div> </div>
); );

View File

@ -3,13 +3,17 @@ import { useEffect, useState } from "react";
// react-hook-form // react-hook-form
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
// types // types
import { Props } from "./types"; import { ICustomAttribute } from "types";
export const CustomEmailAttribute: React.FC<Props & { value: string | undefined }> = ({ type Props = {
attributeDetails, attributeDetails: ICustomAttribute;
onChange, issueId: string;
value, projectId: string;
}) => { value: string | undefined;
onChange: (val: string) => void;
};
export const CustomEmailAttribute: React.FC<Props> = ({ attributeDetails, onChange, value }) => {
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const { control, handleSubmit, reset, setFocus } = useForm({ defaultValues: { email: "" } }); const { control, handleSubmit, reset, setFocus } = useForm({ defaultValues: { email: "" } });
@ -45,7 +49,10 @@ export const CustomEmailAttribute: React.FC<Props & { value: string | undefined
return ( return (
<div className="flex-shrink-0"> <div className="flex-shrink-0">
{!isEditing && ( {!isEditing && (
<div className="cursor-pointer text-xs truncate" onClick={() => setIsEditing(true)}> <div
className="cursor-pointer text-xs truncate bg-custom-background-80 px-2.5 py-0.5 w-min max-w-full whitespace-nowrap"
onClick={() => setIsEditing(true)}
>
{value && value !== "" ? value : "Empty"} {value && value !== "" ? value : "Empty"}
</div> </div>
)} )}

View File

@ -8,17 +8,26 @@ import { useDropzone } from "react-dropzone";
import fileService from "services/file.service"; import fileService from "services/file.service";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
import useWorkspaceDetails from "hooks/use-workspace-details";
// icons // icons
import { getFileIcon } from "components/icons"; import { getFileIcon } from "components/icons";
import { X } from "lucide-react";
// helpers // helpers
import { getFileExtension, getFileName } from "helpers/attachment.helper"; import { getFileExtension, getFileName } from "helpers/attachment.helper";
// types // types
import { Props } from "./types"; import { ICustomAttribute } from "types";
import useWorkspaceDetails from "hooks/use-workspace-details";
type Props = {
attributeDetails: ICustomAttribute;
issueId: string;
projectId: string;
value: string | undefined;
onChange: (val: string | undefined) => void;
};
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5 MB const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5 MB
export const CustomFileAttribute: React.FC<Props & { value: string | undefined }> = (props) => { export const CustomFileAttribute: React.FC<Props> = (props) => {
const { attributeDetails, onChange, value } = props; const { attributeDetails, onChange, value } = props;
const [isUploading, setIsUploading] = useState(false); const [isUploading, setIsUploading] = useState(false);
@ -80,6 +89,13 @@ export const CustomFileAttribute: React.FC<Props & { value: string | undefined }
] ]
); );
const handleRemoveFile = () => {
if (!workspaceDetails || !value || value === "") return;
onChange(undefined);
fileService.deleteFile(workspaceDetails.id, value);
};
const { getRootProps, getInputProps, isDragActive, isDragReject, fileRejections } = useDropzone({ const { getRootProps, getInputProps, isDragActive, isDragReject, fileRejections } = useDropzone({
onDrop, onDrop,
maxSize: MAX_FILE_SIZE, maxSize: MAX_FILE_SIZE,
@ -95,15 +111,24 @@ export const CustomFileAttribute: React.FC<Props & { value: string | undefined }
return ( return (
<div className="flex-shrink-0 truncate space-y-1"> <div className="flex-shrink-0 truncate space-y-1">
{value && value !== "" && ( {value && value !== "" && (
<a <div className="group flex items-center justify-between gap-2 p-1 rounded border border-custom-border-200 text-xs truncate">
href={value} <a
target="_blank" href={value}
rel="noopener noreferrer" target="_blank"
className="flex items-center gap-1 p-1 rounded border border-custom-border-200 text-xs truncate" rel="noopener noreferrer"
> className="flex items-center gap-1 flex-grow truncate"
<span className="flex-shrink-0 h-6 w-6">{getFileIcon(getFileExtension(value))}</span> >
<span className="truncate">{getFileName(value)}</span> <span className="flex-shrink-0 h-6 w-6">{getFileIcon(getFileExtension(value))}</span>
</a> <span className="truncate">{getFileName(value)}</span>
</a>
<button
type="button"
className="hidden group-hover:grid place-items-center flex-shrink-0"
onClick={handleRemoveFile}
>
<X size={12} strokeWidth={1.5} />
</button>
</div>
)} )}
<div <div
{...getRootProps()} {...getRootProps()}

View File

@ -5,13 +5,17 @@ import { Controller, useForm } from "react-hook-form";
// ui // ui
import { ProgressBar } from "components/ui"; import { ProgressBar } from "components/ui";
// types // types
import { Props } from "./types"; import { ICustomAttribute } from "types";
export const CustomNumberAttribute: React.FC<Props & { value: number | undefined }> = ({ type Props = {
attributeDetails, attributeDetails: ICustomAttribute;
onChange, issueId: string;
value, projectId: string;
}) => { value: number | undefined;
onChange: (val: number | undefined) => void;
};
export const CustomNumberAttribute: React.FC<Props> = ({ attributeDetails, onChange, value }) => {
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const { control, handleSubmit, reset, setFocus } = useForm({ defaultValues: { number: "" } }); const { control, handleSubmit, reset, setFocus } = useForm({ defaultValues: { number: "" } });
@ -19,7 +23,10 @@ export const CustomNumberAttribute: React.FC<Props & { value: number | undefined
const handleFormSubmit = (data: { number: string }) => { const handleFormSubmit = (data: { number: string }) => {
setIsEditing(false); setIsEditing(false);
onChange(data.number); const number = parseInt(data.number, 10);
if (isNaN(number)) onChange(undefined);
else onChange(number);
}; };
useEffect(() => { useEffect(() => {
@ -84,10 +91,10 @@ export const CustomNumberAttribute: React.FC<Props & { value: number | undefined
</> </>
) : ( ) : (
<div <div
className="cursor-pointer text-xs truncate bg-custom-background-80 px-2.5 py-0.5 rounded w-min whitespace-nowrap flex-grow" className="text-xs truncate bg-custom-background-80 px-2.5 py-0.5 w-min max-w-full whitespace-nowrap"
onClick={() => setIsEditing(true)} onClick={() => setIsEditing(true)}
> >
{value ?? `Enter ${attributeDetails.display_name}`} {value ?? "Empty"}
</div> </div>
)} )}
</div> </div>

View File

@ -12,11 +12,19 @@ import modulesService from "services/modules.service";
// icons // icons
import { Search } from "lucide-react"; import { Search } from "lucide-react";
// types // types
import { Props } from "./types"; import { ICustomAttribute } from "types";
// fetch-keys // fetch-keys
import { CYCLES_LIST, MODULE_LIST } from "constants/fetch-keys"; import { CYCLES_LIST, MODULE_LIST } from "constants/fetch-keys";
export const CustomRelationAttribute: React.FC<Props & { value: string | undefined }> = ({ type Props = {
attributeDetails: ICustomAttribute;
issueId: string;
projectId: string;
value: string | undefined;
onChange: (val: string | undefined) => void;
};
export const CustomRelationAttribute: React.FC<Props> = ({
attributeDetails, attributeDetails,
onChange, onChange,
projectId, projectId,

View File

@ -7,15 +7,22 @@ import useOutsideClickDetector from "hooks/use-outside-click-detector";
// icons // icons
import { Check, Search } from "lucide-react"; import { Check, Search } from "lucide-react";
// types // types
import { Props } from "./types"; import { ICustomAttribute } from "types";
export const CustomSelectAttribute: React.FC< type Props = {
Props & attributeDetails: ICustomAttribute;
( issueId: string;
| { multiple?: false; value: string | undefined } projectId: string;
| { multiple?: true; value: string[] | undefined } } & (
) | {
> = (props) => { multiple?: false;
onChange: (val: string | undefined) => void;
value: string | undefined;
}
| { multiple?: true; onChange: (val: string[] | undefined) => void; value: string[] | undefined }
);
export const CustomSelectAttribute: React.FC<Props> = (props) => {
const { attributeDetails, multiple = false, onChange, value } = props; const { attributeDetails, multiple = false, onChange, value } = props;
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
@ -62,8 +69,8 @@ export const CustomSelectAttribute: React.FC<
}); });
const comboboxProps: any = { const comboboxProps: any = {
value,
onChange, onChange,
value,
}; };
if (multiple) comboboxProps.multiple = true; if (multiple) comboboxProps.multiple = true;
@ -101,14 +108,16 @@ export const CustomSelectAttribute: React.FC<
</div> </div>
) )
) : ( ) : (
<span <div className="flex items-center gap-2 flex-wrap">
className="px-2.5 py-0.5 rounded text-xs" <span
style={{ className="px-2.5 py-0.5 rounded text-xs"
backgroundColor: `${options.find((o) => o.id === value)?.color}40`, style={{
}} backgroundColor: `${options.find((o) => o.id === value)?.color}40`,
> }}
{options.find((o) => o.id === value)?.display_name} >
</span> {options.find((o) => o.id === value)?.display_name}
</span>
</div>
) )
) : ( ) : (
<div className="cursor-pointer text-xs truncate bg-custom-background-80 px-2.5 py-0.5 rounded"> <div className="cursor-pointer text-xs truncate bg-custom-background-80 px-2.5 py-0.5 rounded">

View File

@ -3,7 +3,15 @@ import { useEffect, useState } from "react";
// react-hook-form // react-hook-form
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
// types // types
import { Props } from "./types"; import { ICustomAttribute } from "types";
type Props = {
attributeDetails: ICustomAttribute;
issueId: string;
projectId: string;
value: string | undefined;
onChange: (val: string) => void;
};
export const CustomTextAttribute: React.FC<Props & { value: string | undefined }> = ({ export const CustomTextAttribute: React.FC<Props & { value: string | undefined }> = ({
attributeDetails, attributeDetails,
@ -45,7 +53,10 @@ export const CustomTextAttribute: React.FC<Props & { value: string | undefined }
return ( return (
<div className="flex-shrink-0"> <div className="flex-shrink-0">
{!isEditing && ( {!isEditing && (
<div className="cursor-pointer text-xs truncate" onClick={() => setIsEditing(true)}> <div
className="cursor-pointer text-xs truncate bg-custom-background-80 px-2.5 py-0.5 w-min max-w-full whitespace-nowrap"
onClick={() => setIsEditing(true)}
>
{value && value !== "" ? value : `Enter ${attributeDetails.display_name}`} {value && value !== "" ? value : `Enter ${attributeDetails.display_name}`}
</div> </div>
)} )}

View File

@ -1,9 +0,0 @@
// types
import { ICustomAttribute } from "types";
export type Props = {
attributeDetails: ICustomAttribute;
issueId: string;
onChange: (value: any) => void;
projectId: string;
};

View File

@ -3,7 +3,15 @@ import { useEffect, useState } from "react";
// react-hook-form // react-hook-form
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
// types // types
import { Props } from "./types"; import { ICustomAttribute } from "types";
type Props = {
attributeDetails: ICustomAttribute;
issueId: string;
projectId: string;
value: string | undefined;
onChange: (val: string) => void;
};
export const CustomUrlAttribute: React.FC<Props & { value: string | undefined }> = ({ export const CustomUrlAttribute: React.FC<Props & { value: string | undefined }> = ({
attributeDetails, attributeDetails,
@ -43,8 +51,11 @@ export const CustomUrlAttribute: React.FC<Props & { value: string | undefined }>
return ( return (
<div className="flex-shrink-0"> <div className="flex-shrink-0">
{!isEditing && ( {!isEditing && (
<div className="cursor-pointer text-xs truncate" onClick={() => setIsEditing(true)}> <div
{value && value !== "" ? value : `Enter ${attributeDetails.display_name}`} className="cursor-pointer text-xs truncate bg-custom-background-80 px-2.5 py-0.5 w-min max-w-full whitespace-nowrap"
onClick={() => setIsEditing(true)}
>
{value && value !== "" ? value : "Empty"}
</div> </div>
)} )}
{isEditing && ( {isEditing && (

View File

@ -3,11 +3,11 @@ import { Controller } from "react-hook-form";
// components // components
import { FormComponentProps, Input } from "components/custom-attributes"; import { FormComponentProps, Input } from "components/custom-attributes";
// ui // ui
import { CustomSelect, ToggleSwitch } from "components/ui"; import { CustomSelect, ToggleSwitch, Tooltip } from "components/ui";
// constants // constants
import { DATE_FORMATS, TIME_FORMATS } from "constants/custom-attributes"; import { DATE_FORMATS, TIME_FORMATS } from "constants/custom-attributes";
export const DateTimeAttributeForm: React.FC<FormComponentProps> = ({ control }) => ( export const DateTimeAttributeForm: React.FC<FormComponentProps> = ({ control, watch }) => (
<div className="space-y-3"> <div className="space-y-3">
<Controller <Controller
control={control} control={control}
@ -44,8 +44,20 @@ export const DateTimeAttributeForm: React.FC<FormComponentProps> = ({ control })
name="extra_settings.hide_date" name="extra_settings.hide_date"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<div className="flex items-center justify-end gap-1 mt-2"> <div className="flex items-center justify-end gap-1 mt-2">
<ToggleSwitch value={value ?? false} onChange={onChange} size="sm" /> <Tooltip
<span className="text-xs">Don{"'"}t show date</span> tooltipContent="Cannot disable both, date and time"
disabled={!watch("extra_settings.hide_time")}
>
<div className="flex items-center gap-1">
<ToggleSwitch
value={value ?? false}
onChange={onChange}
size="sm"
disabled={watch("extra_settings.hide_time")}
/>
<span className="text-xs">Don{"'"}t show date</span>
</div>
</Tooltip>
</div> </div>
)} )}
/> />
@ -78,8 +90,20 @@ export const DateTimeAttributeForm: React.FC<FormComponentProps> = ({ control })
name="extra_settings.hide_time" name="extra_settings.hide_time"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<div className="flex items-center justify-end gap-1 mt-2"> <div className="flex items-center justify-end gap-1 mt-2">
<ToggleSwitch value={value ?? false} onChange={onChange} size="sm" /> <Tooltip
<span className="text-xs">Don{"'"}t show time</span> tooltipContent="Cannot disable both, date and time"
disabled={!watch("extra_settings.hide_date")}
>
<div className="flex items-center gap-1">
<ToggleSwitch
value={value ?? false}
onChange={onChange}
size="sm"
disabled={watch("extra_settings.hide_date")}
/>
<span className="text-xs">Don{"'"}t show time</span>
</div>
</Tooltip>
</div> </div>
)} )}
/> />

View File

@ -63,7 +63,7 @@ export const IssueModalCustomAttributesList: React.FC<Props> = observer(
<CustomCheckboxAttribute <CustomCheckboxAttribute
attributeDetails={attribute} attributeDetails={attribute}
issueId={issueId} issueId={issueId}
onChange={(val: string) => onChange(attribute.id, [val])} onChange={(val) => onChange(attribute.id, [`${val}`])}
projectId={projectId} projectId={projectId}
value={values[attribute.id]?.[0] === "true" ? true : false} value={values[attribute.id]?.[0] === "true" ? true : false}
/> />
@ -72,7 +72,7 @@ export const IssueModalCustomAttributesList: React.FC<Props> = observer(
<CustomDateTimeAttribute <CustomDateTimeAttribute
attributeDetails={attribute} attributeDetails={attribute}
issueId={issueId} issueId={issueId}
onChange={(val: string) => onChange(attribute.id, [val])} onChange={(val) => onChange(attribute.id, [val?.toISOString() ?? ""])}
projectId={projectId} projectId={projectId}
value={ value={
values[attribute.id]?.[0] ? new Date(values[attribute.id]?.[0]) : undefined values[attribute.id]?.[0] ? new Date(values[attribute.id]?.[0]) : undefined
@ -101,7 +101,9 @@ export const IssueModalCustomAttributesList: React.FC<Props> = observer(
<CustomSelectAttribute <CustomSelectAttribute
attributeDetails={attribute} attributeDetails={attribute}
issueId={issueId} issueId={issueId}
onChange={(val: string[]) => onChange(attribute.id, val)} onChange={(val) => {
if (val) onChange(attribute.id, val);
}}
projectId={projectId} projectId={projectId}
value={values[attribute.id] ?? []} value={values[attribute.id] ?? []}
multiple multiple
@ -111,7 +113,9 @@ export const IssueModalCustomAttributesList: React.FC<Props> = observer(
<CustomNumberAttribute <CustomNumberAttribute
attributeDetails={attribute} attributeDetails={attribute}
issueId={issueId} issueId={issueId}
onChange={(val: string) => onChange(attribute.id, [val])} onChange={(val) => {
if (val) onChange(attribute.id, [val.toString()]);
}}
projectId={projectId} projectId={projectId}
value={ value={
values[attribute.id]?.[0] ? parseInt(values[attribute.id]?.[0]) : undefined values[attribute.id]?.[0] ? parseInt(values[attribute.id]?.[0]) : undefined
@ -122,7 +126,9 @@ export const IssueModalCustomAttributesList: React.FC<Props> = observer(
<CustomRelationAttribute <CustomRelationAttribute
attributeDetails={attribute} attributeDetails={attribute}
issueId={issueId} issueId={issueId}
onChange={(val: string) => onChange(attribute.id, [val])} onChange={(val) => {
if (val) onChange(attribute.id, [val]);
}}
projectId={projectId} projectId={projectId}
value={values[attribute.id]?.[0]} value={values[attribute.id]?.[0]}
/> />
@ -131,16 +137,19 @@ export const IssueModalCustomAttributesList: React.FC<Props> = observer(
<CustomSelectAttribute <CustomSelectAttribute
attributeDetails={attribute} attributeDetails={attribute}
issueId={issueId} issueId={issueId}
onChange={(val: string) => onChange(attribute.id, [val])} onChange={(val) => {
if (val) onChange(attribute.id, [val]);
}}
projectId={projectId} projectId={projectId}
value={attribute.default_value !== "" ? attribute.default_value : undefined} value={attribute.default_value !== "" ? attribute.default_value : undefined}
multiple={false}
/> />
)} )}
{attribute.type === "text" && ( {attribute.type === "text" && (
<CustomTextAttribute <CustomTextAttribute
attributeDetails={attribute} attributeDetails={attribute}
issueId={issueId} issueId={issueId}
onChange={(val: string) => onChange(attribute.id, [val])} onChange={(val) => onChange(attribute.id, [val])}
projectId={projectId} projectId={projectId}
value={attribute.default_value} value={attribute.default_value}
/> />
@ -149,10 +158,7 @@ export const IssueModalCustomAttributesList: React.FC<Props> = observer(
<CustomUrlAttribute <CustomUrlAttribute
attributeDetails={attribute} attributeDetails={attribute}
issueId={issueId} issueId={issueId}
onChange={(val: string) => { onChange={(val: string) => onChange(attribute.id, [val])}
console.log(val);
onChange(attribute.id, [val]);
}}
projectId={projectId} projectId={projectId}
value={values[attribute.id]?.[0]} value={values[attribute.id]?.[0]}
/> />

View File

@ -26,9 +26,10 @@ import { CUSTOM_ATTRIBUTES_LIST } from "constants/custom-attributes";
type Props = { type Props = {
issue: IIssue | undefined; issue: IIssue | undefined;
projectId: string;
}; };
export const SidebarCustomAttributesList: React.FC<Props> = observer(({ issue }) => { export const SidebarCustomAttributesList: React.FC<Props> = observer(({ issue, projectId }) => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
@ -37,14 +38,20 @@ export const SidebarCustomAttributesList: React.FC<Props> = observer(({ issue })
customAttributeValues: customAttributeValuesStore, customAttributeValues: customAttributeValuesStore,
} = useMobxStore(); } = useMobxStore();
const { entityAttributes, fetchEntityDetails } = customAttributesStore; const { entityAttributes, fetchEntityDetails } = customAttributesStore;
const { issueAttributeValues, fetchIssueAttributeValues } = customAttributeValuesStore; const { issueAttributeValues, fetchIssueAttributeValues, deleteAttributeValue } =
customAttributeValuesStore;
const handleAttributeUpdate = (attributeId: string, value: string[]) => { const handleAttributeUpdate = (attributeId: string, value: string | string[] | undefined) => {
if (!issue || !workspaceSlug) return; if (!issue || !workspaceSlug) return;
if (!value) {
deleteAttributeValue(workspaceSlug.toString(), projectId, issue.id, attributeId);
return;
}
const payload: ICustomAttributeValueFormData = { const payload: ICustomAttributeValueFormData = {
issue_properties: { issue_properties: {
[attributeId]: value, [attributeId]: Array.isArray(value) ? value : [value],
}, },
}; };
@ -109,7 +116,7 @@ export const SidebarCustomAttributesList: React.FC<Props> = observer(({ issue })
<CustomCheckboxAttribute <CustomCheckboxAttribute
attributeDetails={attribute} attributeDetails={attribute}
issueId={issue.id} issueId={issue.id}
onChange={(val: string) => handleAttributeUpdate(attribute.id, [val])} onChange={(val) => handleAttributeUpdate(attribute.id, [`${val}`])}
projectId={issue.project} projectId={issue.project}
value={ value={
attributeValue ? (attributeValue?.[0]?.value === "true" ? true : false) : false attributeValue ? (attributeValue?.[0]?.value === "true" ? true : false) : false
@ -120,7 +127,9 @@ export const SidebarCustomAttributesList: React.FC<Props> = observer(({ issue })
<CustomDateTimeAttribute <CustomDateTimeAttribute
attributeDetails={attribute} attributeDetails={attribute}
issueId={issue.id} issueId={issue.id}
onChange={(val: Date) => handleAttributeUpdate(attribute.id, [val.toISOString()])} onChange={(val: Date | null) => {
handleAttributeUpdate(attribute.id, val ? [val.toISOString()] : undefined);
}}
projectId={issue.project} projectId={issue.project}
value={attributeValue ? new Date(attributeValue?.[0]?.value ?? "") : undefined} value={attributeValue ? new Date(attributeValue?.[0]?.value ?? "") : undefined}
/> />
@ -129,7 +138,9 @@ export const SidebarCustomAttributesList: React.FC<Props> = observer(({ issue })
<CustomEmailAttribute <CustomEmailAttribute
attributeDetails={attribute} attributeDetails={attribute}
issueId={issue.id} issueId={issue.id}
onChange={(val: string) => handleAttributeUpdate(attribute.id, [val])} onChange={(val) => {
handleAttributeUpdate(attribute.id, val && val !== "" ? [val] : undefined);
}}
projectId={issue.project} projectId={issue.project}
value={attributeValue ? attributeValue?.[0]?.value : undefined} value={attributeValue ? attributeValue?.[0]?.value : undefined}
/> />
@ -138,7 +149,7 @@ export const SidebarCustomAttributesList: React.FC<Props> = observer(({ issue })
<CustomFileAttribute <CustomFileAttribute
attributeDetails={attribute} attributeDetails={attribute}
issueId={issue.id} issueId={issue.id}
onChange={(val: string) => handleAttributeUpdate(attribute.id, [val])} onChange={(val) => handleAttributeUpdate(attribute.id, val)}
projectId={issue.project} projectId={issue.project}
value={attributeValue ? attributeValue?.[0]?.value : undefined} value={attributeValue ? attributeValue?.[0]?.value : undefined}
/> />
@ -147,7 +158,7 @@ export const SidebarCustomAttributesList: React.FC<Props> = observer(({ issue })
<CustomSelectAttribute <CustomSelectAttribute
attributeDetails={attribute} attributeDetails={attribute}
issueId={issue.id} issueId={issue.id}
onChange={(val: string[]) => handleAttributeUpdate(attribute.id, val)} onChange={(val) => handleAttributeUpdate(attribute.id, val)}
projectId={issue.project} projectId={issue.project}
value={Array.isArray(attributeValue) ? attributeValue.map((v) => v.value) : []} value={Array.isArray(attributeValue) ? attributeValue.map((v) => v.value) : []}
multiple multiple
@ -157,7 +168,9 @@ export const SidebarCustomAttributesList: React.FC<Props> = observer(({ issue })
<CustomNumberAttribute <CustomNumberAttribute
attributeDetails={attribute} attributeDetails={attribute}
issueId={issue.id} issueId={issue.id}
onChange={(val: string) => handleAttributeUpdate(attribute.id, [val])} onChange={(val) => {
handleAttributeUpdate(attribute.id, val ? val.toString() : undefined);
}}
projectId={issue.project} projectId={issue.project}
value={ value={
attributeValue ? parseInt(attributeValue?.[0]?.value ?? "0", 10) : undefined attributeValue ? parseInt(attributeValue?.[0]?.value ?? "0", 10) : undefined
@ -168,7 +181,7 @@ export const SidebarCustomAttributesList: React.FC<Props> = observer(({ issue })
<CustomRelationAttribute <CustomRelationAttribute
attributeDetails={attribute} attributeDetails={attribute}
issueId={issue.id} issueId={issue.id}
onChange={(val: string) => handleAttributeUpdate(attribute.id, [val])} onChange={(val) => handleAttributeUpdate(attribute.id, val)}
projectId={issue.project} projectId={issue.project}
value={attributeValue ? attributeValue?.[0]?.value : undefined} value={attributeValue ? attributeValue?.[0]?.value : undefined}
/> />
@ -177,16 +190,19 @@ export const SidebarCustomAttributesList: React.FC<Props> = observer(({ issue })
<CustomSelectAttribute <CustomSelectAttribute
attributeDetails={attribute} attributeDetails={attribute}
issueId={issue.id} issueId={issue.id}
onChange={(val: string) => handleAttributeUpdate(attribute.id, [val])} onChange={(val) => handleAttributeUpdate(attribute.id, val)}
projectId={issue.project} projectId={issue.project}
value={attributeValue ? attributeValue?.[0]?.value : undefined} value={attributeValue ? attributeValue?.[0]?.value : undefined}
multiple={false}
/> />
)} )}
{attribute.type === "text" && ( {attribute.type === "text" && (
<CustomTextAttribute <CustomTextAttribute
attributeDetails={attribute} attributeDetails={attribute}
issueId={issue.id} issueId={issue.id}
onChange={(val: string) => handleAttributeUpdate(attribute.id, [val])} onChange={(val) =>
handleAttributeUpdate(attribute.id, val && val !== "" ? [val] : undefined)
}
projectId={issue.project} projectId={issue.project}
value={attributeValue ? attributeValue?.[0].value : undefined} value={attributeValue ? attributeValue?.[0].value : undefined}
/> />
@ -195,7 +211,9 @@ export const SidebarCustomAttributesList: React.FC<Props> = observer(({ issue })
<CustomUrlAttribute <CustomUrlAttribute
attributeDetails={attribute} attributeDetails={attribute}
issueId={issue.id} issueId={issue.id}
onChange={(val: string) => handleAttributeUpdate(attribute.id, [val])} onChange={(val) =>
handleAttributeUpdate(attribute.id, val && val !== "" ? [val] : undefined)
}
projectId={issue.project} projectId={issue.project}
value={attributeValue ? attributeValue?.[0]?.value : undefined} value={attributeValue ? attributeValue?.[0]?.value : undefined}
/> />

View File

@ -305,7 +305,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
data={issueDetail ?? null} data={issueDetail ?? null}
user={user} user={user}
/> />
<div className="h-full w-full flex flex-col divide-y-2 divide-custom-border-200 overflow-hidden"> <div className="h-full w-full flex flex-col divide-y divide-custom-border-300 overflow-hidden">
<div className="flex items-center justify-between px-5 pb-3"> <div className="flex items-center justify-between px-5 pb-3">
<h4 className="text-sm font-medium"> <h4 className="text-sm font-medium">
{issueDetail?.project_detail?.identifier}-{issueDetail?.sequence_id} {issueDetail?.project_detail?.identifier}-{issueDetail?.sequence_id}
@ -349,30 +349,9 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
</div> </div>
<div className="h-full w-full px-5 overflow-y-auto"> <div className="h-full w-full px-5 overflow-y-auto">
<div className={`divide-y-2 divide-custom-border-200 ${uneditable ? "opacity-60" : ""}`}> <div className={`divide-y divide-custom-border-300 ${uneditable ? "opacity-60" : ""}`}>
{showFirstSection && ( {showFirstSection && (
<div className="py-1"> <div className="py-1">
{/* {(fieldsToShow.includes("all") || fieldsToShow.includes("entity")) && (
<div className="flex flex-wrap items-center py-2">
<div className="flex items-center gap-x-2 text-sm text-custom-text-200 sm:basis-1/2">
<Squares2X2Icon className="h-4 w-4 flex-shrink-0" />
<p>Object</p>
</div>
<div className="sm:basis-1/2">
<Controller
control={control}
name="entity"
render={({ field: { value } }) => (
<ObjectsSelect
onChange={(val: string | null) => submitChanges({ entity: val })}
projectId={projectId?.toString() ?? ""}
value={value}
/>
)}
/>
</div>
</div>
)} */}
{(fieldsToShow.includes("all") || fieldsToShow.includes("state")) && ( {(fieldsToShow.includes("all") || fieldsToShow.includes("state")) && (
<div className="flex flex-wrap items-center py-2"> <div className="flex flex-wrap items-center py-2">
<div className="flex items-center gap-x-2 text-sm text-custom-text-200 sm:basis-1/2"> <div className="flex items-center gap-x-2 text-sm text-custom-text-200 sm:basis-1/2">
@ -663,6 +642,14 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
)} )}
</div> </div>
)} )}
{watchIssue("entity") && (
<div className="py-1">
<SidebarCustomAttributesList
issue={issueDetail}
projectId={projectId?.toString() ?? ""}
/>
</div>
)}
</div> </div>
{(fieldsToShow.includes("all") || fieldsToShow.includes("label")) && {(fieldsToShow.includes("all") || fieldsToShow.includes("label")) &&
watchIssue("entity") === null && ( watchIssue("entity") === null && (
@ -705,11 +692,6 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
</div> </div>
</div> </div>
)} )}
{watchIssue("entity") && (
<div className="py-1">
<SidebarCustomAttributesList issue={issueDetail} />
</div>
)}
</div> </div>
</div> </div>
</> </>

View File

@ -91,6 +91,21 @@ class CustomAttributesService extends APIService {
throw error?.response?.data; throw error?.response?.data;
}); });
} }
async deletePropertyValue(
workspaceSlug: string,
projectId: string,
issueId: string,
propertyId: string
): Promise<any> {
return this.delete(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/property-values/${propertyId}/`
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
} }
const customAttributesService = new CustomAttributesService(); const customAttributesService = new CustomAttributesService();

View File

@ -20,6 +20,7 @@ class CustomAttributeValuesStore {
issueAttributeValues: observable.ref, issueAttributeValues: observable.ref,
fetchIssueAttributeValues: action, fetchIssueAttributeValues: action,
createAttributeValue: action, createAttributeValue: action,
deleteAttributeValue: action,
}); });
this.rootStore = _rootStore; this.rootStore = _rootStore;
@ -94,7 +95,6 @@ class CustomAttributeValuesStore {
...this.issueAttributeValues, ...this.issueAttributeValues,
[issueId]: response.children, [issueId]: response.children,
}; };
this.fetchIssueAttributeValuesLoader = false;
}); });
} catch (error) { } catch (error) {
runInAction(() => { runInAction(() => {
@ -102,6 +102,36 @@ class CustomAttributeValuesStore {
}); });
} }
}; };
deleteAttributeValue = async (
workspaceSlug: string,
projectId: string,
issueId: string,
propertyId: string
) => {
const newChildren = [...(this.issueAttributeValues?.[issueId] ?? [])];
newChildren.filter((c) => c.id !== propertyId);
try {
runInAction(() => {
this.issueAttributeValues = {
...this.issueAttributeValues,
[issueId]: newChildren,
};
});
await customAttributesService.deletePropertyValue(
workspaceSlug,
projectId,
issueId,
propertyId
);
} catch (error) {
runInAction(() => {
this.error = error;
});
}
};
} }
export default CustomAttributeValuesStore; export default CustomAttributeValuesStore;