chore: update UI

This commit is contained in:
Aaryan Khandelwal 2024-05-22 18:25:51 +05:30
parent 12000904ee
commit a76e02045b
26 changed files with 268 additions and 107 deletions

View File

@ -7,20 +7,21 @@ import { TSelectionHelper } from "@/hooks/use-multiple-select";
type Props = {
className?: string;
disabled?: boolean;
groupId: string;
id: string;
selectionHelpers: TSelectionHelper;
};
export const MultipleSelectAction: React.FC<Props> = (props) => {
const { className, groupId, id, selectionHelpers } = props;
const { className, disabled = false, groupId, id, selectionHelpers } = props;
// derived values
const isSelected = selectionHelpers.isEntitySelected(id);
const isActive = selectionHelpers.isEntityActive(id);
return (
<Checkbox
className={cn("outline-0", className)}
className={cn("!outline-none", className)}
onClick={(e) => {
e.stopPropagation();
selectionHelpers.handleEntityClick(e, id, groupId);
@ -30,6 +31,7 @@ export const MultipleSelectAction: React.FC<Props> = (props) => {
data-entity-id={id}
data-type="multiple-select-action"
data-active={isActive}
disabled={disabled}
/>
);
};

View File

@ -5,6 +5,7 @@ import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { AlertModalCore, EModalPosition, EModalWidth } from "@/components/core";
// constants
import { EErrorCodes, ERROR_DETAILS } from "@/constants/errors";
import { EIssuesStoreType } from "@/constants/issue";
// hooks
import { useIssues } from "@/hooks/store";
@ -40,13 +41,14 @@ export const BulkArchiveConfirmationModal: React.FC<Props> = observer((props) =>
onSubmit?.();
handleClose();
})
.catch(() =>
.catch((error) => {
const errorInfo = ERROR_DETAILS[error?.error_code as EErrorCodes] ?? undefined;
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Something went wrong. Please try again.",
})
)
title: errorInfo?.title ?? "Error!",
message: errorInfo?.message ?? "Something went wrong. Please try again.",
});
})
.finally(() => setIsDeleting(false));
};

View File

@ -2,8 +2,12 @@ import { useRouter } from "next/router";
import { CalendarCheck2, CalendarClock } from "lucide-react";
// types
import { TBulkIssueProperties } from "@plane/types";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { DateDropdown, MemberDropdown, PriorityDropdown, StateDropdown } from "@/components/dropdowns";
// constants
import { EErrorCodes, ERROR_DETAILS } from "@/constants/errors";
// helpers
import { renderFormattedPayloadDate } from "@/helpers/date-time.helper";
// hooks
@ -29,6 +33,13 @@ export const IssueBulkOperationsProperties: React.FC<Props> = (props) => {
bulkUpdateProperties(workspaceSlug.toString(), projectId.toString(), {
issue_ids: snapshot.selectedEntityIds,
properties: data,
}).catch((error) => {
const errorInfo = ERROR_DETAILS[error?.error_code as EErrorCodes] ?? undefined;
setToast({
type: TOAST_TYPE.ERROR,
title: errorInfo?.title ?? "Error!",
message: errorInfo?.message ?? "Something went wrong. Please try again.",
});
});
};

View File

