diff --git a/plane-src/apps/api/plane/app/serializers/issue.py b/plane-src/apps/api/plane/app/serializers/issue.py index c5af9b9..5efae03 100644 --- a/plane-src/apps/api/plane/app/serializers/issue.py +++ b/plane-src/apps/api/plane/app/serializers/issue.py @@ -846,9 +846,13 @@ class IssueListDetailSerializer(serializers.Serializer): "created_at": instance.created_at, "updated_at": instance.updated_at, "created_by": instance.created_by_id, + "created_by_avatar_url": getattr(instance, "created_by_avatar_url", None), + "created_by_detail": UserLiteSerializer(instance.created_by).data if instance.created_by else None, + "created_by_display_name": getattr(instance, "created_by_display_name", None), "updated_by": instance.updated_by_id, "is_draft": instance.is_draft, "archived_at": instance.archived_at, + "source_project_name": getattr(instance, "source_project_name", None), # Computed fields "cycle_id": instance.cycle_id, "module_ids": self.get_module_ids(instance), diff --git a/plane-src/apps/api/plane/app/views/issue/base.py b/plane-src/apps/api/plane/app/views/issue/base.py index bb33180..045c83e 100644 --- a/plane-src/apps/api/plane/app/views/issue/base.py +++ b/plane-src/apps/api/plane/app/views/issue/base.py @@ -113,6 +113,18 @@ class IssueListEndpoint(BaseAPIView): CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1] ) ) + .annotate( + created_by_display_name=F("created_by__display_name"), + created_by_avatar_url=F("created_by__avatar"), + ) + .annotate( + source_project_name=Subquery( + IntakeIssue.objects.filter( + issue_id=OuterRef("id"), + extra__bridge="external-contours", + ).values("extra__source_project_name")[:1] + ) + ) .annotate( link_count=IssueLink.objects.filter(issue=OuterRef("id")) .order_by() @@ -180,12 +192,15 @@ class IssueListEndpoint(BaseAPIView): "created_at", "updated_at", "created_by", + "created_by_display_name", + "created_by_avatar_url", "updated_by", "attachment_count", "link_count", "is_draft", "archived_at", "deleted_at", + "source_project_name", ) datetime_fields = ["created_at", "updated_at"] issues = user_timezone_converter(issues, datetime_fields, request.user.user_timezone) @@ -206,7 +221,7 @@ class IssueViewSet(BaseViewSet): issues = Issue.issue_objects.filter( project_id=self.kwargs.get("project_id"), workspace__slug=self.kwargs.get("slug"), - ).distinct() + ).select_related("created_by").distinct() return issues @@ -217,6 +232,18 @@ class IssueViewSet(BaseViewSet): CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1] ) ) + .annotate( + created_by_display_name=F("created_by__display_name"), + created_by_avatar_url=F("created_by__avatar"), + ) + .annotate( + source_project_name=Subquery( + IntakeIssue.objects.filter( + issue_id=OuterRef("id"), + extra__bridge="external-contours", + ).values("extra__source_project_name")[:1] + ) + ) .annotate( link_count=Subquery( IssueLink.objects.filter(issue=OuterRef("id")) @@ -445,6 +472,8 @@ class IssueViewSet(BaseViewSet): "created_at", "updated_at", "created_by", + "created_by_display_name", + "created_by_avatar_url", "updated_by", "attachment_count", "link_count", diff --git a/plane-src/apps/api/plane/utils/grouper.py b/plane-src/apps/api/plane/utils/grouper.py index ab00879..f92fadc 100644 --- a/plane-src/apps/api/plane/utils/grouper.py +++ b/plane-src/apps/api/plane/utils/grouper.py @@ -121,12 +121,15 @@ def issue_on_results( "created_at", "updated_at", "created_by", + "created_by_display_name", + "created_by_avatar_url", "updated_by", "attachment_count", "link_count", "is_draft", "archived_at", "state__group", + "source_project_name", ] if group_by in FIELD_MAPPER: diff --git a/plane-src/apps/web/core/components/dropdowns/date.tsx b/plane-src/apps/web/core/components/dropdowns/date.tsx index 59f1ec9..5797cf0 100644 --- a/plane-src/apps/web/core/components/dropdowns/date.tsx +++ b/plane-src/apps/web/core/components/dropdowns/date.tsx @@ -28,6 +28,7 @@ import { BUTTON_VARIANTS_WITH_TEXT } from "./constants"; import type { TDropdownProps } from "./types"; type Props = TDropdownProps & { + button?: React.ReactNode; clearIconClassName?: string; defaultOpen?: boolean; optionsClassName?: string; @@ -47,6 +48,7 @@ type Props = TDropdownProps & { export const DateDropdown = observer(function DateDropdown(props: Props) { const { buttonClassName = "", + button, buttonContainerClassName, buttonVariant, className = "", @@ -121,47 +123,61 @@ export const DateDropdown = observer(function DateDropdown(props: Props) { if (maxDate) disabledDays.push({ after: maxDate }); const comboButton = ( - + ) : ( + )} - ref={setReferenceElement} - onClick={handleOnClick} - disabled={disabled} - > - - {!hideIcon && icon} - {BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && ( - - {value ? renderFormattedDate(value, formatToken) : placeholder} - - )} - {isClearable && !disabled && isDateSelected && ( - { - e.stopPropagation(); - e.preventDefault(); - onChange(null); - }} - /> - )} - - + ); return ( diff --git a/plane-src/apps/web/core/components/issues/issue-layouts/kanban/base-kanban-root.tsx b/plane-src/apps/web/core/components/issues/issue-layouts/kanban/base-kanban-root.tsx index 8971573..f22eba9 100644 --- a/plane-src/apps/web/core/components/issues/issue-layouts/kanban/base-kanban-root.tsx +++ b/plane-src/apps/web/core/components/issues/issue-layouts/kanban/base-kanban-root.tsx @@ -32,6 +32,7 @@ import type { IQuickActionProps, TRenderQuickActions } from "../list/list-view-t import { getSourceFromDropPayload } from "../utils"; import { KanBan } from "./default"; import { KanBanSwimLanes } from "./swimlanes"; +import type { TKanbanCardVariant } from "./types"; export type KanbanStoreType = | EIssuesStoreType.PROJECT @@ -47,6 +48,7 @@ export interface IBaseKanBanLayout { QuickActions: FC; addIssuesToView?: (issueIds: string[]) => Promise; canEditPropertiesBasedOnProject?: (projectId: string) => boolean; + cardVariant?: TKanbanCardVariant; isCompletedCycle?: boolean; viewId?: string | undefined; isEpic?: boolean; @@ -57,6 +59,7 @@ export const BaseKanBanRoot = observer(function BaseKanBanRoot(props: IBaseKanBa QuickActions, addIssuesToView, canEditPropertiesBasedOnProject, + cardVariant = "default", isCompletedCycle = false, viewId, isEpic = false, @@ -284,6 +287,7 @@ export const BaseKanBanRoot = observer(function BaseKanBanRoot(props: IBaseKanBa disableIssueCreation={!enableIssueCreation || !isEditingAllowed || isCompletedCycle} canEditProperties={canEditProperties} addIssuesToView={addIssuesToView} + cardVariant={cardVariant} scrollableContainerRef={scrollableContainerRef} handleOnDrop={handleOnDrop} loadMoreIssues={fetchMoreIssues} diff --git a/plane-src/apps/web/core/components/issues/issue-layouts/kanban/block.tsx b/plane-src/apps/web/core/components/issues/issue-layouts/kanban/block.tsx index c953132..a691a43 100644 --- a/plane-src/apps/web/core/components/issues/issue-layouts/kanban/block.tsx +++ b/plane-src/apps/web/core/components/issues/issue-layouts/kanban/block.tsx @@ -38,6 +38,8 @@ import { IssueStats } from "@/plane-web/components/issues/issue-layouts/issue-st import type { TRenderQuickActions } from "../list/list-view-types"; import { IssueProperties } from "../properties/all-properties"; import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC"; +import { InternalContourKanbanCard } from "./internal-contour-card"; +import type { TKanbanCardVariant } from "./types"; interface IssueBlockProps { issueId: string; @@ -54,6 +56,7 @@ interface IssueBlockProps { scrollableContainerRef?: MutableRefObject; shouldRenderByDefault?: boolean; isEpic?: boolean; + cardVariant?: TKanbanCardVariant; } interface IssueDetailsBlockProps { @@ -64,10 +67,22 @@ interface IssueDetailsBlockProps { quickActions: TRenderQuickActions; isReadOnly: boolean; isEpic?: boolean; + isActive?: boolean; + cardVariant?: TKanbanCardVariant; } const KanbanIssueDetailsBlock = observer(function KanbanIssueDetailsBlock(props: IssueDetailsBlockProps) { - const { cardRef, issue, updateIssue, quickActions, isReadOnly, displayProperties, isEpic = false } = props; + const { + cardRef, + issue, + updateIssue, + quickActions, + isReadOnly, + displayProperties, + isEpic = false, + isActive = false, + cardVariant = "default", + } = props; // refs const menuActionRef = useRef(null); // states @@ -97,6 +112,20 @@ const KanbanIssueDetailsBlock = observer(function KanbanIssueDetailsBlock(props: useOutsideClickDetector(menuActionRef, () => setIsMenuActive(false)); + if (cardVariant === "internal-contour") { + return ( + + ); + } + return ( <>
@@ -168,6 +197,7 @@ export const KanbanIssueBlock = observer(function KanbanIssueBlock(props: IssueB scrollableContainerRef, shouldRenderByDefault, isEpic = false, + cardVariant = "default", } = props; const cardRef = useRef(null); @@ -194,6 +224,7 @@ export const KanbanIssueBlock = observer(function KanbanIssueBlock(props: IssueB const isDragAllowed = canDragIssuesInCurrentGrouping && !issue?.tempId && canEditIssueProperties; const projectIdentifier = getProjectIdentifierById(issue?.project_id); + const isPeeked = issue ? getIsIssuePeeked(issue.id) : false; const workItemLink = generateWorkItemLink({ workspaceSlug, @@ -275,10 +306,18 @@ export const KanbanIssueBlock = observer(function KanbanIssueBlock(props: IssueB href={workItemLink} ref={cardRef} className={cn( - "block w-full rounded-lg border border-subtle bg-layer-2 p-3 text-13 shadow-raised-100 outline-[0.5px] outline-transparent transition-all hover:border-strong hover:shadow-raised-200", + "block w-full text-13 transition-all", + cardVariant === "internal-contour" + ? "rounded-[28px] border-0 p-4 shadow-none outline-none ring-0 hover:border-0 hover:outline-none hover:ring-0" + : "rounded-lg border border-subtle bg-layer-2 p-3 shadow-raised-100 outline-[0.5px] outline-transparent hover:border-strong hover:shadow-raised-200", { "hover:cursor-pointer": isDragAllowed }, - { "border border-accent-strong hover:border-accent-strong": getIsIssuePeeked(issue.id) }, - { "z-[100] bg-layer-1": isCurrentBlockDragging } + { + "bg-[#C3FF66] text-[#111111]": cardVariant === "internal-contour" && isPeeked, + "bg-[#2A2B2E] text-white": cardVariant === "internal-contour" && !isPeeked, + "border border-accent-strong hover:border-accent-strong": cardVariant !== "internal-contour" && isPeeked, + }, + { "z-[100] bg-layer-1": isCurrentBlockDragging && cardVariant !== "internal-contour" }, + { "z-[100] bg-[#C3FF66]": isCurrentBlockDragging && cardVariant === "internal-contour" } )} onClick={() => handleIssuePeekOverview(issue)} disabled={!!issue?.tempId} @@ -286,7 +325,7 @@ export const KanbanIssueBlock = observer(function KanbanIssueBlock(props: IssueB diff --git a/plane-src/apps/web/core/components/issues/issue-layouts/kanban/blocks-list.tsx b/plane-src/apps/web/core/components/issues/issue-layouts/kanban/blocks-list.tsx index 9361e4c..616cf9e 100644 --- a/plane-src/apps/web/core/components/issues/issue-layouts/kanban/blocks-list.tsx +++ b/plane-src/apps/web/core/components/issues/issue-layouts/kanban/blocks-list.tsx @@ -11,6 +11,7 @@ import type { TIssue, IIssueDisplayProperties, IIssueMap } from "@plane/types"; // local imports import type { TRenderQuickActions } from "../list/list-view-types"; import { KanbanIssueBlock } from "./block"; +import type { TKanbanCardVariant } from "./types"; interface IssueBlocksListProps { sub_group_id: string; @@ -21,6 +22,7 @@ interface IssueBlocksListProps { updateIssue: ((projectId: string | null, issueId: string, data: Partial) => Promise) | undefined; quickActions: TRenderQuickActions; canEditProperties: (projectId: string | undefined) => boolean; + cardVariant?: TKanbanCardVariant; canDropOverIssue: boolean; canDragIssuesInCurrentGrouping: boolean; scrollableContainerRef?: MutableRefObject; @@ -39,6 +41,7 @@ export const KanbanIssueBlocksList = observer(function KanbanIssueBlocksList(pro updateIssue, quickActions, canEditProperties, + cardVariant = "default", scrollableContainerRef, isEpic = false, } = props; @@ -66,6 +69,7 @@ export const KanbanIssueBlocksList = observer(function KanbanIssueBlocksList(pro updateIssue={updateIssue} quickActions={quickActions} draggableId={draggableId} + cardVariant={cardVariant} canDropOverIssue={canDropOverIssue} canDragIssuesInCurrentGrouping={canDragIssuesInCurrentGrouping} canEditProperties={canEditProperties} diff --git a/plane-src/apps/web/core/components/issues/issue-layouts/kanban/default.tsx b/plane-src/apps/web/core/components/issues/issue-layouts/kanban/default.tsx index b7326ee..0439fce 100644 --- a/plane-src/apps/web/core/components/issues/issue-layouts/kanban/default.tsx +++ b/plane-src/apps/web/core/components/issues/issue-layouts/kanban/default.tsx @@ -35,6 +35,7 @@ import { getGroupByColumns, isWorkspaceLevel, getApproximateCardHeight } from ". // components import { HeaderGroupByCard } from "./headers/group-by-card"; import { KanbanGroup } from "./kanban-group"; +import type { TKanbanCardVariant } from "./types"; export interface IKanBan { issuesMap: IIssueMap; @@ -62,6 +63,7 @@ export interface IKanBan { disableIssueCreation?: boolean; addIssuesToView?: (issueIds: string[]) => Promise; canEditProperties: (projectId: string | undefined) => boolean; + cardVariant?: TKanbanCardVariant; scrollableContainerRef?: MutableRefObject; handleOnDrop: (source: GroupDropLocation, destination: GroupDropLocation) => Promise; showEmptyGroup?: boolean; @@ -88,6 +90,7 @@ export const KanBan = observer(function KanBan(props: IKanBan) { disableIssueCreation, addIssuesToView, canEditProperties, + cardVariant = "default", scrollableContainerRef, handleOnDrop, showEmptyGroup = true, @@ -224,6 +227,7 @@ export const KanBan = observer(function KanBan(props: IKanBan) { quickAddCallback={quickAddCallback} disableIssueCreation={disableIssueCreation} canEditProperties={canEditProperties} + cardVariant={cardVariant} scrollableContainerRef={scrollableContainerRef} loadMoreIssues={loadMoreIssues} handleOnDrop={handleOnDrop} diff --git a/plane-src/apps/web/core/components/issues/issue-layouts/kanban/internal-contour-card.tsx b/plane-src/apps/web/core/components/issues/issue-layouts/kanban/internal-contour-card.tsx new file mode 100644 index 0000000..cc8bce9 --- /dev/null +++ b/plane-src/apps/web/core/components/issues/issue-layouts/kanban/internal-contour-card.tsx @@ -0,0 +1,202 @@ +/** + * Copyright (c) 2023-present Plane Software, Inc. and contributors + * SPDX-License-Identifier: AGPL-3.0-only + * See the LICENSE file for details. + */ + +import { useMemo, useRef, useState } from "react"; +import { CalendarDays, MoreHorizontal } from "lucide-react"; +import { observer } from "mobx-react"; +import { useTranslation } from "@plane/i18n"; +import { useOutsideClickDetector } from "@plane/hooks"; +import { PriorityIcon, StateGroupIcon } from "@plane/propel/icons"; +import type { IIssueDisplayProperties, TIssue } from "@plane/types"; +import { Avatar } from "@plane/ui"; +import { cn, getFileURL, renderFormattedDate, renderFormattedPayloadDate } from "@plane/utils"; +import { DateDropdown } from "@/components/dropdowns/date"; +import { ButtonAvatars } from "@/components/dropdowns/member/avatar"; +import { MemberDropdown } from "@/components/dropdowns/member/dropdown"; +import { PriorityDropdown } from "@/components/dropdowns/priority"; +import { StateDropdown } from "@/components/dropdowns/state/dropdown"; +import { useMember } from "@/hooks/store/use-member"; +import { useProject } from "@/hooks/store/use-project"; +import { useProjectState } from "@/hooks/store/use-project-state"; +import { usePlatformOS } from "@/hooks/use-platform-os"; +import type { TRenderQuickActions } from "../list/list-view-types"; + +type Props = { + cardRef: React.RefObject; + issue: TIssue; + displayProperties: IIssueDisplayProperties | undefined; + updateIssue: ((projectId: string | null, issueId: string, data: Partial) => Promise) | undefined; + quickActions: TRenderQuickActions; + isReadOnly: boolean; + isActive: boolean; +}; + +const basePillClasses = + "inline-flex items-center gap-1.5 rounded-full border-0 px-2.5 py-1 text-[11px] font-medium shadow-none outline-none transition-colors"; + +export const InternalContourKanbanCard = observer(function InternalContourKanbanCard(props: Props) { + const { cardRef, issue, updateIssue, quickActions, isReadOnly, isActive } = props; + const { t } = useTranslation(); + const { isMobile } = usePlatformOS(); + const { getUserDetails } = useMember(); + const { getProjectById } = useProject(); + const { getStateById, getProjectStateIds } = useProjectState(); + + const menuActionRef = useRef(null); + const [isMenuActive, setIsMenuActive] = useState(false); + + const creatorDetails = useMemo(() => { + if (issue.created_by_detail) return issue.created_by_detail; + if (issue.created_by && getUserDetails(issue.created_by)) return getUserDetails(issue.created_by); + if (issue.created_by_display_name || issue.created_by_avatar_url) { + return { + id: issue.created_by, + display_name: issue.created_by_display_name ?? t("common.none"), + avatar_url: issue.created_by_avatar_url ?? "", + first_name: "", + last_name: "", + is_bot: false, + }; + } + return undefined; + }, [ + getUserDetails, + issue.created_by, + issue.created_by_avatar_url, + issue.created_by_detail, + issue.created_by_display_name, + t, + ]); + + const sourceContourName = issue.source_project_name ?? getProjectById(issue.project_id)?.name ?? t("common.none"); + const selectedState = getStateById(issue.state_id); + const projectStateIds = issue.project_id ? getProjectStateIds(issue.project_id) : []; + const foregroundClasses = isActive ? "text-[#111111]" : "text-white"; + const subtleTextClasses = isActive ? "text-[#2F4721]" : "text-[#B3B3B8]"; + const pillBackgroundClasses = isActive ? "bg-black/10 text-[#111111]" : "bg-[#1B1B1F] text-white"; + const iconBubbleClasses = isActive ? "bg-black text-[#C3FF66]" : "bg-[#111214] text-white"; + const statusIconColor = selectedState?.color ?? (isActive ? "#111111" : "var(--text-color-primary)"); + + const handleEventPropagation = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + }; + + useOutsideClickDetector(menuActionRef, () => setIsMenuActive(false)); + + const creatorName = creatorDetails?.display_name ?? t("common.none"); + const dueDateLabel = issue.target_date ? renderFormattedDate(issue.target_date, "d MMM, yyyy") : t("common.none"); + + const customActionButton = ( +
setIsMenuActive(!isMenuActive)} + > + +
+ ); + + return ( +
+
+ {quickActions({ + issue, + parentRef: cardRef, + customActionButton, + })} + updateIssue?.(issue.project_id ?? null, issue.id, { priority })} + disabled={isReadOnly || !updateIssue} + button={ +
+ +
+ } + /> + updateIssue?.(issue.project_id ?? null, issue.id, { state_id: stateId })} + disabled={isReadOnly || !updateIssue} + button={ +
+ +
+ } + /> +
+ +
+ +
+
{creatorName}
+
{sourceContourName}
+
+
+ +
+
{issue.name}
+
+ +
+ updateIssue?.(issue.project_id ?? null, issue.id, { assignee_ids: assigneeIds })} + disabled={isReadOnly || !updateIssue} + button={ +
+ +
+ } + /> + +
+ + updateIssue?.(issue.project_id ?? null, issue.id, { + target_date: targetDate ? renderFormattedPayloadDate(targetDate) : null, + }) + } + disabled={isReadOnly || !updateIssue} + button={ +
+ + {dueDateLabel} +
+ } + /> +
+
+
+ ); +}); diff --git a/plane-src/apps/web/core/components/issues/issue-layouts/kanban/kanban-group.tsx b/plane-src/apps/web/core/components/issues/issue-layouts/kanban/kanban-group.tsx index 1358110..537e404 100644 --- a/plane-src/apps/web/core/components/issues/issue-layouts/kanban/kanban-group.tsx +++ b/plane-src/apps/web/core/components/issues/issue-layouts/kanban/kanban-group.tsx @@ -46,6 +46,7 @@ import { GroupDragOverlay } from "../group-drag-overlay"; import type { TRenderQuickActions } from "../list/list-view-types"; import { KanbanQuickAddIssueButton, QuickAddIssueRoot } from "../quick-add"; import { KanbanIssueBlocksList } from "./blocks-list"; +import type { TKanbanCardVariant } from "./types"; interface IKanbanGroup { groupId: string; @@ -65,6 +66,7 @@ interface IKanbanGroup { loadMoreIssues: (groupId?: string, subGroupId?: string) => void; disableIssueCreation?: boolean; canEditProperties: (projectId: string | undefined) => boolean; + cardVariant?: TKanbanCardVariant; groupByVisibilityToggle?: boolean; scrollableContainerRef?: MutableRefObject; handleOnDrop: (source: GroupDropLocation, destination: GroupDropLocation) => Promise; @@ -87,6 +89,7 @@ export const KanbanGroup = observer(function KanbanGroup(props: IKanbanGroup) { updateIssue, quickActions, canEditProperties, + cardVariant = "default", loadMoreIssues, enableQuickIssueCreate, disableIssueCreation, @@ -305,6 +308,7 @@ export const KanbanGroup = observer(function KanbanGroup(props: IKanbanGroup) { updateIssue={updateIssue} quickActions={quickActions} canEditProperties={canEditProperties} + cardVariant={cardVariant} scrollableContainerRef={scrollableContainerRef} canDropOverIssue={!canOverlayBeVisible} canDragIssuesInCurrentGrouping={canDragIssuesInCurrentGrouping} diff --git a/plane-src/apps/web/core/components/issues/issue-layouts/kanban/roots/project-root.tsx b/plane-src/apps/web/core/components/issues/issue-layouts/kanban/roots/project-root.tsx index 6d691ad..88c187f 100644 --- a/plane-src/apps/web/core/components/issues/issue-layouts/kanban/roots/project-root.tsx +++ b/plane-src/apps/web/core/components/issues/issue-layouts/kanban/roots/project-root.tsx @@ -32,6 +32,7 @@ export const KanBanLayout = observer(function KanBanLayout() { ); }); diff --git a/plane-src/apps/web/core/components/issues/issue-layouts/kanban/swimlanes.tsx b/plane-src/apps/web/core/components/issues/issue-layouts/kanban/swimlanes.tsx index 84998f3..fadbea5 100644 --- a/plane-src/apps/web/core/components/issues/issue-layouts/kanban/swimlanes.tsx +++ b/plane-src/apps/web/core/components/issues/issue-layouts/kanban/swimlanes.tsx @@ -31,6 +31,7 @@ import { getGroupByColumns, isWorkspaceLevel } from "../utils"; import { KanBan } from "./default"; import { HeaderGroupByCard } from "./headers/group-by-card"; import { HeaderSubGroupByCard } from "./headers/sub-group-by-card"; +import type { TKanbanCardVariant } from "./types"; interface ISubGroupSwimlaneHeader { collapsedGroups: TIssueKanbanFilters; @@ -107,6 +108,7 @@ const SubGroupSwimlaneHeader = observer(function SubGroupSwimlaneHeader({ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader { addIssuesToView?: (issueIds: string[]) => Promise; canEditProperties: (projectId: string | undefined) => boolean; + cardVariant?: TKanbanCardVariant; collapsedGroups: TIssueKanbanFilters; disableIssueCreation?: boolean; displayProperties: IIssueDisplayProperties | undefined; @@ -134,6 +136,7 @@ const SubGroupSwimlane = observer(function SubGroupSwimlane(props: ISubGroupSwim const { addIssuesToView, canEditProperties, + cardVariant = "default", collapsedGroups, disableIssueCreation, displayProperties, @@ -216,6 +219,7 @@ const SubGroupSwimlane = observer(function SubGroupSwimlane(props: ISubGroupSwim enableQuickIssueCreate={enableQuickIssueCreate} disableIssueCreation={disableIssueCreation} canEditProperties={canEditProperties} + cardVariant={cardVariant} addIssuesToView={addIssuesToView} quickAddCallback={quickAddCallback} scrollableContainerRef={scrollableContainerRef} @@ -238,6 +242,7 @@ const SubGroupSwimlane = observer(function SubGroupSwimlane(props: ISubGroupSwim export interface IKanBanSwimLanes { addIssuesToView?: (issueIds: string[]) => Promise; canEditProperties: (projectId: string | undefined) => boolean; + cardVariant?: TKanbanCardVariant; collapsedGroups: TIssueKanbanFilters; disableIssueCreation?: boolean; displayProperties: IIssueDisplayProperties | undefined; @@ -282,6 +287,7 @@ export const KanBanSwimLanes = observer(function KanBanSwimLanes(props: IKanBanS disableIssueCreation, enableQuickIssueCreate, canEditProperties, + cardVariant = "default", addIssuesToView, quickAddCallback, scrollableContainerRef, @@ -341,6 +347,7 @@ export const KanBanSwimLanes = observer(function KanBanSwimLanes(props: IKanBanS enableQuickIssueCreate={enableQuickIssueCreate} addIssuesToView={addIssuesToView} canEditProperties={canEditProperties} + cardVariant={cardVariant} quickAddCallback={quickAddCallback} scrollableContainerRef={scrollableContainerRef} isEpic={isEpic} diff --git a/plane-src/apps/web/core/components/issues/issue-layouts/kanban/types.ts b/plane-src/apps/web/core/components/issues/issue-layouts/kanban/types.ts new file mode 100644 index 0000000..ac8766c --- /dev/null +++ b/plane-src/apps/web/core/components/issues/issue-layouts/kanban/types.ts @@ -0,0 +1 @@ +export type TKanbanCardVariant = "default" | "internal-contour"; diff --git a/plane-src/packages/types/src/issues/issue.ts b/plane-src/packages/types/src/issues/issue.ts index 8054b4c..7626561 100644 --- a/plane-src/packages/types/src/issues/issue.ts +++ b/plane-src/packages/types/src/issues/issue.ts @@ -6,6 +6,7 @@ import type { TIssuePriorities } from "../issues"; import type { TStateGroups } from "../state"; +import type { IUserLite } from "../users"; import type { TIssuePublicComment } from "./activity/issue_comment"; import type { TIssueAttachment } from "./issue_attachment"; import type { TIssueLink } from "./issue_link"; @@ -90,12 +91,16 @@ type IssueRelation = { export type TIssue = TBaseIssue & { description_html?: string; is_subscribed?: boolean; + created_by_avatar_url?: string | null; + created_by_detail?: Pick | null; + created_by_display_name?: string | null; parent?: Partial; issue_reactions?: TIssueReaction[]; issue_attachments?: TIssueAttachment[]; issue_link?: TIssueLink[]; issue_relation?: IssueRelation[]; issue_related?: IssueRelation[]; + source_project_name?: string | null; // tempId is used for optimistic updates. It is not a part of the API response. tempId?: string; // sourceIssueId is used to store the original issue id when creating a copy of an issue. Used in cloning property values. It is not a part of the API response.