ФУНКЦИИ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: стабилизация контуров, досок и Voice Tasker
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
+2
-2
@@ -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,
|
||||
}
|
||||
|
||||
+2
-2
@@ -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>
|
||||
|
||||
+10
-5
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user