@ -128,12 +128,10 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
<div
ref={issueRef}
className={cn(
"group/list-block min-h-11 relative flex flex-col md:flex-row md:items-center gap-3 bg-custom-background-100 p-3 pl-1.5 text-sm transition-colors issue-list-block",
"group/list-block min-h-11 relative flex flex-col md:flex-row md:items-center gap-3 bg-custom-background-100 hover:bg-custom-background-90 p-3 pl-1.5 text-sm transition-colors issue-list-block border border-transparent",
{
"border border-custom-primary-70 hover:border-custom-primary-70":
getIsIssuePeeked(issue.id) && peekIssue?.nestingLevel === nestingLevel,
"border-custom-primary-70": getIsIssuePeeked(issue.id) && peekIssue?.nestingLevel === nestingLevel,
"last:border-b-transparent": !getIsIssuePeeked(issue.id),
"hover:bg-custom-background-90": !isIssueSelected,
"bg-custom-primary-100/5 hover:bg-custom-primary-100/10": isIssueSelected,
"bg-custom-background-80": isCurrentBlockDragging,
}
@ -153,21 +151,33 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
<div className="flex items-center gap-1 group">
<DragHandle isDragging={isCurrentBlockDragging} ref={dragHandleRef} disabled={!canDrag} />
{projectId && canEditIssueProperties && (
<div className="flex-shrink-0 grid place-items-center w-3.5">
<MultipleSelectAction
className={cn(
"opacity-0 pointer-events-none group-hover/list-block:opacity-100 group-hover/list-block:pointer-events-auto transition-opacity",
{
"opacity-100 pointer-events-auto": isIssueSelected,
}
)}
groupId={groupId}
id={issue.id}
selectionHelpers={selectionHelpers}
/>
</div>
<Tooltip
tooltipContent={
<>
Only issues within the current
<br />
project can be selected.
</>
}
disabled={issue.project_id === projectId}
>
<div className="flex-shrink-0 grid place-items-center w-3.5">
<MultipleSelectAction
className={cn(
"opacity-0 pointer-events-none group-hover/list-block:opacity-100 group-hover/list-block:pointer-events-auto transition-opacity",
{
"opacity-100 pointer-events-auto": isIssueSelected,
}
)}
groupId={groupId}
id={issue.id}
selectionHelpers={selectionHelpers}
disabled={issue.project_id !== projectId}
/>
</div>
</Tooltip>
)}
<div className="flex h-5 w-5 items-center justify-center">
<div className="size-5 flex items-center justify-center">
{subIssuesCount > 0 && (
<button
className="flex items-center justify-center h-5 w-5 cursor-pointer rounded-sm text-custom-text-400 hover:text-custom-text-300"

View File

@ -84,9 +84,9 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => {
return (
<>
<div className="group/list-header relative w-full flex-shrink-0 flex items-center gap-2 py-1.5 pl-2.5">
<div className="group/list-header relative w-full flex-shrink-0 flex items-center gap-1.5 py-1.5 pl-4">
{canSelectIssues && (
<div className="flex-shrink-0 flex items-center w-3.5">
<div className="flex-shrink-0 flex items-center w-3.5 pl-0.5">
<Checkbox
className={cn(
"opacity-0 pointer-events-none group-hover/list-header:opacity-100 group-hover/list-header:pointer-events-auto outline-0",
@ -99,6 +99,7 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => {
/>
</div>
)}
<div className="size-5" />
<div className="flex-shrink-0 grid place-items-center overflow-hidden">
{icon ?? <CircleDashed className="h-3.5 w-3.5" strokeWidth={2} />}
</div>

View File

@ -192,7 +192,7 @@ export const ListGroup = observer((props: Props) => {
"border-custom-primary-100 ": isDraggingOverColumn,
})}
>
<div className="sticky top-0 z-[2] w-full flex-shrink-0 border-b border-custom-border-200 bg-custom-background-90 px-3 pl-5 py-1">
<div className="sticky top-0 z-[2] w-full flex-shrink-0 border-b border-custom-border-200 bg-custom-background-90 px-3 py-1">
<HeaderGroupByCard
groupID={group.id}
icon={group.icon}

View File

@ -1,19 +1,22 @@
import React from "react";
import { observer } from "mobx-react-lite";
// types
import { TIssue } from "@plane/types";
// components
import { MemberDropdown } from "@/components/dropdowns";
// types
// helpers
import { cn } from "@/helpers/common.helper";
type Props = {
issue: TIssue;
onClose: () => void;
onChange: (issue: TIssue, data: Partial<TIssue>, updates: any) => void;
disabled: boolean;
isIssueSelected: boolean;
};
export const SpreadsheetAssigneeColumn: React.FC<Props> = observer((props: Props) => {
const { issue, onChange, disabled, onClose } = props;
const { issue, onChange, disabled, onClose, isIssueSelected } = props;
return (
<div className="h-11 border-b-[0.5px] border-custom-border-200">
@ -36,7 +39,9 @@ export const SpreadsheetAssigneeColumn: React.FC<Props> = observer((props: Props
buttonVariant={
issue?.assignee_ids && issue.assignee_ids.length > 0 ? "transparent-without-text" : "transparent-with-text"
}
buttonClassName="text-left"
buttonClassName={cn("text-left rounded-none", {
"bg-custom-primary-100/5 hover:bg-custom-primary-100/10": isIssueSelected,
})}
buttonContainerClassName="w-full"
onClose={onClose}
/>

View File

@ -1,17 +1,27 @@
import React from "react";
import { observer } from "mobx-react-lite";
import { TIssue } from "@plane/types";
// types
import { TIssue } from "@plane/types";
// helpers
import { cn } from "@/helpers/common.helper";
type Props = {
issue: TIssue;
isIssueSelected: boolean;
};
export const SpreadsheetAttachmentColumn: React.FC<Props> = observer((props) => {
const { issue } = props;
const { issue, isIssueSelected } = props;
return (
<div className="flex h-11 w-full items-center border-b-[0.5px] border-custom-border-200 px-2.5 py-1 text-xs hover:bg-custom-background-80">
<div
className={cn(
"flex h-11 w-full items-center border-b-[0.5px] border-custom-border-200 px-2.5 py-1 text-xs hover:bg-custom-background-80",
{
"bg-custom-primary-100/5 hover:bg-custom-primary-100/10": isIssueSelected,
}
)}
>
{issue?.attachment_count} {issue?.attachment_count === 1 ? "attachment" : "attachments"}
</div>
);

View File

@ -1,18 +1,28 @@
import React from "react";
import { observer } from "mobx-react-lite";
// types
import { TIssue } from "@plane/types";
// helpers
import { cn } from "@/helpers/common.helper";
import { renderFormattedDate } from "@/helpers/date-time.helper";
// types
type Props = {
issue: TIssue;
isIssueSelected: boolean;
};
export const SpreadsheetCreatedOnColumn: React.FC<Props> = observer((props: Props) => {
const { issue } = props;
const { issue, isIssueSelected } = props;
return (
<div className="flex h-11 w-full items-center justify-center border-b-[0.5px] border-custom-border-200 text-xs hover:bg-custom-background-80">
<div
className={cn(
"flex h-11 w-full items-center justify-center border-b-[0.5px] border-custom-border-200 text-xs hover:bg-custom-background-80",
{
"bg-custom-primary-100/5 hover:bg-custom-primary-100/10": isIssueSelected,
}
)}
>
{renderFormattedDate(issue.created_at)}
</div>
);

View File

@ -1,27 +1,29 @@
import React, { useCallback } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { TIssue } from "@plane/types";
// hooks
import { CycleDropdown } from "@/components/dropdowns";
import { EIssuesStoreType } from "@/constants/issue";
import { useEventTracker, useIssues } from "@/hooks/store";
// components
// types
import { TIssue } from "@plane/types";
// components
import { CycleDropdown } from "@/components/dropdowns";
// constants
import { EIssuesStoreType } from "@/constants/issue";
// helpers
import { cn } from "@/helpers/common.helper";
// hooks
import { useEventTracker, useIssues } from "@/hooks/store";
type Props = {
issue: TIssue;
onClose: () => void;
disabled: boolean;
isIssueSelected: boolean;
};
export const SpreadsheetCycleColumn: React.FC<Props> = observer((props) => {
const { issue, disabled, onClose, isIssueSelected } = props;
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// props
const { issue, disabled, onClose } = props;
// hooks
const { captureIssueEvent } = useEventTracker();
const {
@ -56,7 +58,9 @@ export const SpreadsheetCycleColumn: React.FC<Props> = observer((props) => {
disabled={disabled}
placeholder="Select cycle"
buttonVariant="transparent-with-text"
buttonContainerClassName="w-full relative flex items-center p-2"
buttonContainerClassName={cn("w-full relative flex items-center p-2", {
"bg-custom-primary-100/5 hover:bg-custom-primary-100/10": isIssueSelected,
})}
buttonClassName="relative leading-4 h-4.5 bg-transparent"
onClose={onClose}
/>

View File

@ -1,26 +1,27 @@
import React from "react";
import { observer } from "mobx-react";
import { CalendarCheck2 } from "lucide-react";
// types
import { TIssue } from "@plane/types";
// hooks
// components
import { DateDropdown } from "@/components/dropdowns";
// helpers
import { cn } from "@/helpers/common.helper";
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
import { shouldHighlightIssueDueDate } from "@/helpers/issue.helper";
// hooks
import { useProjectState } from "@/hooks/store";
// types
type Props = {
issue: TIssue;
onClose: () => void;
onChange: (issue: TIssue, data: Partial<TIssue>, updates: any) => void;
disabled: boolean;
isIssueSelected: boolean;
};
export const SpreadsheetDueDateColumn: React.FC<Props> = observer((props: Props) => {
const { issue, onChange, disabled, onClose } = props;
const { issue, onChange, disabled, onClose, isIssueSelected } = props;
// store hooks
const { getStateById } = useProjectState();
// derived values
@ -49,6 +50,7 @@ export const SpreadsheetDueDateColumn: React.FC<Props> = observer((props: Props)
buttonContainerClassName="w-full"
buttonClassName={cn("rounded-none text-left", {
"text-red-500": shouldHighlightIssueDueDate(issue.target_date, stateDetails?.group),
"bg-custom-primary-100/5 hover:bg-custom-primary-100/10": isIssueSelected,
})}
clearIconClassName="!text-custom-text-100"
onClose={onClose}

View File

@ -1,18 +1,21 @@
// components
import { observer } from "mobx-react-lite";
import { TIssue } from "@plane/types";
import { EstimateDropdown } from "@/components/dropdowns";
// types
import { TIssue } from "@plane/types";
// components
import { EstimateDropdown } from "@/components/dropdowns";
// helpers
import { cn } from "@/helpers/common.helper";
type Props = {
issue: TIssue;
onClose: () => void;
onChange: (issue: TIssue, data: Partial<TIssue>, updates: any) => void;
disabled: boolean;
isIssueSelected: boolean;
};
export const SpreadsheetEstimateColumn: React.FC<Props> = observer((props: Props) => {
const { issue, onChange, disabled, onClose } = props;
const { issue, onChange, disabled, onClose, isIssueSelected } = props;
return (
<div className="h-11 border-b-[0.5px] border-custom-border-200">
@ -25,7 +28,9 @@ export const SpreadsheetEstimateColumn: React.FC<Props> = observer((props: Props
projectId={issue.project_id}
disabled={disabled}
buttonVariant="transparent-with-text"
buttonClassName="rounded-none text-left"
buttonClassName={cn("text-left rounded-none", {
"bg-custom-primary-100/5 hover:bg-custom-primary-100/10": isIssueSelected,
})}
buttonContainerClassName="w-full"
onClose={onClose}
/>

View File

@ -1,10 +1,12 @@
import React from "react";
import { observer } from "mobx-react-lite";
// types
import { TIssue } from "@plane/types";
// components
// helpers
import { cn } from "@/helpers/common.helper";
// hooks
import { useLabel } from "@/hooks/store";
// types
// components
import { IssuePropertyLabels } from "../../properties";
type Props = {
@ -12,10 +14,11 @@ type Props = {
onClose: () => void;
onChange: (issue: TIssue, data: Partial<TIssue>, updates: any) => void;
disabled: boolean;
isIssueSelected: boolean;
};
export const SpreadsheetLabelColumn: React.FC<Props> = observer((props: Props) => {
const { issue, onChange, disabled, onClose } = props;
const { issue, onChange, disabled, onClose, isIssueSelected } = props;
// hooks
const { labelMap } = useLabel();
@ -28,7 +31,9 @@ export const SpreadsheetLabelColumn: React.FC<Props> = observer((props: Props) =
defaultOptions={defaultLabelOptions}
onChange={(data) => onChange(issue, { label_ids: data }, { changed_property: "labels", change_details: data })}
className="h-11 w-full border-b-[0.5px] border-custom-border-200 hover:bg-custom-background-80"
buttonClassName="px-2.5 h-full"
buttonClassName={cn("px-2.5 h-full", {
"bg-custom-primary-100/5 hover:bg-custom-primary-100/10": isIssueSelected,
})}
hideDropdownArrow
maxRender={1}
disabled={disabled}

View File

@ -1,17 +1,27 @@
import React from "react";
import { observer } from "mobx-react-lite";
import { TIssue } from "@plane/types";
// types
import { TIssue } from "@plane/types";
// helpers
import { cn } from "@/helpers/common.helper";
type Props = {
issue: TIssue;
isIssueSelected: boolean;
};
export const SpreadsheetLinkColumn: React.FC<Props> = observer((props: Props) => {
const { issue } = props;
const { issue, isIssueSelected } = props;
return (
<div className="flex h-11 w-full items-center border-b-[0.5px] border-custom-border-200 px-2.5 py-1 text-xs hover:bg-custom-background-80">
<div
className={cn(
"flex h-11 w-full items-center border-b-[0.5px] border-custom-border-200 px-2.5 py-1 text-xs hover:bg-custom-background-80",
{
"bg-custom-primary-100/5 hover:bg-custom-primary-100/10": isIssueSelected,
}
)}
>
{issue?.link_count} {issue?.link_count === 1 ? "link" : "links"}
</div>
);

View File

@ -2,27 +2,29 @@ import React, { useCallback } from "react";
import xor from "lodash/xor";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { TIssue } from "@plane/types";
// hooks
import { ModuleDropdown } from "@/components/dropdowns";
import { EIssuesStoreType } from "@/constants/issue";
import { useEventTracker, useIssues } from "@/hooks/store";
// components
// types
import { TIssue } from "@plane/types";
// components
import { ModuleDropdown } from "@/components/dropdowns";
// constants
import { EIssuesStoreType } from "@/constants/issue";
// helpers
import { cn } from "@/helpers/common.helper";
// hooks
import { useEventTracker, useIssues } from "@/hooks/store";
type Props = {
issue: TIssue;
onClose: () => void;
disabled: boolean;
isIssueSelected: boolean;
};
export const SpreadsheetModuleColumn: React.FC<Props> = observer((props) => {
const { issue, disabled, onClose, isIssueSelected } = props;
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// props
const { issue, disabled, onClose } = props;
// hooks
const { captureIssueEvent } = useEventTracker();
const {
@ -65,7 +67,9 @@ export const SpreadsheetModuleColumn: React.FC<Props> = observer((props) => {
disabled={disabled}
placeholder="Select modules"
buttonVariant="transparent-with-text"
buttonContainerClassName="w-full relative flex items-center p-2"
buttonContainerClassName={cn("w-full relative flex items-center p-2", {
"bg-custom-primary-100/5 hover:bg-custom-primary-100/10": isIssueSelected,
})}
buttonClassName="relative leading-4 h-4.5 bg-transparent"
onClose={onClose}
multiple

View File

@ -1,19 +1,22 @@
import React from "react";
import { observer } from "mobx-react-lite";
// types
import { TIssue } from "@plane/types";
// components
import { PriorityDropdown } from "@/components/dropdowns";
// types
// helpers
import { cn } from "@/helpers/common.helper";
type Props = {
issue: TIssue;
onClose: () => void;
onChange: (issue: TIssue, data: Partial<TIssue>, updates: any) => void;
disabled: boolean;
isIssueSelected: boolean;
};
export const SpreadsheetPriorityColumn: React.FC<Props> = observer((props: Props) => {
const { issue, onChange, disabled, onClose } = props;
const { issue, onChange, disabled, onClose, isIssueSelected } = props;
return (
<div className="h-11 border-b-[0.5px] border-custom-border-200">
@ -22,7 +25,9 @@ export const SpreadsheetPriorityColumn: React.FC<Props> = observer((props: Props
onChange={(data) => onChange(issue, { priority: data }, { changed_property: "priority", change_details: data })}
disabled={disabled}
buttonVariant="transparent-with-text"
buttonClassName="rounded-none text-left"
buttonClassName={cn("text-left rounded-none", {
"bg-custom-primary-100/5 hover:bg-custom-primary-100/10": isIssueSelected,
})}
buttonContainerClassName="w-full"
onClose={onClose}
/>

View File

@ -1,22 +1,24 @@
import React from "react";
import { observer } from "mobx-react";
import { CalendarClock } from "lucide-react";
// types
import { TIssue } from "@plane/types";
// components
import { DateDropdown } from "@/components/dropdowns";
// helpers
import { cn } from "@/helpers/common.helper";
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
// types
type Props = {
issue: TIssue;
onClose: () => void;
onChange: (issue: TIssue, data: Partial<TIssue>, updates: any) => void;
disabled: boolean;
isIssueSelected: boolean;
};
export const SpreadsheetStartDateColumn: React.FC<Props> = observer((props: Props) => {
const { issue, onChange, disabled, onClose } = props;
const { issue, onChange, disabled, onClose, isIssueSelected } = props;
return (
<div className="h-11 border-b-[0.5px] border-custom-border-200">
@ -38,7 +40,9 @@ export const SpreadsheetStartDateColumn: React.FC<Props> = observer((props: Prop
placeholder="Start date"
icon={<CalendarClock className="h-3 w-3 flex-shrink-0" />}
buttonVariant="transparent-with-text"
buttonClassName="rounded-none text-left"
buttonClassName={cn("text-left rounded-none", {
"bg-custom-primary-100/5 hover:bg-custom-primary-100/10": isIssueSelected,
})}
buttonContainerClassName="w-full"
onClose={onClose}
/>

View File

@ -1,19 +1,22 @@
import React from "react";
import { observer } from "mobx-react-lite";
// types
import { TIssue } from "@plane/types";
// components
import { StateDropdown } from "@/components/dropdowns";
// types
// helpers
import { cn } from "@/helpers/common.helper";
type Props = {
issue: TIssue;
onClose: () => void;
onChange: (issue: TIssue, data: Partial<TIssue>, updates: any) => void;
disabled: boolean;
isIssueSelected: boolean;
};
export const SpreadsheetStateColumn: React.FC<Props> = observer((props) => {
const { issue, onChange, disabled, onClose } = props;
const { issue, onChange, disabled, onClose, isIssueSelected } = props;
return (
<div className="h-11 border-b-[0.5px] border-custom-border-200">
@ -23,7 +26,9 @@ export const SpreadsheetStateColumn: React.FC<Props> = observer((props) => {
onChange={(data) => onChange(issue, { state_id: data }, { changed_property: "state", change_details: data })}
disabled={disabled}
buttonVariant="transparent-with-text"
buttonClassName="rounded-none text-left"
buttonClassName={cn("text-left rounded-none", {
"bg-custom-primary-100/5 hover:bg-custom-primary-100/10": isIssueSelected,
})}
buttonContainerClassName="w-full"
onClose={onClose}
/>

View File

@ -10,10 +10,11 @@ import { useAppRouter } from "@/hooks/store";
type Props = {
issue: TIssue;
isIssueSelected: boolean;
};
export const SpreadsheetSubIssueColumn: React.FC<Props> = observer((props: Props) => {
const { issue } = props;
const { issue, isIssueSelected } = props;
// router
const router = useRouter();
// hooks
@ -37,6 +38,7 @@ export const SpreadsheetSubIssueColumn: React.FC<Props> = observer((props: Props
"flex h-11 w-full items-center border-b-[0.5px] border-custom-border-200 px-2.5 py-1 text-xs hover:bg-custom-background-80",
{
"cursor-pointer": subIssueCount,
"bg-custom-primary-100/5 hover:bg-custom-primary-100/10": isIssueSelected,
}
)}
>

View File

@ -1,18 +1,28 @@
import React from "react";
import { observer } from "mobx-react-lite";
// types
import { TIssue } from "@plane/types";
// helpers
import { cn } from "@/helpers/common.helper";
import { renderFormattedDate } from "@/helpers/date-time.helper";
// types
type Props = {
issue: TIssue;
isIssueSelected: boolean;
};
export const SpreadsheetUpdatedOnColumn: React.FC<Props> = observer((props: Props) => {
const { issue } = props;
const { issue, isIssueSelected } = props;
return (
<div className="flex h-11 w-full items-center justify-center border-b-[0.5px] border-custom-border-200 text-xs hover:bg-custom-background-80">
<div
className={cn(
"flex h-11 w-full items-center justify-center border-b-[0.5px] border-custom-border-200 text-xs hover:bg-custom-background-80",
{
"bg-custom-primary-100/5 hover:bg-custom-primary-100/10": isIssueSelected,
}
)}
>
{renderFormattedDate(issue.updated_at)}
</div>
);

View File

@ -1,13 +1,14 @@
import { useRef } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { IIssueDisplayProperties, TIssue } from "@plane/types";
// types
import { SPREADSHEET_PROPERTY_DETAILS } from "@/constants/spreadsheet";
import { useEventTracker } from "@/hooks/store";
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
import { IIssueDisplayProperties, TIssue } from "@plane/types";
// constants
import { SPREADSHEET_PROPERTY_DETAILS } from "@/constants/spreadsheet";
// hooks
import { useEventTracker } from "@/hooks/store";
// components
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
type Props = {
displayProperties: IIssueDisplayProperties;
@ -16,10 +17,19 @@ type Props = {
property: keyof IIssueDisplayProperties;
updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
isEstimateEnabled: boolean;
isIssueSelected: boolean;
};
export const IssueColumn = observer((props: Props) => {
const { displayProperties, issueDetail, disableUserActions, property, updateIssue, isEstimateEnabled } = props;
const {
displayProperties,
issueDetail,
disableUserActions,
property,
updateIssue,
isEstimateEnabled,
isIssueSelected,
} = props;
// router
const router = useRouter();
const tableCellRef = useRef<HTMLTableCellElement | null>(null);
@ -58,9 +68,8 @@ export const IssueColumn = observer((props: Props) => {
})
}
disabled={disableUserActions}
onClose={() => {
tableCellRef?.current?.focus();
}}
onClose={() => tableCellRef?.current?.focus()}
isIssueSelected={isIssueSelected}
/>
</td>
</WithDisplayPropertiesHOC>

View File

@ -216,7 +216,12 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => {
return (
<>
<td id={`issue-${issueId}`} ref={cellRef} tabIndex={0} className="sticky left-0 z-10 group/list-block">
<td
id={`issue-${issueId}`}
ref={cellRef}
tabIndex={0}
className="sticky left-0 z-10 group/list-block bg-custom-background-100"
>
<ControlLink
href={`/${workspaceSlug}/projects/${issueDetail.project_id}/issues/${issueId}`}
target="_blank"
@ -228,6 +233,7 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => {
"border border-custom-primary-70 hover:border-custom-primary-70":
getIsIssuePeeked(issueDetail.id) && nestingLevel === peekIssue?.nestingLevel,
"shadow-[8px_22px_22px_10px_rgba(0,0,0,0.05)]": isScrolled.current,
"bg-custom-primary-100/5 hover:bg-custom-primary-100/10": isIssueSelected,
}
)}
disabled={!!issueDetail?.tempId}
@ -239,19 +245,31 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => {
<div className="flex items-center">
{/* bulk ops */}
{projectId && !disableUserActions && (
<div className="flex-shrink-0 grid place-items-center w-3.5">
<MultipleSelectAction
className={cn(
"opacity-0 pointer-events-none group-hover/list-block:opacity-100 group-hover/list-block:pointer-events-auto transition-opacity",
{
"opacity-100 pointer-events-auto": isIssueSelected,
}
)}
groupId={SPREADSHEET_SELECT_GROUP}
id={issueDetail.id}
selectionHelpers={selectionHelpers}
/>
</div>
<Tooltip
tooltipContent={
<>
Only issues within the current
<br />
project can be selected.
</>
}
disabled={issueDetail.project_id === projectId}
>
<div className="flex-shrink-0 grid place-items-center w-3.5">
<MultipleSelectAction
className={cn(
"opacity-0 pointer-events-none group-hover/list-block:opacity-100 group-hover/list-block:pointer-events-auto transition-opacity",
{
"opacity-100 pointer-events-auto": isIssueSelected,
}
)}
groupId={SPREADSHEET_SELECT_GROUP}
id={issueDetail.id}
selectionHelpers={selectionHelpers}
disabled={issueDetail.project_id !== projectId}
/>
</div>
</Tooltip>
)}
<div className="flex size-4 items-center justify-center">
{subIssuesCount > 0 && (
@ -308,6 +326,7 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => {
property={property}
updateIssue={updateIssue}
isEstimateEnabled={isEstimateEnabled}
isIssueSelected={isIssueSelected}
/>
))}
</>

25
web/constants/errors.ts Normal file
View File

@ -0,0 +1,25 @@
export enum EErrorCodes {
"INVALID_ARCHIVE_STATE_GROUP" = 4091,
"INVALID_ISSUE_START_DATE" = 4101,
"INVALID_ISSUE_TARGET_DATE" = 4102,
}
export const ERROR_DETAILS: {
[key in EErrorCodes]: {
title: string;
message: string;
};
} = {
[EErrorCodes.INVALID_ARCHIVE_STATE_GROUP]: {
title: "Unable to archive issues",
message: "Only issues belonging to Completed or Canceled states can be archived.",
},
[EErrorCodes.INVALID_ISSUE_START_DATE]: {
title: "Unable to update issues",
message: "Start date selected succeeds the due date for some issues. Ensure start date to be before the due date.",
},
[EErrorCodes.INVALID_ISSUE_TARGET_DATE]: {
title: "Unable to update issues",
message: "Due date selected precedes the start date for some issues. Ensure due date to be after the start date.",
},
};

View File

@ -35,6 +35,7 @@ export const SPREADSHEET_PROPERTY_DETAILS: {
onClose: () => void;
onChange: (issue: TIssue, data: Partial<TIssue>, updates: any) => void;
disabled: boolean;
isIssueSelected: boolean;
}>;
};
} = {

View File

@ -242,7 +242,7 @@ export class IssueService extends APIService {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/bulk-operation-issues/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
throw error?.response?.data;
});
}

View File

@ -8,7 +8,7 @@ import { IIssueRootStore } from "./root.store";
export type IIssueBulkOperationsStore = {
// actions
bulkUpdateProperties: (workspaceSlug: string, projectId: string, data: TBulkOperationsPayload) => void;
bulkUpdateProperties: (workspaceSlug: string, projectId: string, data: TBulkOperationsPayload) => Promise<void>;
};
export class IssueBulkOperationsStore implements IIssueBulkOperationsStore {