mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: update UI
This commit is contained in:
parent
12000904ee
commit
a76e02045b
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -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));
|
||||
};
|
||||
|
||||
|
@ -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.",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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,
|
||||
}
|
||||
)}
|
||||
>
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
|
@ -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
25
web/constants/errors.ts
Normal 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.",
|
||||
},
|
||||
};
|
@ -35,6 +35,7 @@ export const SPREADSHEET_PROPERTY_DETAILS: {
|
||||
onClose: () => void;
|
||||
onChange: (issue: TIssue, data: Partial<TIssue>, updates: any) => void;
|
||||
disabled: boolean;
|
||||
isIssueSelected: boolean;
|
||||
}>;
|
||||
};
|
||||
} = {
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user