feat: issue highlighting (#3514)

* chore: kanban issue highlight

* chore: issue highlighting added
This commit is contained in:
Anmol Singh Bhatia 2024-01-30 20:12:39 +05:30 committed by GitHub
parent 817737b2c0
commit 888665783e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 89 additions and 22 deletions

View File

@ -1,9 +1,11 @@
import { FC } from "react"; import { FC } from "react";
// hooks // hooks
import { useIssueDetail } from "hooks/store";
import { useChart } from "../hooks"; import { useChart } from "../hooks";
// helpers // helpers
import { ChartAddBlock, ChartDraggable } from "components/gantt-chart"; import { ChartAddBlock, ChartDraggable } from "components/gantt-chart";
import { renderFormattedPayloadDate } from "helpers/date-time.helper"; import { renderFormattedPayloadDate } from "helpers/date-time.helper";
import { cn } from "helpers/common.helper";
// types // types
import { IBlockUpdateData, IGanttBlock } from "../types"; import { IBlockUpdateData, IGanttBlock } from "../types";
@ -31,6 +33,7 @@ export const GanttChartBlocks: FC<GanttChartBlocksProps> = (props) => {
} = props; } = props;
const { activeBlock, dispatch } = useChart(); const { activeBlock, dispatch } = useChart();
const { peekIssue } = useIssueDetail();
// update the active block on hover // update the active block on hover
const updateActiveBlock = (block: IGanttBlock | null) => { const updateActiveBlock = (block: IGanttBlock | null) => {
@ -88,7 +91,14 @@ export const GanttChartBlocks: FC<GanttChartBlocksProps> = (props) => {
return ( return (
<div <div
key={`block-${block.id}`} key={`block-${block.id}`}
className={`h-11 ${activeBlock?.id === block.id ? "bg-custom-background-80" : ""}`} className={cn(
"h-11",
{ "rounded bg-custom-background-80": activeBlock?.id === block.id },
{
"rounded-l border border-r-0 border-custom-primary-70 hover:border-custom-primary-70":
peekIssue?.issueId === block.data.id,
}
)}
onMouseEnter={() => updateActiveBlock(block)} onMouseEnter={() => updateActiveBlock(block)}
onMouseLeave={() => updateActiveBlock(null)} onMouseLeave={() => updateActiveBlock(null)}
> >

View File

@ -3,12 +3,14 @@ import { DragDropContext, Draggable, Droppable, DropResult } from "@hello-pangea
import { MoreVertical } from "lucide-react"; import { MoreVertical } from "lucide-react";
// hooks // hooks
import { useChart } from "components/gantt-chart/hooks"; import { useChart } from "components/gantt-chart/hooks";
import { useIssueDetail } from "hooks/store";
// ui // ui
import { Loader } from "@plane/ui"; import { Loader } from "@plane/ui";
// components // components
import { GanttQuickAddIssueForm, IssueGanttSidebarBlock } from "components/issues"; import { GanttQuickAddIssueForm, IssueGanttSidebarBlock } from "components/issues";
// helpers // helpers
import { findTotalDaysInRange } from "helpers/date-time.helper"; import { findTotalDaysInRange } from "helpers/date-time.helper";
import { cn } from "helpers/common.helper";
// types // types
import { IGanttBlock, IBlockUpdateData } from "components/gantt-chart/types"; import { IGanttBlock, IBlockUpdateData } from "components/gantt-chart/types";
import { TIssue } from "@plane/types"; import { TIssue } from "@plane/types";
@ -45,6 +47,7 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
const { cycleId } = router.query; const { cycleId } = router.query;
const { activeBlock, dispatch } = useChart(); const { activeBlock, dispatch } = useChart();
const { peekIssue } = useIssueDetail();
// update the active block on hover // update the active block on hover
const updateActiveBlock = (block: IGanttBlock | null) => { const updateActiveBlock = (block: IGanttBlock | null) => {
@ -104,7 +107,7 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
{(droppableProvided) => ( {(droppableProvided) => (
<div <div
id={`gantt-sidebar-${cycleId}`} id={`gantt-sidebar-${cycleId}`}
className="mt-3 max-h-full overflow-y-auto pl-2.5" className="mt-[12px] max-h-full overflow-y-auto pl-2.5"
ref={droppableProvided.innerRef} ref={droppableProvided.innerRef}
{...droppableProvided.droppableProps} {...droppableProvided.droppableProps}
> >
@ -130,7 +133,14 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
> >
{(provided, snapshot) => ( {(provided, snapshot) => (
<div <div
className={`h-11 ${snapshot.isDragging ? "rounded bg-custom-background-80" : ""}`} className={cn(
"h-11",
{ "rounded bg-custom-background-80": snapshot.isDragging },
{
"rounded-l border border-r-0 border-custom-primary-70 hover:border-custom-primary-70":
peekIssue?.issueId === block.data.id,
}
)}
onMouseEnter={() => updateActiveBlock(block)} onMouseEnter={() => updateActiveBlock(block)}
onMouseLeave={() => updateActiveBlock(null)} onMouseLeave={() => updateActiveBlock(null)}
ref={provided.innerRef} ref={provided.innerRef}

View File

@ -6,7 +6,8 @@ import { MoreHorizontal } from "lucide-react";
import { Tooltip, ControlLink } from "@plane/ui"; import { Tooltip, ControlLink } from "@plane/ui";
// hooks // hooks
import useOutsideClickDetector from "hooks/use-outside-click-detector"; import useOutsideClickDetector from "hooks/use-outside-click-detector";
// ui // helpers
import { cn } from "helpers/common.helper";
// types // types
import { TIssue, TIssueMap } from "@plane/types"; import { TIssue, TIssueMap } from "@plane/types";
import { useApplication, useIssueDetail, useProject, useProjectState } from "hooks/store"; import { useApplication, useIssueDetail, useProject, useProjectState } from "hooks/store";
@ -26,7 +27,7 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
} = useApplication(); } = useApplication();
const { getProjectById } = useProject(); const { getProjectById } = useProject();
const { getProjectStates } = useProjectState(); const { getProjectStates } = useProjectState();
const { setPeekIssue } = useIssueDetail(); const { peekIssue, setPeekIssue } = useIssueDetail();
// states // states
const [isMenuActive, setIsMenuActive] = useState(false); const [isMenuActive, setIsMenuActive] = useState(false);
@ -84,11 +85,18 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
)} )}
<div <div
className={`group/calendar-block flex h-8 w-full items-center justify-between gap-1.5 rounded border-[0.5px] border-custom-border-100 px-1 py-1.5 shadow-custom-shadow-2xs ${ className={cn(
snapshot.isDragging "group/calendar-block flex h-8 w-full items-center justify-between gap-1.5 rounded border-[0.5px] border-custom-border-200 hover:border-custom-border-400 px-1 py-1.5 ",
? "bg-custom-background-90 shadow-custom-shadow-rg" {
: "bg-custom-background-100 hover:bg-custom-background-90" "bg-custom-background-90 shadow-custom-shadow-rg border-custom-primary-100":
}`} snapshot.isDragging,
},
{ "bg-custom-background-100 hover:bg-custom-background-90": !snapshot.isDragging },
{
"border border-custom-primary-70 hover:border-custom-primary-70":
peekIssue?.issueId === issue.id,
}
)}
> >
<div className="flex h-full items-center gap-1.5"> <div className="flex h-full items-center gap-1.5">
<span <span

View File

@ -1,6 +1,8 @@
import { memo } from "react"; import { memo } from "react";
import { Draggable, DraggableProvided, DraggableStateSnapshot } from "@hello-pangea/dnd"; import { Draggable, DraggableProvided, DraggableStateSnapshot } from "@hello-pangea/dnd";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// hooks
import { useApplication, useIssueDetail, useProject } from "hooks/store";
// components // components
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC"; import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
import { IssueProperties } from "../properties/all-properties"; import { IssueProperties } from "../properties/all-properties";
@ -9,9 +11,11 @@ import { Tooltip, ControlLink } from "@plane/ui";
// types // types
import { TIssue, IIssueDisplayProperties, IIssueMap } from "@plane/types"; import { TIssue, IIssueDisplayProperties, IIssueMap } from "@plane/types";
import { EIssueActions } from "../types"; import { EIssueActions } from "../types";
import { useApplication, useIssueDetail, useProject } from "hooks/store"; // helper
import { cn } from "helpers/common.helper";
interface IssueBlockProps { interface IssueBlockProps {
peekIssueId?: string;
issueId: string; issueId: string;
issuesMap: IIssueMap; issuesMap: IIssueMap;
displayProperties: IIssueDisplayProperties | undefined; displayProperties: IIssueDisplayProperties | undefined;
@ -86,6 +90,7 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = observer((prop
export const KanbanIssueBlock: React.FC<IssueBlockProps> = memo((props) => { export const KanbanIssueBlock: React.FC<IssueBlockProps> = memo((props) => {
const { const {
peekIssueId,
issueId, issueId,
issuesMap, issuesMap,
displayProperties, displayProperties,
@ -121,9 +126,12 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = memo((props) => {
<div className="absolute left-0 top-0 z-[99999] h-full w-full animate-pulse bg-custom-background-100/20" /> <div className="absolute left-0 top-0 z-[99999] h-full w-full animate-pulse bg-custom-background-100/20" />
)} )}
<div <div
className={`space-y-2 rounded border-[0.5px] border-custom-border-200 bg-custom-background-100 px-3 py-2 text-sm shadow-custom-shadow-2xs transition-all ${ className={cn(
isDragDisabled ? "" : "hover:cursor-grab" "space-y-2 rounded border-[0.5px] border-custom-border-200 bg-custom-background-100 px-3 py-2 text-sm transition-all hover:border-custom-border-400",
} ${snapshot.isDragging ? `border-custom-primary-100` : `border-transparent`}`} { "hover:cursor-grab": !isDragDisabled },
{ "border-custom-primary-100": snapshot.isDragging },
{ "border border-custom-primary-70 hover:border-custom-primary-70": peekIssueId === issue.id }
)}
> >
<KanbanIssueDetailsBlock <KanbanIssueDetailsBlock
issue={issue} issue={issue}

View File

@ -9,6 +9,7 @@ interface IssueBlocksListProps {
sub_group_id: string; sub_group_id: string;
columnId: string; columnId: string;
issuesMap: IIssueMap; issuesMap: IIssueMap;
peekIssueId?: string;
issueIds: string[]; issueIds: string[];
displayProperties: IIssueDisplayProperties | undefined; displayProperties: IIssueDisplayProperties | undefined;
isDragDisabled: boolean; isDragDisabled: boolean;
@ -22,6 +23,7 @@ const KanbanIssueBlocksListMemo: React.FC<IssueBlocksListProps> = (props) => {
sub_group_id, sub_group_id,
columnId, columnId,
issuesMap, issuesMap,
peekIssueId,
issueIds, issueIds,
displayProperties, displayProperties,
isDragDisabled, isDragDisabled,
@ -44,6 +46,7 @@ const KanbanIssueBlocksListMemo: React.FC<IssueBlocksListProps> = (props) => {
return ( return (
<KanbanIssueBlock <KanbanIssueBlock
key={draggableId} key={draggableId}
peekIssueId={peekIssueId}
issueId={issueId} issueId={issueId}
issuesMap={issuesMap} issuesMap={issuesMap}
displayProperties={displayProperties} displayProperties={displayProperties}

View File

@ -1,6 +1,6 @@
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// hooks // hooks
import { useKanbanView, useLabel, useMember, useProject, useProjectState } from "hooks/store"; import { useIssueDetail, useKanbanView, useLabel, useMember, useProject, useProjectState } from "hooks/store";
// components // components
import { HeaderGroupByCard } from "./headers/group-by-card"; import { HeaderGroupByCard } from "./headers/group-by-card";
import { KanbanGroup } from "./kanban-group"; import { KanbanGroup } from "./kanban-group";
@ -73,6 +73,7 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
const project = useProject(); const project = useProject();
const label = useLabel(); const label = useLabel();
const projectState = useProjectState(); const projectState = useProjectState();
const { peekIssue } = useIssueDetail();
const list = getGroupByColumns(group_by as GroupByColumnTypes, project, label, projectState, member); const list = getGroupByColumns(group_by as GroupByColumnTypes, project, label, projectState, member);
@ -120,6 +121,7 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
groupId={_list.id} groupId={_list.id}
issuesMap={issuesMap} issuesMap={issuesMap}
issueIds={issueIds} issueIds={issueIds}
peekIssueId={peekIssue?.issueId ?? ""}
displayProperties={displayProperties} displayProperties={displayProperties}
sub_group_by={sub_group_by} sub_group_by={sub_group_by}
group_by={group_by} group_by={group_by}

View File

@ -17,6 +17,7 @@ import { EIssueActions } from "../types";
interface IKanbanGroup { interface IKanbanGroup {
groupId: string; groupId: string;
issuesMap: IIssueMap; issuesMap: IIssueMap;
peekIssueId?: string;
issueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues; issueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues;
displayProperties: IIssueDisplayProperties | undefined; displayProperties: IIssueDisplayProperties | undefined;
sub_group_by: string | null; sub_group_by: string | null;
@ -47,6 +48,7 @@ export const KanbanGroup = (props: IKanbanGroup) => {
issuesMap, issuesMap,
displayProperties, displayProperties,
issueIds, issueIds,
peekIssueId,
isDragDisabled, isDragDisabled,
handleIssues, handleIssues,
quickActions, quickActions,
@ -118,6 +120,7 @@ export const KanbanGroup = (props: IKanbanGroup) => {
sub_group_id={sub_group_id} sub_group_id={sub_group_id}
columnId={groupId} columnId={groupId}
issuesMap={issuesMap} issuesMap={issuesMap}
peekIssueId={peekIssueId}
issueIds={(issueIds as TGroupedIssues)?.[groupId] || []} issueIds={(issueIds as TGroupedIssues)?.[groupId] || []}
displayProperties={displayProperties} displayProperties={displayProperties}
isDragDisabled={isDragDisabled} isDragDisabled={isDragDisabled}

View File

@ -5,6 +5,8 @@ import { IssueProperties } from "../properties/all-properties";
import { useApplication, useIssueDetail, useProject } from "hooks/store"; import { useApplication, useIssueDetail, useProject } from "hooks/store";
// ui // ui
import { Spinner, Tooltip, ControlLink } from "@plane/ui"; import { Spinner, Tooltip, ControlLink } from "@plane/ui";
// helper
import { cn } from "helpers/common.helper";
// types // types
import { TIssue, IIssueDisplayProperties, TIssueMap } from "@plane/types"; import { TIssue, IIssueDisplayProperties, TIssueMap } from "@plane/types";
import { EIssueActions } from "../types"; import { EIssueActions } from "../types";
@ -25,7 +27,7 @@ export const IssueBlock: React.FC<IssueBlockProps> = observer((props: IssueBlock
router: { workspaceSlug, projectId }, router: { workspaceSlug, projectId },
} = useApplication(); } = useApplication();
const { getProjectById } = useProject(); const { getProjectById } = useProject();
const { setPeekIssue } = useIssueDetail(); const { peekIssue, setPeekIssue } = useIssueDetail();
const updateIssue = (issueToUpdate: TIssue) => { const updateIssue = (issueToUpdate: TIssue) => {
handleIssues(issueToUpdate, EIssueActions.UPDATE); handleIssues(issueToUpdate, EIssueActions.UPDATE);
@ -47,7 +49,15 @@ export const IssueBlock: React.FC<IssueBlockProps> = observer((props: IssueBlock
return ( return (
<> <>
<div className="relative flex items-center gap-3 bg-custom-background-100 p-3 text-sm"> <div
className={cn(
"relative flex items-center gap-3 bg-custom-background-100 p-3 text-sm border border-transparent border-b-custom-border-200 last:border-b-transparent",
{
"border border-custom-primary-70 hover:border-custom-primary-70":
peekIssue && peekIssue.issueId === issue.id,
}
)}
>
{displayProperties && displayProperties?.key && ( {displayProperties && displayProperties?.key && (
<div className="flex-shrink-0 text-xs font-medium text-custom-text-300"> <div className="flex-shrink-0 text-xs font-medium text-custom-text-300">
{projectDetails?.identifier}-{issue.sequence_id} {projectDetails?.identifier}-{issue.sequence_id}

View File

@ -18,7 +18,7 @@ export const IssueBlocksList: FC<Props> = (props) => {
const { issueIds, issuesMap, handleIssues, quickActions, displayProperties, canEditProperties } = props; const { issueIds, issuesMap, handleIssues, quickActions, displayProperties, canEditProperties } = props;
return ( return (
<div className="relative h-full w-full divide-y-[0.5px] divide-custom-border-200"> <div className="relative h-full w-full">
{issueIds && issueIds.length > 0 ? ( {issueIds && issueIds.length > 0 ? (
issueIds.map((issueId: string) => { issueIds.map((issueId: string) => {
if (!issueId) return null; if (!issueId) return null;

View File

@ -12,6 +12,8 @@ import { ControlLink, Tooltip } from "@plane/ui";
// hooks // hooks
import useOutsideClickDetector from "hooks/use-outside-click-detector"; import useOutsideClickDetector from "hooks/use-outside-click-detector";
import { useIssueDetail, useProject } from "hooks/store"; import { useIssueDetail, useProject } from "hooks/store";
// helper
import { cn } from "helpers/common.helper";
// types // types
import { IIssueDisplayProperties, TIssue } from "@plane/types"; import { IIssueDisplayProperties, TIssue } from "@plane/types";
import { EIssueActions } from "../types"; import { EIssueActions } from "../types";
@ -48,7 +50,7 @@ export const SpreadsheetIssueRow = observer((props: Props) => {
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
//hooks //hooks
const { getProjectById } = useProject(); const { getProjectById } = useProject();
const { setPeekIssue } = useIssueDetail(); const { peekIssue, setPeekIssue } = useIssueDetail();
// states // states
const [isMenuActive, setIsMenuActive] = useState(false); const [isMenuActive, setIsMenuActive] = useState(false);
const [isExpanded, setExpanded] = useState<boolean>(false); const [isExpanded, setExpanded] = useState<boolean>(false);
@ -95,9 +97,20 @@ export const SpreadsheetIssueRow = observer((props: Props) => {
return ( return (
<> <>
<tr> <tr
className={cn({
"border border-custom-primary-70 hover:border-custom-primary-70": peekIssue?.issueId === issueDetail.id,
})}
>
{/* first column/ issue name and key column */} {/* first column/ issue name and key column */}
<td className="sticky group left-0 h-11 w-[28rem] flex items-center bg-custom-background-100 text-sm after:absolute after:w-full after:bottom-[-1px] after:border after:border-l-0 after:border-custom-border-100 before:absolute before:h-full before:right-0 before:border before:border-l-0 before:border-custom-border-100"> <td
className={cn(
"sticky group left-0 h-11 w-[28rem] flex items-center bg-custom-background-100 text-sm after:absolute border-r-[0.5px] border-custom-border-200",
{
"border-b-[0.5px]": peekIssue?.issueId !== issueDetail.id,
}
)}
>
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="key"> <WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="key">
<div <div
className="flex min-w-min items-center gap-1.5 px-4 py-2.5 pr-0" className="flex min-w-min items-center gap-1.5 px-4 py-2.5 pr-0"

View File

@ -36,7 +36,7 @@ export const SpreadsheetTable = observer((props: Props) => {
} = props; } = props;
return ( return (
<table className="divide-x-[0.5px] divide-custom-border-200 overflow-y-auto"> <table className="overflow-y-auto">
<SpreadsheetHeader <SpreadsheetHeader
displayProperties={displayProperties} displayProperties={displayProperties}
displayFilters={displayFilters} displayFilters={displayFilters}