ФУНКЦИИ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: стабилизация контуров, досок и Voice Tasker

This commit is contained in:
DCCONSTRUCTIONS
2026-04-24 23:34:20 +03:00
parent 597480adb9
commit 7209d2caab
37 changed files with 960 additions and 390 deletions
@@ -7,6 +7,7 @@
import { observer } from "mobx-react";
import { Combobox } from "@headlessui/react";
// hooks
import { getStateGroupColor } from "@plane/propel/icons";
import type { ISearchIssueResponse } from "@plane/types";
// plane web hooks
import { IssueIdentifier } from "@/plane-web/components/issues/issue-details/issue-identifier";
@@ -19,7 +20,7 @@ interface Props {
export const BulkDeleteIssuesModalItem = observer(function BulkDeleteIssuesModalItem(props: Props) {
const { issue, canDeleteIssueIds } = props;
const color = issue.state__color;
const color = getStateGroupColor(issue.state__group, issue.state__color);
return (
<Combobox.Option
@@ -11,7 +11,7 @@ import { Combobox } from "@headlessui/react";
import { useTranslation } from "@plane/i18n";
// types
import { Button } from "@plane/propel/button";
import { SearchIcon, CloseIcon } from "@plane/propel/icons";
import { SearchIcon, CloseIcon, getStateGroupColor } from "@plane/propel/icons";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import { Tooltip } from "@plane/propel/tooltip";
import type { ISearchIssueResponse, TProjectIssuesSearchParams } from "@plane/types";
@@ -263,7 +263,7 @@ export function ExistingIssuesListModal(props: Props) {
<span
className="block h-1.5 w-1.5 flex-shrink-0 rounded-full"
style={{
backgroundColor: issue.state__color,
backgroundColor: getStateGroupColor(issue.state__group, issue.state__color),
}}
/>
<span className="flex-shrink-0">
@@ -81,7 +81,7 @@ export const BlockRow = observer(function BlockRow(props: Props) {
return (
<div
className="relative w-max min-w-full"
className="nodedc-project-gantt-row relative w-max min-w-full"
onMouseEnter={() => updateActiveBlockId(blockId)}
onMouseLeave={() => updateActiveBlockId(null)}
style={{
@@ -89,19 +89,18 @@ export const BlockRow = observer(function BlockRow(props: Props) {
}}
>
<div
className={cn("relative h-full bg-layer-transparent hover:bg-layer-transparent-hover", {
"rounded-l-sm border border-r-0 border-accent-strong": getIsIssuePeeked(block.data.id),
"bg-layer-transparent-hover": isBlockHoveredOn,
"bg-accent-primary/5 hover:bg-accent-primary/10": isBlockSelected,
"bg-accent-primary/10": isBlockSelected && isBlockHoveredOn,
"border border-r-0 border-strong-1": isBlockFocused,
className={cn("nodedc-project-gantt-row-bg relative h-full", {
"nodedc-project-gantt-row-peeked": getIsIssuePeeked(block.data.id),
"nodedc-project-gantt-row-hovered": isBlockHoveredOn,
"nodedc-project-gantt-row-selected": isBlockSelected,
"nodedc-project-gantt-row-focused": isBlockFocused,
})}
>
{isBlockVisibleOnChart
? isHidden && (
<button
type="button"
className="sticky z-[5] grid h-8 w-8 translate-y-1.5 cursor-pointer place-items-center rounded-sm border border-strong bg-layer-1 text-secondary hover:text-primary"
className="nodedc-project-gantt-jump-button sticky z-[5] grid h-8 w-8 translate-y-1.5 cursor-pointer place-items-center text-secondary hover:text-primary"
style={{
left: `${SIDEBAR_WIDTH + 4}px`,
}}
@@ -83,7 +83,7 @@ export const GanttChartBlock = observer(function GanttChartBlock(props: Props) {
horizontalOffset={100}
verticalOffset={200}
classNames="flex h-full w-full items-center"
placeholderChildren={<div className="h-8 w-full rounded-sm bg-layer-1" />}
placeholderChildren={<div className="nodedc-project-gantt-block-placeholder h-8 w-full" />}
shouldRecordHeights={false}
forceRender={isCurrentDependencyDragging}
>
@@ -5,7 +5,7 @@
*/
import { observer } from "mobx-react";
import { Expand, Shrink } from "lucide-react";
import { CalendarDays, Expand, Shrink } from "lucide-react";
import { useTranslation } from "@plane/i18n";
// plane
import type { TGanttViews } from "@plane/types";
@@ -25,62 +25,81 @@ type Props = {
handleChartView: (view: TGanttViews) => void;
handleToday: () => void;
loaderTitle: string;
title: string;
toggleFullScreenMode: () => void;
showToday: boolean;
};
const GANTT_VIEW_SHORT_LABELS: Record<TGanttViews, string> = {
week: "1W",
month: "1M",
quarter: "3M",
};
export const GanttChartHeader = observer(function GanttChartHeader(props: Props) {
const { t } = useTranslation();
const { blockIds, fullScreenMode, handleChartView, handleToday, loaderTitle, toggleFullScreenMode, showToday } =
props;
const {
blockIds,
fullScreenMode,
handleChartView,
handleToday,
loaderTitle,
title,
toggleFullScreenMode,
showToday,
} = props;
// chart hook
const { currentView } = useTimeLineChartStore();
return (
<Row
className="relative flex w-full flex-shrink-0 flex-wrap items-center gap-2 bg-surface-1 py-2 whitespace-nowrap"
className="nodedc-project-gantt-toolbar relative flex w-full flex-shrink-0 flex-wrap items-center justify-between gap-3 whitespace-nowrap"
style={{ height: `${GANTT_BREADCRUMBS_HEIGHT}px` }}
>
<div className="ml-auto">
<div className="ml-auto text-11 font-medium text-tertiary">
{blockIds ? `${blockIds.length} ${loaderTitle}` : t("common.loading")}
<div className="flex min-w-0 items-center gap-3">
<div className="nodedc-project-gantt-toolbar-icon">
<CalendarDays className="size-4" />
</div>
<div className="min-w-0">
<div className="truncate text-14 font-semibold text-primary">{title}</div>
<div className="mt-0.5 truncate text-11 font-medium text-tertiary">
{currentView ? GANTT_VIEW_SHORT_LABELS[currentView] : null}
{currentView ? " / " : null}
{blockIds ? `${blockIds.length} ${loaderTitle}` : t("common.loading")}
</div>
</div>
</div>
<div className="flex flex-wrap items-center gap-2">
<div className="flex flex-wrap items-center justify-end gap-2">
{showToday && (
<button
type="button"
className="nodedc-project-gantt-chip nodedc-project-gantt-chip-live"
onClick={handleToday}
>
Live
</button>
)}
{VIEWS_LIST.map((chartView: any) => (
<div
<button
key={chartView?.key}
className={cn(
"cursor-pointer rounded-md bg-layer-transparent p-1 px-2 text-11 hover:bg-layer-transparent-hover",
{
"bg-layer-transparent-selected": currentView === chartView?.key,
}
)}
type="button"
aria-label={t(chartView?.i18n_title)}
aria-pressed={currentView === chartView?.key}
className={cn("nodedc-project-gantt-chip", {
"nodedc-project-gantt-chip-active": currentView === chartView?.key,
})}
onClick={() => handleChartView(chartView?.key)}
>
{t(chartView?.i18n_title)}
</div>
{GANTT_VIEW_SHORT_LABELS[chartView?.key as TGanttViews]}
</button>
))}
</div>
{showToday && (
<button
type="button"
className="rounded-md bg-layer-transparent p-1 px-2 text-11 hover:bg-layer-transparent-hover"
onClick={handleToday}
>
{t("common.today")}
<button type="button" className="nodedc-project-gantt-round-button" onClick={toggleFullScreenMode}>
{fullScreenMode ? <Shrink className="size-4" /> : <Expand className="size-4" />}
</button>
)}
<button
type="button"
className="flex items-center justify-center rounded-md border border-subtle bg-layer-transparent p-1 transition-all hover:bg-layer-transparent-hover"
onClick={toggleFullScreenMode}
>
{fullScreenMode ? <Shrink className="h-4 w-4" /> : <Expand className="h-4 w-4" />}
</button>
</div>
</Row>
);
});
@@ -177,7 +177,7 @@ export const GanttChartMainContent = observer(function GanttChartMainContent(pro
// DO NOT REMOVE THE ID
id="gantt-container"
className={cn(
"vertical-scrollbar horizontal-scrollbar flex scrollbar-lg h-full w-full overflow-auto border-t-[0.5px] border-subtle",
"nodedc-project-gantt-scroll vertical-scrollbar horizontal-scrollbar flex scrollbar-lg h-full w-full overflow-auto",
{
"mb-8": bottomSpacing,
}
@@ -199,11 +199,11 @@ export const GanttChartMainContent = observer(function GanttChartMainContent(pro
showAllBlocks={showAllBlocks}
isEpic={isEpic}
/>
<div className="relative h-max min-h-full flex-shrink-0 flex-grow">
<div className="nodedc-project-gantt-stage relative h-max min-h-full flex-shrink-0 flex-grow">
<ActiveChartView />
{currentViewData && (
<div
className="relative h-full"
className="nodedc-project-gantt-layer relative h-full"
style={{
width: `${itemsContainerWidth}px`,
transform: `translateY(${HEADER_HEIGHT}px)`,
@@ -180,8 +180,8 @@ export const ChartViewRoot = observer(function ChartViewRoot(props: ChartViewRoo
const content = (
<div
className={cn("shadow relative flex h-full flex-col rounded-xs bg-surface-1 select-none", {
"inset-0 z-[25] bg-surface-1": fullScreenMode,
className={cn("nodedc-project-gantt-card shadow relative flex h-full flex-col select-none", {
"fixed inset-0 z-[25] rounded-none": fullScreenMode,
"border-[0.5px] border-subtle": border,
})}
>
@@ -192,6 +192,7 @@ export const ChartViewRoot = observer(function ChartViewRoot(props: ChartViewRoo
handleChartView={(key) => updateCurrentViewRenderPayload(null, key)}
handleToday={handleToday}
loaderTitle={loaderTitle}
title={title}
showToday={showToday}
/>
<GanttChartMainContent
@@ -30,12 +30,12 @@ export const MonthChartView = observer(function MonthChartView(_props: any) {
const marginLeftDays = getNumberOfDaysBetweenTwoDates(monthsStartDate, weeksStartDate);
return (
<div className="absolute top-0 left-0 flex h-max min-h-full w-max">
<div className="nodedc-project-gantt-calendar absolute top-0 left-0 flex h-max min-h-full w-max">
{currentViewData && (
<div className="relative flex flex-col outline-[0.25px] outline-subtle-1">
<div className="nodedc-project-gantt-calendar-group relative flex flex-col">
{/** Header Div */}
<div
className="sticky top-0 z-[5] w-full flex-shrink-0 bg-surface-1"
className="nodedc-project-gantt-calendar-header sticky top-0 z-[5] w-full flex-shrink-0"
style={{
height: `${HEADER_HEIGHT}px`,
}}
@@ -45,18 +45,22 @@ export const MonthChartView = observer(function MonthChartView(_props: any) {
{months?.map((monthBlock) => (
<div
key={`month-${monthBlock?.month}-${monthBlock?.year}`}
className="flex outline-[0.5px] outline-subtle-1"
className="flex"
style={{ width: `${monthBlock.days * currentViewData?.data.dayWidth}px` }}
>
<div
className="sticky z-[1] m-1 flex items-center bg-surface-1 px-3 py-1 text-14 font-regular whitespace-nowrap text-secondary capitalize"
className="nodedc-project-gantt-period-label sticky z-[1] m-1 flex items-center px-3 py-1 text-12 font-semibold whitespace-nowrap text-secondary capitalize"
style={{
left: `${SIDEBAR_WIDTH}px`,
}}
>
{monthBlock?.title}
{monthBlock.today && (
<span className={cn("ml-2 rounded-sm bg-accent-primary px-1 text-9 font-medium text-on-color")}>
<span
className={cn(
"ml-2 rounded-full bg-[rgb(var(--nodedc-card-active-rgb))] px-2 py-0.5 text-9 font-semibold text-[rgb(var(--nodedc-on-card-active-rgb))]"
)}
>
Current
</span>
)}
@@ -70,9 +74,9 @@ export const MonthChartView = observer(function MonthChartView(_props: any) {
<div
key={`sub-title-${weekBlock.startDate.toString()}-${weekBlock.endDate.toString()}`}
className={cn(
"flex flex-shrink-0 justify-between px-2 py-1 text-center capitalize outline-[0.25px] outline-subtle-1",
"nodedc-project-gantt-subcell flex flex-shrink-0 justify-between px-2 py-1 text-center capitalize",
{
"bg-accent-primary/20": weekBlock.today,
"nodedc-project-gantt-subcell-today": weekBlock.today,
}
)}
style={{ width: `${currentViewData?.data.dayWidth * 7}px` }}
@@ -80,7 +84,8 @@ export const MonthChartView = observer(function MonthChartView(_props: any) {
<div className="space-x-1 text-11 font-medium text-placeholder">
<span
className={cn({
"rounded-sm bg-accent-primary px-1 text-on-color": weekBlock.today,
"rounded-full bg-[rgb(var(--nodedc-card-active-rgb))] px-1.5 text-[rgb(var(--nodedc-on-card-active-rgb))]":
weekBlock.today,
})}
>
{weekBlock.startDate.getDate()}-{weekBlock.endDate.getDate()}
@@ -96,8 +101,8 @@ export const MonthChartView = observer(function MonthChartView(_props: any) {
{weeks?.map((weekBlock) => (
<div
key={`column-${weekBlock.startDate.toString()}-${weekBlock.endDate.toString()}`}
className={cn("h-full overflow-hidden outline-[0.25px] outline-subtle", {
"bg-accent-primary/20": weekBlock.today,
className={cn("nodedc-project-gantt-column h-full overflow-hidden", {
"nodedc-project-gantt-column-today": weekBlock.today,
})}
style={{ width: `${currentViewData?.data.dayWidth * 7}px` }}
/>
@@ -21,16 +21,16 @@ export const QuarterChartView = observer(function QuarterChartView(_props: any)
const quarterBlocks: IQuarterMonthBlock[] = groupMonthsToQuarters(monthBlocks);
return (
<div className={`absolute top-0 left-0 flex h-max min-h-full w-max`}>
<div className="nodedc-project-gantt-calendar absolute top-0 left-0 flex h-max min-h-full w-max">
{currentViewData &&
quarterBlocks?.map((quarterBlock, rootIndex) => (
<div
key={`month-${quarterBlock.quarterNumber}-${quarterBlock.year}`}
className="relative flex flex-col outline-[0.25px] outline-subtle-1"
className="nodedc-project-gantt-calendar-group relative flex flex-col"
>
{/** Header Div */}
<div
className="sticky top-0 z-[5] w-full flex-shrink-0 bg-surface-1 outline-[1px] outline-subtle-1"
className="nodedc-project-gantt-calendar-header sticky top-0 z-[5] w-full flex-shrink-0"
style={{
height: `${HEADER_HEIGHT}px`,
}}
@@ -38,19 +38,23 @@ export const QuarterChartView = observer(function QuarterChartView(_props: any)
{/** Main Quarter Title */}
<div className="inline-flex h-7 w-full justify-between">
<div
className="sticky z-[1] my-1 flex items-center bg-surface-1 px-3 py-1 text-14 font-regular whitespace-nowrap text-secondary capitalize"
className="nodedc-project-gantt-period-label sticky z-[1] my-1 flex items-center px-3 py-1 text-12 font-semibold whitespace-nowrap text-secondary capitalize"
style={{
left: `${SIDEBAR_WIDTH}px`,
}}
>
{quarterBlock?.title}
{quarterBlock.today && (
<span className={cn("ml-2 rounded-sm bg-accent-primary px-1 text-9 font-medium text-on-color")}>
<span
className={cn(
"ml-2 rounded-full bg-[rgb(var(--nodedc-card-active-rgb))] px-2 py-0.5 text-9 font-semibold text-[rgb(var(--nodedc-on-card-active-rgb))]"
)}
>
Current
</span>
)}
</div>
<div className="sticky px-3 py-2 text-11 whitespace-nowrap text-placeholder capitalize">
<div className="nodedc-project-gantt-period-meta sticky px-3 py-2 text-11 whitespace-nowrap text-placeholder capitalize">
{quarterBlock.shortTitle}
</div>
</div>
@@ -60,9 +64,9 @@ export const QuarterChartView = observer(function QuarterChartView(_props: any)
<div
key={`sub-title-${rootIndex}-${index}`}
className={cn(
"flex flex-shrink-0 justify-center text-center capitalize outline-[0.25px] outline-subtle-1",
"nodedc-project-gantt-subcell flex flex-shrink-0 justify-center text-center capitalize",
{
"bg-accent-primary/20": monthBlock.today,
"nodedc-project-gantt-subcell-today": monthBlock.today,
}
)}
style={{ width: `${currentViewData?.data.dayWidth * monthBlock.days}px` }}
@@ -70,7 +74,8 @@ export const QuarterChartView = observer(function QuarterChartView(_props: any)
<div className="flex h-full items-center justify-center space-x-1 text-11 font-medium">
<span
className={cn({
"rounded-lg bg-accent-primary px-2 text-on-color": monthBlock.today,
"rounded-full bg-[rgb(var(--nodedc-card-active-rgb))] px-2 text-[rgb(var(--nodedc-on-card-active-rgb))]":
monthBlock.today,
})}
>
{monthBlock.monthData.shortTitle}
@@ -85,8 +90,8 @@ export const QuarterChartView = observer(function QuarterChartView(_props: any)
{quarterBlock?.children?.map((monthBlock, index) => (
<div
key={`column-${rootIndex}-${index}`}
className={cn("h-full overflow-hidden outline-[0.25px] outline-subtle", {
"bg-accent-primary/20": monthBlock.today,
className={cn("nodedc-project-gantt-column h-full overflow-hidden", {
"nodedc-project-gantt-column-today": monthBlock.today,
})}
style={{ width: `${currentViewData?.data.dayWidth * monthBlock.days}px` }}
/>
@@ -18,16 +18,16 @@ export const WeekChartView = observer(function WeekChartView(_props: any) {
const weekBlocks: IWeekBlock[] = renderView;
return (
<div className={`absolute top-0 left-0 flex h-max min-h-full w-max`}>
<div className="nodedc-project-gantt-calendar absolute top-0 left-0 flex h-max min-h-full w-max">
{currentViewData &&
weekBlocks?.map((block, rootIndex) => (
<div
key={`month-${block?.startDate.toString()}-${block?.endDate.toString()}`}
className="relative flex flex-col outline-[0.25px] outline-subtle-1"
className="nodedc-project-gantt-calendar-group relative flex flex-col"
>
{/** Header Div */}
<div
className="sticky top-0 z-[5] w-full flex-shrink-0 bg-surface-1 outline-[1px] outline-subtle-1"
className="nodedc-project-gantt-calendar-header sticky top-0 z-[5] w-full flex-shrink-0"
style={{
height: `${HEADER_HEIGHT}px`,
}}
@@ -35,14 +35,14 @@ export const WeekChartView = observer(function WeekChartView(_props: any) {
{/** Main Months Title */}
<div className="inline-flex h-7 w-full justify-between">
<div
className="sticky z-[1] m-1 flex items-center bg-surface-1 px-3 py-1 text-13 font-regular whitespace-nowrap text-secondary capitalize"
className="nodedc-project-gantt-period-label sticky z-[1] m-1 flex items-center px-3 py-1 text-12 font-semibold whitespace-nowrap text-secondary capitalize"
style={{
left: `${SIDEBAR_WIDTH}px`,
}}
>
{block?.title}
</div>
<div className="sticky px-3 py-2 text-11 whitespace-nowrap text-placeholder capitalize">
<div className="nodedc-project-gantt-period-meta sticky px-3 py-2 text-11 whitespace-nowrap text-placeholder capitalize">
{block?.weekData?.title}
</div>
</div>
@@ -52,9 +52,9 @@ export const WeekChartView = observer(function WeekChartView(_props: any) {
<div
key={`sub-title-${rootIndex}-${index}`}
className={cn(
"flex flex-shrink-0 justify-between p-1 text-center capitalize outline-[0.25px] outline-subtle-1",
"nodedc-project-gantt-subcell flex flex-shrink-0 justify-between p-1 text-center capitalize",
{
"bg-accent-primary/20": weekDay.today,
"nodedc-project-gantt-subcell-today": weekDay.today,
}
)}
style={{ width: `${currentViewData?.data.dayWidth}px` }}
@@ -63,7 +63,8 @@ export const WeekChartView = observer(function WeekChartView(_props: any) {
<div className="space-x-1 text-11 font-medium">
<span
className={cn({
"rounded-sm bg-accent-primary px-1 text-on-color": weekDay.today,
"rounded-full bg-[rgb(var(--nodedc-card-active-rgb))] px-1.5 text-[rgb(var(--nodedc-on-card-active-rgb))]":
weekDay.today,
})}
>
{weekDay.date.getDate()}
@@ -74,17 +75,17 @@ export const WeekChartView = observer(function WeekChartView(_props: any) {
</div>
</div>
{/** Day Columns */}
<div className="flex h-full w-full flex-grow bg-surface-1">
<div className="flex h-full w-full flex-grow">
{block?.children?.map((weekDay, index) => (
<div
key={`column-${rootIndex}-${index}`}
className={cn("h-full overflow-hidden outline-[0.25px] outline-subtle", {
"bg-accent-primary/20": weekDay.today,
className={cn("nodedc-project-gantt-column h-full overflow-hidden", {
"nodedc-project-gantt-column-today": weekDay.today,
})}
style={{ width: `${currentViewData?.data.dayWidth}px` }}
>
{["sat", "sun"].includes(weekDay?.dayData?.shortTitle) && (
<div className="h-full bg-surface-2 outline-[0.25px] outline-strong" />
<div className="nodedc-project-gantt-column-weekend h-full" />
)}
</div>
))}
@@ -4,11 +4,11 @@
* See the LICENSE file for details.
*/
export const BLOCK_HEIGHT = 44;
export const BLOCK_HEIGHT = 46;
export const HEADER_HEIGHT = 48;
export const HEADER_HEIGHT = 56;
export const GANTT_BREADCRUMBS_HEIGHT = 40;
export const GANTT_BREADCRUMBS_HEIGHT = 60;
export const SIDEBAR_WIDTH = 360;
@@ -39,7 +39,7 @@ export const LeftResizable = observer(function LeftResizable(props: LeftResizabl
<>
{(isHovering || isLeftResizing) && dateString && (
<div className="absolute -left-36 flex h-full w-32 items-center justify-end text-11 font-regular text-tertiary">
<div className="rounded-sm bg-accent-subtle px-2 py-1">{dateString}</div>
<div className="nodedc-project-gantt-resize-tooltip px-2 py-1">{dateString}</div>
</div>
)}
<div
@@ -56,7 +56,7 @@ export const LeftResizable = observer(function LeftResizable(props: LeftResizabl
/>
<div
className={cn(
"absolute top-1/2 left-1 z-[5] h-7 w-1 -translate-y-1/2 rounded-xs bg-surface-1 opacity-0 transition-all duration-300 group-hover:opacity-100",
"nodedc-project-gantt-resize-handle absolute top-1/2 left-1 z-[5] h-7 w-1 -translate-y-1/2 opacity-0 transition-all duration-300 group-hover:opacity-100",
{
"-left-1.5 opacity-100": isLeftResizing,
}
@@ -39,7 +39,7 @@ export const RightResizable = observer(function RightResizable(props: RightResiz
<>
{(isHovering || isRightResizing) && dateString && (
<div className="absolute -right-36 z-[10] flex h-full w-32 items-center justify-start text-11 font-regular text-tertiary">
<div className="rounded-sm bg-accent-subtle px-2 py-1">{dateString}</div>
<div className="nodedc-project-gantt-resize-tooltip px-2 py-1">{dateString}</div>
</div>
)}
<div
@@ -54,7 +54,7 @@ export const RightResizable = observer(function RightResizable(props: RightResiz
/>
<div
className={cn(
"absolute top-1/2 right-1 z-[5] h-7 w-1 -translate-y-1/2 rounded-xs bg-surface-1 opacity-0 transition-all duration-300 group-hover:opacity-100",
"nodedc-project-gantt-resize-handle absolute top-1/2 right-1 z-[5] h-7 w-1 -translate-y-1/2 opacity-0 transition-all duration-300 group-hover:opacity-100",
{
"-right-1.5 opacity-100": isRightResizing,
}
@@ -43,7 +43,7 @@ export const ChartDraggable = observer(function ChartDraggable(props: Props) {
} = props;
return (
<div className="group relative z-[5] inline-flex h-full w-full cursor-pointer items-center font-medium transition-all">
<div className="nodedc-project-gantt-draggable group relative z-[5] inline-flex h-full w-full cursor-pointer items-center font-medium transition-all">
{/* left resize drag handle */}
{(typeof enableDependency === "function" ? enableDependency(block.id) : enableDependency) && (
<LeftDependencyDraggable block={block} ganttContainerRef={ganttContainerRef} />
@@ -55,7 +55,7 @@ export const ChartDraggable = observer(function ChartDraggable(props: Props) {
position={block.position}
/>
<div
className={cn("relative z-[6] flex h-8 w-full items-center rounded-sm", {
className={cn("nodedc-project-gantt-draggable-shell relative z-[6] flex h-8 w-full items-center", {
"pointer-events-none": isMoving,
})}
onMouseDown={(e) => enableBlockMove && handleBlockDrag(e, "move")}
@@ -44,23 +44,19 @@ export const IssuesSidebarBlock = observer(function IssuesSidebarBlock(props: Pr
return (
<div
className={cn("group/list-block", {
"rounded-sm bg-layer-1": isDragging,
"rounded-l-sm border border-r-0 border-accent-strong": getIsIssuePeeked(block.data.id),
"border border-r-0 border-strong-1": isIssueFocused,
className={cn("nodedc-project-gantt-sidebar-block group/list-block", {
"nodedc-project-gantt-sidebar-block-dragging": isDragging,
"nodedc-project-gantt-sidebar-block-peeked": getIsIssuePeeked(block.data.id),
"nodedc-project-gantt-sidebar-block-focused": isIssueFocused,
})}
onMouseEnter={() => updateActiveBlockId(block.id)}
onMouseLeave={() => updateActiveBlockId(null)}
>
<Row
className={cn(
"group flex w-full items-center gap-2 bg-layer-transparent pr-4 hover:bg-layer-transparent-hover",
{
"bg-layer-transparent-hover": isBlockHoveredOn,
"bg-accent-primary/5 hover:bg-accent-primary/10": isIssueSelected,
"bg-accent-primary/10": isIssueSelected && isBlockHoveredOn,
}
)}
className={cn("nodedc-project-gantt-sidebar-row group flex w-full items-center gap-2 pr-4", {
"nodedc-project-gantt-sidebar-row-hovered": isBlockHoveredOn,
"nodedc-project-gantt-sidebar-row-selected": isIssueSelected,
})}
style={{
height: `${BLOCK_HEIGHT}px`,
}}
@@ -77,7 +77,7 @@ export const IssueGanttSidebar = observer(function IssueGanttSidebar(props: Prop
};
return (
<div>
<div className="nodedc-project-gantt-sidebar-list">
{blockIds ? (
<>
{blockIds.map((blockId, index) => {
@@ -117,7 +117,7 @@ export const IssueGanttSidebar = observer(function IssueGanttSidebar(props: Prop
})}
{canLoadMoreBlocks && (
<div ref={setIntersectionElement} className="p-2">
<div className="flex h-10 w-full animate-pulse items-center justify-between gap-1.5 rounded-sm bg-layer-1 px-4 py-1.5 md:h-8 md:px-1" />
<div className="nodedc-project-gantt-sidebar-loader flex h-10 w-full animate-pulse items-center justify-between gap-1.5 px-4 py-1.5 md:h-8 md:px-1" />
</div>
)}
</>
@@ -56,14 +56,14 @@ export const GanttChartSidebar = observer(function GanttChartSidebar(props: Prop
<Row
// DO NOT REMOVE THE ID
id="gantt-sidebar"
className="sticky left-0 z-10 h-max min-h-full flex-shrink-0 border-r-[0.5px] border-subtle-1 bg-surface-1"
className="nodedc-project-gantt-sidebar sticky left-0 z-10 h-max min-h-full flex-shrink-0"
style={{
width: `${SIDEBAR_WIDTH}px`,
}}
variant={ERowVariant.HUGGING}
>
<Row
className="group/list-header sticky top-0 z-10 box-border flex flex-shrink-0 items-end justify-between gap-2 border-b-[0.5px] border-subtle-1 bg-surface-1 pr-4 pb-2 text-13 font-medium text-tertiary"
className="nodedc-project-gantt-sidebar-header group/list-header sticky top-0 z-10 box-border flex flex-shrink-0 items-end justify-between gap-2 pr-4 pb-3 text-12 font-semibold text-tertiary"
style={{
height: `${HEADER_HEIGHT}px`,
}}
@@ -88,7 +88,7 @@ export const GanttChartSidebar = observer(function GanttChartSidebar(props: Prop
<h6>{t("common.duration")}</h6>
</Row>
<Row variant={ERowVariant.HUGGING} className="h-max min-h-full bg-surface-1">
<Row variant={ERowVariant.HUGGING} className="nodedc-project-gantt-sidebar-body h-max min-h-full">
{sidebarToRender &&
sidebarToRender({
title,
@@ -34,11 +34,13 @@ const EXTERNAL_DECK_LIMIT = 10;
const INTERNAL_CURSOR = `${INTERNAL_DECK_LIMIT}:0:0`;
type HomeRecentIssueDecksProps = {
compact?: boolean;
project?: THomeProjectData;
workspaceSlug: string;
};
type DeckSectionProps = {
compact?: boolean;
count: number;
description: string;
emptyDescription: string;
@@ -49,6 +51,7 @@ type DeckSectionProps = {
};
type InternalIssueCardProps = {
compact?: boolean;
isActive: boolean;
issue: TIssue;
onSelect: () => void;
@@ -56,6 +59,7 @@ type InternalIssueCardProps = {
};
type ExternalIssueCardProps = {
compact?: boolean;
isActive: boolean;
onSelect: () => void;
project: THomeProjectData;
@@ -76,14 +80,14 @@ const sortByRecentCreatedDate = <
};
const DeckSection = (props: DeckSectionProps) => {
const { count, description, emptyDescription, emptyTitle, isLoading, items, title } = props;
const { compact = false, count, description, emptyDescription, emptyTitle, isLoading, items, title } = props;
return (
<div className="space-y-3">
<div className="flex flex-wrap items-center justify-between gap-3">
<div>
<div className="text-14 font-semibold text-primary">{title}</div>
<div className="text-12 text-secondary">{description}</div>
{!compact && <div className="text-12 text-secondary">{description}</div>}
</div>
<div className="nodedc-toolbar-pill inline-flex items-center gap-2">
<Sparkles className="size-3.5" />
@@ -91,16 +95,24 @@ const DeckSection = (props: DeckSectionProps) => {
</div>
</div>
<div className="nodedc-home-task-deck-scroller">
<div className="flex min-h-[236px] items-end px-1 py-4">
<div
className={cn("nodedc-home-task-deck-scroller", {
"nodedc-home-task-deck-scroller-compact": compact,
})}
>
<div
className={cn("flex items-start px-1 py-2", {
"min-h-[236px] gap-4": !compact,
"nodedc-home-task-deck-row-compact min-h-[172px]": compact,
})}
>
{isLoading
? Array.from({ length: 4 }, (_, index) => (
<div
key={`skeleton-${title}-${index}`}
className={cn("nodedc-home-task-card nodedc-home-task-card-skeleton animate-pulse", {
"-ml-16": index > 0,
"nodedc-home-task-card-compact": compact,
})}
style={{ zIndex: 5 - index }}
/>
))
: items}
@@ -118,7 +130,7 @@ const DeckSection = (props: DeckSectionProps) => {
};
const HomeInternalContourDeckCard = observer(function HomeInternalContourDeckCard(props: InternalIssueCardProps) {
const { isActive, issue, onSelect, project } = props;
const { compact = false, isActive, issue, onSelect, project } = props;
const { t } = useTranslation();
const { getProjectById } = useProject();
const { getUserDetails } = useMember();
@@ -183,7 +195,7 @@ const HomeInternalContourDeckCard = observer(function HomeInternalContourDeckCar
const footer = (
<>
<div className={cn("inline-flex min-h-9 items-center rounded-full pl-1 pr-2", pillBackgroundClasses)}>
<div className={cn("inline-flex min-h-9 items-center rounded-full pr-2 pl-1", pillBackgroundClasses)}>
{(issue.assignee_ids?.length ?? 0) > 0 ? (
<ButtonAvatars showTooltip={false} userIds={issue.assignee_ids ?? []} size="sm" />
) : (
@@ -191,7 +203,12 @@ const HomeInternalContourDeckCard = observer(function HomeInternalContourDeckCar
)}
</div>
<div className={cn("inline-flex min-h-9 items-center gap-1.5 rounded-full px-2.5 py-1 text-[11px] font-medium", pillBackgroundClasses)}>
<div
className={cn(
"inline-flex min-h-9 items-center gap-1.5 rounded-full px-2.5 py-1 text-[11px] font-medium",
pillBackgroundClasses
)}
>
<CalendarDays className="h-3.5 w-3.5" />
<span className="truncate">{dueDateLabel}</span>
</div>
@@ -199,14 +216,24 @@ const HomeInternalContourDeckCard = observer(function HomeInternalContourDeckCar
);
return (
<button type="button" className="nodedc-home-task-card" data-active={isActive} onClick={onSelect} title={issue.name}>
<button
type="button"
className={cn("nodedc-home-task-card", { "nodedc-home-task-card-compact": compact })}
data-active={isActive}
onClick={onSelect}
title={issue.name}
>
<NodedcWorkItemCard
isActive={isActive}
surfaceClassName={cn(
"nodedc-home-task-card-surface px-0",
compact && "!rounded-[24px]",
isActive ? "nodedc-home-task-card-surface-active" : "nodedc-home-task-card-surface-passive"
)}
contentClassName="px-1"
contentClassName={cn("px-1", compact && "min-h-[168px]")}
titleContainerClassName={cn(compact && "px-3 py-3")}
titleClassName={cn(compact && "text-base leading-5")}
footerClassName={cn(compact && "gap-2")}
header={header}
subtitle={sourceContourName}
title={issue.name}
@@ -217,15 +244,13 @@ const HomeInternalContourDeckCard = observer(function HomeInternalContourDeckCar
});
const HomeExternalContourDeckCard = observer(function HomeExternalContourDeckCard(props: ExternalIssueCardProps) {
const { isActive, onSelect, project, request } = props;
const { compact = false, isActive, onSelect, project, request } = props;
const { t } = useTranslation();
const { getStateById } = useProjectState();
const { iconBubbleClasses, pillBackgroundClasses } = getNodedcWorkItemCardAppearance(isActive);
const issue = request.issue;
const isOutgoing = request.direction
? request.direction === "outgoing"
: request.source_project_id === project.id;
const isOutgoing = request.direction ? request.direction === "outgoing" : request.source_project_id === project.id;
const requester =
request.requested_by?.display_name ||
request.requested_by_name ||
@@ -242,15 +267,28 @@ const HomeExternalContourDeckCard = observer(function HomeExternalContourDeckCar
const dueDateLabel = issue.target_date ? renderFormattedDate(issue.target_date, "d MMM, yyyy") : t("common.none");
return (
<button type="button" className="nodedc-home-task-card" data-active={isActive} onClick={onSelect} title={issue.name}>
<button
type="button"
className={cn("nodedc-home-task-card", { "nodedc-home-task-card-compact": compact })}
data-active={isActive}
onClick={onSelect}
title={issue.name}
>
<div
data-active={isActive}
className={cn(
"nodedc-external-card nodedc-home-task-card-surface relative flex min-h-[220px] w-full flex-col p-4",
"nodedc-external-card nodedc-home-task-card-surface relative flex w-full flex-col",
compact ? "min-h-[168px] rounded-[24px] p-3" : "min-h-[220px] p-4",
isActive ? "nodedc-home-task-card-surface-active" : "nodedc-home-task-card-surface-passive"
)}
>
<div className={cn("relative flex min-h-[220px] flex-col px-1", isActive ? "text-[rgb(var(--nodedc-on-card-active-rgb))]" : "text-white")}>
<div
className={cn(
"relative flex flex-col px-1",
compact ? "min-h-[168px]" : "min-h-[220px]",
isActive ? "text-[rgb(var(--nodedc-on-card-active-rgb))]" : "text-white/70"
)}
>
<div className="space-y-0.5">
<div className="flex items-center justify-between gap-3">
<div className="flex min-w-0 flex-1 items-center gap-3">
@@ -283,7 +321,7 @@ const HomeExternalContourDeckCard = observer(function HomeExternalContourDeckCar
<div
className={cn(
"truncate -mt-0.5 pl-8 text-[11px] font-medium leading-4",
"-mt-0.5 truncate pl-8 text-[11px] leading-4 font-medium",
isActive ? "text-[rgb(var(--nodedc-on-card-active-rgb))]/72" : "text-[#B3B3B8]"
)}
>
@@ -291,12 +329,21 @@ const HomeExternalContourDeckCard = observer(function HomeExternalContourDeckCar
</div>
</div>
<div className="flex flex-1 items-center justify-center px-5 py-4 text-center">
<div className="line-clamp-4 max-w-full text-lg font-semibold leading-6">{issue.name}</div>
<div
className={cn("flex flex-1 items-center justify-center text-center", compact ? "px-3 py-3" : "px-5 py-4")}
>
<div
className={cn(
"line-clamp-4 max-w-full font-semibold",
compact ? "text-base leading-5" : "text-lg leading-6"
)}
>
{issue.name}
</div>
</div>
<div className="flex items-center justify-between gap-3">
<div className={cn("inline-flex min-h-9 items-center rounded-full pl-1 pr-2", pillBackgroundClasses)}>
<div className={cn("flex items-center justify-between", compact ? "gap-2" : "gap-3")}>
<div className={cn("inline-flex min-h-9 items-center rounded-full pr-2 pl-1", pillBackgroundClasses)}>
{(issue.assignee_ids?.length ?? 0) > 0 ? (
<ButtonAvatars showTooltip={false} userIds={issue.assignee_ids ?? []} size="sm" />
) : (
@@ -304,7 +351,12 @@ const HomeExternalContourDeckCard = observer(function HomeExternalContourDeckCar
)}
</div>
<div className={cn("inline-flex min-h-9 items-center gap-1.5 rounded-full px-2.5 py-1 text-[11px] font-medium", pillBackgroundClasses)}>
<div
className={cn(
"inline-flex min-h-9 items-center gap-1.5 rounded-full px-2.5 py-1 text-[11px] font-medium",
pillBackgroundClasses
)}
>
<CalendarDays className="h-3.5 w-3.5" />
<span className="truncate">{dueDateLabel}</span>
</div>
@@ -316,7 +368,7 @@ const HomeExternalContourDeckCard = observer(function HomeExternalContourDeckCar
});
export const HomeRecentIssueDecks = observer(function HomeRecentIssueDecks(props: HomeRecentIssueDecksProps) {
const { project, workspaceSlug } = props;
const { compact = false, project, workspaceSlug } = props;
const [selectedInternalIssueId, setSelectedInternalIssueId] = useState<string | null>(null);
const [selectedExternalRequestId, setSelectedExternalRequestId] = useState<string | null>(null);
@@ -382,9 +434,13 @@ export const HomeRecentIssueDecks = observer(function HomeRecentIssueDecks(props
if (!project) {
return (
<HomeCardShell
eyebrow="Task Decks"
title="Последние задачи по проекту"
description="Выберите проект слева, и здесь появятся колоды последних задач внешнего и внутреннего контуров."
eyebrow={compact ? "Последние задачи" : "Task Decks"}
title={compact ? "Последние задачи проекта" : "Последние задачи по проекту"}
description={
compact
? undefined
: "Выберите проект слева, и здесь появятся колоды последних задач внешнего и внутреннего контуров."
}
>
<div className="rounded-[24px] border border-white/6 bg-black/10 px-5 py-6 text-13 text-secondary">
Фокус проекта пока не выбран.
@@ -393,61 +449,63 @@ export const HomeRecentIssueDecks = observer(function HomeRecentIssueDecks(props
);
}
const internalIssueCards = internalIssues.map((issue, index) => (
<div
const internalIssueCards = internalIssues.map((issue) => (
<HomeInternalContourDeckCard
compact={compact}
key={issue.id}
className={cn({ "-ml-16": index > 0 })}
style={{ zIndex: issue.id === selectedInternalIssueId ? internalIssues.length + 6 : index + 1 }}
>
<HomeInternalContourDeckCard
issue={issue}
isActive={issue.id === selectedInternalIssueId}
onSelect={() => setSelectedInternalIssueId(issue.id)}
project={project}
/>
</div>
issue={issue}
isActive={issue.id === selectedInternalIssueId}
onSelect={() => setSelectedInternalIssueId(issue.id)}
project={project}
/>
));
const externalIssueCards = externalRequests.map((request, index) => (
<div
const externalIssueCards = externalRequests.map((request) => (
<HomeExternalContourDeckCard
compact={compact}
key={request.id}
className={cn({ "-ml-16": index > 0 })}
style={{ zIndex: request.id === selectedExternalRequestId ? externalRequests.length + 6 : index + 1 }}
>
<HomeExternalContourDeckCard
isActive={request.id === selectedExternalRequestId}
onSelect={() => setSelectedExternalRequestId(request.id)}
project={project}
request={request}
/>
</div>
isActive={request.id === selectedExternalRequestId}
onSelect={() => setSelectedExternalRequestId(request.id)}
project={project}
request={request}
/>
));
return (
<HomeCardShell
eyebrow={`${project.identifier} • последние задачи`}
title="Последние задачи проекта"
description="Ниже собраны две колоды для быстрого просмотра новых карточек во внешнем и внутреннем контурах."
contentClassName="space-y-5 p-5"
eyebrow={compact ? `${project.identifier} • последние задачи` : `${project.identifier} • последние задачи`}
title={compact ? "Последние задачи проекта" : "Последние задачи проекта"}
description={
compact
? undefined
: "Ниже собраны две колоды для быстрого просмотра новых карточек во внешнем и внутреннем контурах."
}
contentClassName={compact ? "space-y-3 p-4" : "grid gap-5 p-5 xl:grid-cols-2"}
>
<DeckSection
compact={compact}
count={externalRequests.length}
description="Последние запросы и задачи внешнего контура по текущему проекту."
description={
compact ? "Последние внешние карточки" : "Последние запросы и задачи внешнего контура по текущему проекту."
}
emptyDescription="У проекта пока нет внешних контурных задач. Когда появится первый обмен с контрагентом, он сразу попадет в эту колоду."
emptyTitle="Внешний контур пока пуст"
isLoading={isExternalRequestsLoading}
items={externalIssueCards}
title="Последние задачи внешнего контура"
title={compact ? "Внешний контур" : "Внешний контур"}
/>
<DeckSection
compact={compact}
count={internalIssues.length}
description="Последние добавленные внутренние задачи выбранного проекта."
description={
compact ? "Последние внутренние карточки" : "Последние добавленные внутренние задачи выбранного проекта."
}
emptyDescription="Во внутреннем контуре пока нет задач. Как только в проекте появится новая карточка, она ляжет сюда первой."
emptyTitle="Внутренний контур пока пуст"
isLoading={isInternalIssuesLoading}
items={internalIssueCards}
title="Последние задачи внутреннего контура"
title={compact ? "Внутренний контур" : "Внутренний контур"}
/>
</HomeCardShell>
);
@@ -4,24 +4,20 @@
* See the LICENSE file for details.
*/
// plane types
import { useTranslation } from "@plane/i18n";
import type { IUser, TProjectAnalyticsCount } from "@plane/types";
import type { IUser } from "@plane/types";
import { Avatar } from "@plane/ui";
import { getFileURL } from "@plane/utils";
import { useCurrentTime } from "@/hooks/use-current-time";
import { getCompletionRate, type THomeProjectData } from "./home.utils";
export interface IUserGreetingsView {
user: IUser;
workspaceName?: string | null;
selectedProject?: THomeProjectData;
selectedProjectAnalytics?: TProjectAnalyticsCount;
}
export function UserGreetingsView(props: IUserGreetingsView) {
const { user, workspaceName, selectedProject, selectedProjectAnalytics } = props;
// current time hook
const { user, workspaceName } = props;
const { currentTime } = useCurrentTime();
// store hooks
const { t, currentLocale } = useTranslation();
const hour = new Intl.DateTimeFormat(currentLocale, {
@@ -46,39 +42,37 @@ export function UserGreetingsView(props: IUserGreetingsView) {
}).format(currentTime);
const greeting = parseInt(hour, 10) < 12 ? "morning" : parseInt(hour, 10) < 18 ? "afternoon" : "evening";
const completionRate = getCompletionRate(selectedProjectAnalytics);
const userName = `${user?.first_name ?? ""} ${user?.last_name ?? ""}`.trim() || user?.email || "Workspace admin";
const userEmail = user?.email || workspaceName || "NODE DC";
return (
<section className="nodedc-home-card px-5 py-4">
<div className="grid items-stretch gap-3 xl:grid-cols-[minmax(0,1fr)_280px_220px]">
<div className="flex min-w-0 items-center">
<section className="nodedc-home-user-card">
<div className="nodedc-home-user-card-orb" />
<div className="relative z-[1] p-5">
<div className="text-[11px] font-semibold tracking-[0.22em] text-white/55 uppercase">
{workspaceName ?? "NODE DC"}
</div>
<div className="mt-5 flex items-start gap-4">
<div className="shrink-0 rounded-[26px] bg-white/14 p-1 backdrop-blur-xl">
<Avatar src={getFileURL(user?.avatar_url ?? "")} name={userName} size="lg" />
</div>
<div className="min-w-0">
<div className="text-[11px] font-semibold tracking-[0.22em] text-placeholder uppercase">
{workspaceName ?? "Workspace Home"}
</div>
<h2 className="mt-1 truncate text-24 font-semibold text-primary">
{`${t("good")} ${t(greeting)}, ${user?.first_name} ${user?.last_name}`}
<div className="text-12 text-white/58">{`${weekDay}, ${date} ${timeString}`}</div>
<h2 className="mt-1 line-clamp-2 text-24 font-semibold leading-tight text-white">
{`${t("good")} ${t(greeting)}, ${user?.first_name || "DC"}`}
</h2>
<div className="mt-1 text-13 text-secondary">{`${weekDay}, ${date} ${timeString}`}</div>
</div>
</div>
<div className="nodedc-home-subpanel flex min-h-[108px] flex-col justify-center px-4 py-3">
<div className="text-12 font-medium text-secondary">Текущий фокус</div>
<div className="mt-2 line-clamp-2 text-18 font-semibold text-primary">
{selectedProject ? selectedProject.name : "Выберите проект слева"}
</div>
<div className="mt-1 text-12 text-secondary">
{selectedProject ? selectedProject.identifier : "Домашняя сводка перестроится под выбранную карточку."}
</div>
<div className="mt-8 space-y-1">
<div className="text-18 font-semibold text-white">{userName}</div>
<div className="truncate text-13 text-white/62">{userEmail}</div>
</div>
<div className="nodedc-home-subpanel flex min-h-[108px] flex-col justify-center px-4 py-3">
<div className="text-12 font-medium text-secondary">Прогресс фокуса</div>
<div className="mt-2 text-18 font-semibold text-primary">{selectedProject ? `${completionRate}%` : "—"}</div>
<div className="mt-1 text-12 text-secondary">
{selectedProject ? "Закрытые задачи относительно общего объёма." : "Станет доступен после выбора проекта."}
</div>
<div className="mt-5 flex flex-wrap items-center gap-2 text-[11px] font-medium text-white/56">
<div className="rounded-full bg-black/24 px-3 py-1.5 backdrop-blur-md">home admin</div>
<div className="rounded-full bg-black/24 px-3 py-1.5 backdrop-blur-md">{workspaceName ?? "workspace"}</div>
</div>
</div>
</section>
@@ -11,9 +11,6 @@ import { useTranslation } from "@plane/i18n";
// plane types
import { PageIcon, ProjectIcon, WorkItemsIcon } from "@plane/propel/icons";
import type { TActivityEntityData, THomeWidgetProps, TRecentActivityFilterKeys } from "@plane/types";
// plane ui
// components
import { ContentOverflowWrapper } from "@/components/core/content-overflow-HOC";
// plane web services
import { WorkspaceService } from "@/services/workspace.service";
import { getActivityProjectId } from "../../home.utils";
@@ -105,20 +102,15 @@ export const RecentActivityWidget = observer(function RecentActivityWidget(props
);
return (
<ContentOverflowWrapper
maxHeight={415}
containerClassName="box-border min-h-[250px]"
fallback={<></>}
buttonClassName="nodedc-toolbar-pill justify-center"
>
<div className="box-border min-h-[250px]">
<div className="mb-2 flex items-center justify-between">
<div className="text-14 font-semibold text-tertiary">{t("home.recents.title")}</div>
{showFilterSelect && <FiltersDropdown filters={filters} activeFilter={filter} setActiveFilter={setFilter} />}
</div>
<div className="flex min-h-[250px] flex-col">
<div className="flex max-h-[415px] min-h-[250px] flex-col overflow-y-auto">
{isLoading && <WidgetLoader widgetKey={WIDGET_KEY} />}
{!isLoading && recents.map((activity) => <div key={activity.id}>{resolveRecent(activity)}</div>)}
</div>
</ContentOverflowWrapper>
</div>
);
});
@@ -10,7 +10,7 @@ import { useTheme } from "next-themes";
import { Combobox } from "@headlessui/react";
// plane imports
import { useTranslation } from "@plane/i18n";
import { SearchIcon } from "@plane/propel/icons";
import { SearchIcon, getStateGroupColor } from "@plane/propel/icons";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { ISearchIssueResponse } from "@plane/types";
import { Loader, EModalPosition, EModalWidth, ModalCore } from "@plane/ui";
@@ -90,7 +90,7 @@ export function SelectDuplicateInboxIssueModal(props: Props) {
{query === "" && <h2 className="mt-4 mb-2 px-3 text-11 font-semibold text-primary">Select work item</h2>}
<ul className="text-13 text-primary">
{filteredIssues.map((issue) => {
const stateColor = issue.state__color || "";
const stateColor = getStateGroupColor(issue.state__group, issue.state__color);
return (
<Combobox.Option
@@ -9,6 +9,7 @@ import { useRouter } from "next/navigation";
import { MinusCircle } from "lucide-react";
import { useTranslation } from "@plane/i18n";
import { getIconButtonStyling } from "@plane/propel/icon-button";
import { getStateGroupColor } from "@plane/propel/icons";
import type { TIssue } from "@plane/types";
// component
// ui
@@ -55,7 +56,7 @@ export const IssueParentDetail = observer(function IssueParentDetail(props: TIss
const issueParentState = getProjectStates(parentIssue?.project_id)?.find(
(state) => state?.id === parentIssue?.state_id
);
const stateColor = issueParentState?.color || undefined;
const stateColor = getStateGroupColor(issueParentState?.group, issueParentState?.color);
if (!parentIssue) return <></>;
@@ -10,6 +10,7 @@ import { useParams } from "next/navigation";
import { MoreHorizontal } from "lucide-react";
// plane imports
import { Popover } from "@plane/propel/popover";
import { getStateGroupColor } from "@plane/propel/icons";
import type { TIssue } from "@plane/types";
import { ControlLink } from "@plane/ui";
import { cn, generateWorkItemLink } from "@plane/utils";
@@ -50,16 +51,15 @@ export const CalendarIssueBlock = observer(
const { issuesFilter } = useIssues(storeType);
const { getProjectIdentifierById } = useProject();
const stateColor = getProjectStates(issue?.project_id)?.find((state) => state?.id == issue?.state_id)?.color || "";
const stateDetails = getProjectStates(issue?.project_id)?.find((state) => state?.id == issue?.state_id);
const stateColor = getStateGroupColor(stateDetails?.group, stateDetails?.color);
const projectIdentifier = getProjectIdentifierById(issue?.project_id);
// handlers
const handleIssuePeekOverview = (issue: TIssue) => handleRedirection(workspaceSlug.toString(), issue, isMobile);
const customActionButton = (
<div
className="w-full cursor-pointer rounded-sm p-1 text-secondary hover:bg-layer-1 hover:text-primary"
>
<div className="w-full cursor-pointer rounded-sm p-1 text-secondary hover:bg-layer-1 hover:text-primary">
<MoreHorizontal className="h-3.5 w-3.5" />
</div>
);
@@ -7,6 +7,7 @@
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// plane imports
import { getStateGroupColor } from "@plane/propel/icons";
import { Popover } from "@plane/propel/popover";
import { Tooltip } from "@plane/propel/tooltip";
import { ControlLink } from "@plane/ui";
@@ -53,7 +54,15 @@ export const IssueGanttBlock = observer(function IssueGanttBlock(props: Props) {
const stateDetails =
issueDetails && getProjectStates(issueDetails?.project_id)?.find((state) => state?.id == issueDetails?.state_id);
const { blockStyle } = getBlockViewDetails(issueDetails, stateDetails?.color ?? "");
const stateGroupColor = getStateGroupColor(stateDetails?.group, stateDetails?.color);
const { blockStyle } = getBlockViewDetails(issueDetails, stateGroupColor);
const ganttBlockStyle = {
...blockStyle,
backgroundColor: stateGroupColor,
maskImage: "none",
WebkitMaskImage: "none",
};
const ganttBlockTextColor = ["completed", "started"].includes(stateDetails?.group ?? "") ? "#0B1117" : "#FFFFFF";
const handleIssuePeekOverview = () => handleRedirection(workspaceSlug, issueDetails, isMobile);
@@ -66,14 +75,14 @@ export const IssueGanttBlock = observer(function IssueGanttBlock(props: Props) {
render={
<div
id={`issue-${issueId}`}
className="space-between relative flex h-full w-full cursor-pointer items-center rounded-sm"
style={blockStyle}
className="nodedc-project-gantt-issue-bar space-between relative flex h-full w-full cursor-pointer items-center"
style={ganttBlockStyle}
onClick={handleIssuePeekOverview}
>
<div className="absolute top-0 left-0 h-full w-full bg-surface-1/50" />
<div className="nodedc-project-gantt-issue-bar-shade absolute top-0 left-0 h-full w-full" />
<div
className="sticky w-auto flex-1 truncate overflow-hidden px-2.5 py-1 text-13 text-primary"
style={{ left: `${SIDEBAR_WIDTH}px` }}
className="nodedc-project-gantt-issue-bar-title sticky w-auto flex-1 truncate overflow-hidden px-3 py-1 text-12 font-semibold text-primary"
style={{ left: `${SIDEBAR_WIDTH}px`, color: ganttBlockTextColor }}
>
{issueDetails?.name}
</div>
@@ -73,11 +73,12 @@ export const InternalContourKanbanCard = observer(function InternalContourKanban
const projectStateIds = issue.project_id ? getProjectStateIds(issue.project_id) : [];
const { pillBackgroundClasses } = getNodedcWorkItemCardAppearance(isActive);
const statusIconColor = getStateGroupColor(selectedState?.group, selectedState?.color);
const controlStatusIconColor = selectedState?.group === "started" ? "#F5F7FB" : statusIconColor;
const creatorName = creatorDetails?.display_name ?? t("common.none");
const dueDateLabel = issue.target_date ? renderFormattedDate(issue.target_date, "d MMM, yyyy") : t("common.none");
const cornerControlClasses =
"flex h-12 w-12 items-center justify-center rounded-full border-0 bg-white text-[#0B1117] shadow-none outline-none ring-0 transition-transform hover:scale-[1.03]";
"flex h-12 w-12 items-center justify-center rounded-full border-0 bg-[#17181B] text-white shadow-none outline-none ring-0 transition-transform hover:scale-[1.03] hover:bg-[#0F1012]";
const dateButton = (
<DateDropdown
className="h-auto self-start"
@@ -112,7 +113,7 @@ export const InternalContourKanbanCard = observer(function InternalContourKanban
<div
data-control-link-ignore="true"
className={cn(
"flex h-12 w-12 cursor-pointer items-center justify-center rounded-full bg-black text-white shadow-none ring-0 transition-transform outline-none hover:scale-[1.03]",
"flex h-12 w-12 -translate-x-0.5 -translate-y-0.5 cursor-pointer items-center justify-center rounded-full bg-black text-white shadow-none ring-0 transition-transform outline-none hover:scale-[1.03]",
isActive
? "text-[rgb(var(--nodedc-card-active-rgb))] hover:bg-black/90"
: "bg-[#111214] text-white hover:bg-[#0A0B0C]"
@@ -131,6 +132,7 @@ export const InternalContourKanbanCard = observer(function InternalContourKanban
name={creatorName}
size="md"
showTooltip={!isMobile}
className="border-0 shadow-none ring-0 outline-none"
/>
</div>
<div className="flex min-w-0 flex-col gap-1">
@@ -139,7 +141,7 @@ export const InternalContourKanbanCard = observer(function InternalContourKanban
</div>
</div>
<div className="absolute top-0 right-0 flex shrink-0 items-center gap-0">
<div className="absolute top-0.5 right-0.5 flex shrink-0 items-center gap-0">
<PriorityDropdown
value={issue.priority}
onChange={(priority) => updateIssue?.(issue.project_id ?? null, issue.id, { priority })}
@@ -160,7 +162,7 @@ export const InternalContourKanbanCard = observer(function InternalContourKanban
<div data-control-link-ignore="true" className={cornerControlClasses}>
<StateGroupIcon
stateGroup={selectedState?.group ?? "backlog"}
color={statusIconColor}
color={controlStatusIconColor}
className="h-4 w-4"
percentage={selectedState?.order}
/>
@@ -190,7 +192,10 @@ export const InternalContourKanbanCard = observer(function InternalContourKanban
button={
<div
data-control-link-ignore="true"
className={cn(basePillClasses, "h-11 min-w-11 justify-center rounded-full bg-transparent px-0 py-0")}
className={cn(
basePillClasses,
"h-11 min-w-11 justify-center rounded-full bg-transparent px-0 py-0 shadow-none ring-0 outline-none [&_.bg-accent-subtle]:!bg-transparent [&_.border-subtle-1]:!border-0"
)}
>
<ButtonAvatars showTooltip={false} userIds={issue.assignee_ids} size="sm" />
</div>
@@ -9,7 +9,7 @@ import { observer } from "mobx-react";
import type { Control } from "react-hook-form";
import { Controller } from "react-hook-form";
import { ETabIndices } from "@plane/constants";
import { CloseIcon } from "@plane/propel/icons";
import { CloseIcon, getStateGroupColor } from "@plane/propel/icons";
// plane imports
// types
import type { ISearchIssueResponse, TIssue } from "@plane/types";
@@ -44,7 +44,7 @@ export const IssueParentTag = observer(function IssueParentTag(props: TIssuePare
<span
className="block h-1.5 w-1.5 rounded-full"
style={{
backgroundColor: selectedParentIssue.state__color,
backgroundColor: getStateGroupColor(selectedParentIssue.state__group, selectedParentIssue.state__color),
}}
/>
<span className="flex-shrink-0 text-secondary">
@@ -12,7 +12,7 @@ import { Rocket } from "lucide-react";
import { Combobox } from "@headlessui/react";
// i18n
import { useTranslation } from "@plane/i18n";
import { SearchIcon } from "@plane/propel/icons";
import { SearchIcon, getStateGroupColor } from "@plane/propel/icons";
// types
import type { ISearchIssueResponse } from "@plane/types";
// ui
@@ -159,7 +159,7 @@ export function ParentIssuesListModal({
<span
className="block h-1.5 w-1.5 flex-shrink-0 rounded-full"
style={{
backgroundColor: issue.state__color,
backgroundColor: getStateGroupColor(issue.state__group, issue.state__color),
}}
/>
<span className="flex-shrink-0">
@@ -40,6 +40,11 @@ export const ModuleGanttBlock = observer(function ModuleGanttBlock(props: Props)
moduleDetails,
MODULE_STATUS.find((s) => s.value === moduleDetails?.status)?.color ?? ""
);
const ganttBlockStyle = {
...blockStyle,
maskImage: "none",
WebkitMaskImage: "none",
};
return (
<Tooltip
@@ -53,15 +58,15 @@ export const ModuleGanttBlock = observer(function ModuleGanttBlock(props: Props)
position="top-start"
>
<div
className="relative flex h-full w-full cursor-pointer items-center rounded-sm"
style={blockStyle}
className="nodedc-project-gantt-issue-bar relative flex h-full w-full cursor-pointer items-center"
style={ganttBlockStyle}
onClick={() =>
router.push(
`/${workspaceSlug?.toString()}/projects/${moduleDetails?.project_id}/modules/${moduleDetails?.id}`
)
}
>
<div className="absolute top-0 left-0 h-full w-full bg-surface-1/50" />
<div className="nodedc-project-gantt-issue-bar-shade absolute top-0 left-0 h-full w-full" />
<div
className="sticky w-auto truncate overflow-hidden px-2.5 py-1 text-13 text-primary"
style={{ left: `${SIDEBAR_WIDTH}px` }}
@@ -311,10 +311,10 @@ export function VoiceTaskerGlobalControl({ workspaceSlug }: Props) {
};
const refreshVisibleIssueStores = useCallback(
async (createdProjectId?: string) => {
async () => {
const refreshes: Promise<unknown>[] = [];
if (createdProjectId && activeProjectId === createdProjectId) {
if (activeProjectId) {
refreshes.push(refreshProjectIssues(workspaceSlug, activeProjectId, "mutation"));
if (activeProjectViewId) {
refreshes.push(refreshProjectViewIssues(workspaceSlug, activeProjectId, activeProjectViewId, "mutation"));
@@ -361,7 +361,7 @@ export function VoiceTaskerGlobalControl({ workspaceSlug }: Props) {
action,
draft: parseResult.draft,
});
await refreshVisibleIssueStores(result.project_id);
await refreshVisibleIssueStores();
setCommitResult(result);
setStatus("committed");
setToast({
@@ -456,7 +456,7 @@ export function VoiceTaskerGlobalControl({ workspaceSlug }: Props) {
{parseResult.transcript && (
<div>
<div className="text-11 font-medium text-tertiary uppercase">Транскрипт</div>
<p className="mt-1 max-h-20 overflow-y-auto text-12 leading-5 text-secondary">
<p className="mt-1 whitespace-pre-wrap break-words text-12 leading-5 text-secondary">
{parseResult.transcript}
</p>
</div>
@@ -524,7 +524,7 @@ export function VoiceTaskerGlobalControl({ workspaceSlug }: Props) {
{parseResult.draft.description && (
<div>
<div className="text-11 font-medium text-tertiary uppercase">Описание</div>
<p className="mt-1 max-h-20 overflow-y-auto text-12 leading-5 text-secondary">
<p className="mt-1 whitespace-pre-wrap break-words text-12 leading-5 text-secondary">
{parseResult.draft.description}
</p>
</div>