UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: приведение карточек внешнего контура к референсу

This commit is contained in:
DCCONSTRUCTIONS 2026-04-29 13:24:57 +03:00
parent 248292bd52
commit d53fa2b38c
1 changed files with 197 additions and 201 deletions

View File

@ -40,7 +40,7 @@ const issueService = new IssueService();
const issueArchiveService = new IssueArchiveService(); const issueArchiveService = new IssueArchiveService();
const basePillClasses = const basePillClasses =
"inline-flex min-h-9 items-center gap-1.5 rounded-full border-0 px-2.5 py-1 text-[11px] font-medium shadow-none outline-none transition-colors"; "inline-flex min-h-8 items-center gap-1.5 rounded-full border-0 px-2.5 py-1 text-[10px] font-medium shadow-none outline-none transition-colors";
const buildSourceStateMap = ( const buildSourceStateMap = (
states: { id: string; name: string; color: string; group: IState["group"] }[] | undefined, states: { id: string; name: string; color: string; group: IState["group"] }[] | undefined,
@ -130,7 +130,16 @@ export const ExternalContoursBoardItem = observer(function ExternalContoursBoard
const pillBackgroundClasses = isActive const pillBackgroundClasses = isActive
? "bg-black/10 text-[rgb(var(--nodedc-on-card-active-rgb))]" ? "bg-black/10 text-[rgb(var(--nodedc-on-card-active-rgb))]"
: "bg-[rgb(var(--nodedc-card-passive-rgb))] text-white"; : "bg-[rgb(var(--nodedc-card-passive-rgb))] text-white";
const iconBubbleClasses = isActive ? "bg-black text-[rgb(var(--nodedc-card-active-rgb))]" : "bg-[#111214] text-white"; const cornerActionButtonClasses = cn(
"flex h-12 w-12 -translate-x-0.5 -translate-y-0.5 items-center justify-center rounded-full border bg-transparent shadow-none ring-0 transition-colors outline-none",
isActive
? "border-black/25 text-black hover:bg-black/5"
: "border-white/20 text-white hover:border-white/35 hover:bg-white/5"
);
const assigneeButtonClasses = cn(
"flex h-7 min-w-7 items-center justify-center rounded-full border-0 bg-transparent p-0 shadow-none outline-none transition-colors",
isActive ? "text-[rgb(var(--nodedc-on-card-active-rgb))]" : "text-white"
);
const dueDateLabel = issue.target_date ? renderFormattedDate(issue.target_date, "d MMM, yyyy") : t("common.none"); const dueDateLabel = issue.target_date ? renderFormattedDate(issue.target_date, "d MMM, yyyy") : t("common.none");
const canArchive = canEditTargetIssue && !!selectedState && ARCHIVABLE_STATE_GROUPS.includes(selectedState.group); const canArchive = canEditTargetIssue && !!selectedState && ARCHIVABLE_STATE_GROUPS.includes(selectedState.group);
@ -294,36 +303,35 @@ export const ExternalContoursBoardItem = observer(function ExternalContoursBoard
}} }}
> >
<div className={cn("relative flex min-h-[220px] flex-col px-1", foregroundClasses)}> <div className={cn("relative flex min-h-[220px] flex-col px-1", foregroundClasses)}>
<div className="space-y-0.5"> <div className="absolute top-0.5 left-0.5 z-20">
<div className="flex items-center justify-between gap-3"> <Avatar
<div className="flex min-w-0 flex-1 items-center gap-3"> src={requesterAvatar}
<div className="shrink-0"> name={requester}
<Avatar src={requesterAvatar} name={requester} size="md" /> size={48}
</div> className="border border-white/10 shadow-none ring-0 outline-none"
<div className={cn("truncate text-body-sm-medium leading-5", foregroundClasses)}>{requester}</div>
</div>
<div className="flex shrink-0 items-center gap-2" onClick={stopCardPropagation}>
{request.has_unread_updates && (
<span
className={cn("size-2 rounded-full", isActive ? "bg-black/70" : "bg-accent-primary")}
title={t("external_contours_page.list.unread_updates")}
/> />
)} </div>
<div className="absolute top-0.5 right-0.5 z-20" onClick={stopCardPropagation}>
<ActionDropdown <ActionDropdown
placement="bottom-end" placement="bottom-end"
button={<div className={cn("flex h-8 w-8 items-center justify-center rounded-full", iconBubbleClasses)}><MoreHorizontal className="h-4 w-4" /></div>} button={
buttonClassName="h-8 w-8" <div className={cornerActionButtonClasses}>
<MoreHorizontal className="h-4 w-4" />
</div>
}
buttonClassName="h-12 w-12"
menuClassName="min-w-[18rem]" menuClassName="min-w-[18rem]"
onOpenChange={(isOpen) => { onOpenChange={(isOpen) => {
if (isOpen) void ensureSourceOptions(); if (isOpen) void ensureSourceOptions();
}} }}
items={[]} items={[]}
menuContent={({ closeDropdown }) => ( menuContent={({ closeDropdown }) => (
<div className="max-h-[min(75vh,34rem)] space-y-2 overflow-y-auto" onClick={stopCardPropagation}> <div className="max-h-[calc(100vh-2rem)] space-y-2 overflow-y-auto" onClick={stopCardPropagation}>
<div className="space-y-1"> <div className="space-y-1">
<div className="px-2 text-[10px] font-semibold tracking-[0.16em] text-tertiary uppercase">Приоритет</div> <div className="px-2 text-[10px] font-semibold tracking-[0.16em] text-tertiary uppercase">
Приоритет
</div>
{priorityOptions.map((priority) => ( {priorityOptions.map((priority) => (
<button <button
key={priority} key={priority}
@ -342,7 +350,9 @@ export const ExternalContoursBoardItem = observer(function ExternalContoursBoard
</div> </div>
<div className="space-y-1 border-t border-white/8 pt-2"> <div className="space-y-1 border-t border-white/8 pt-2">
<div className="px-2 text-[10px] font-semibold tracking-[0.16em] text-tertiary uppercase">Статус</div> <div className="px-2 text-[10px] font-semibold tracking-[0.16em] text-tertiary uppercase">
Статус
</div>
{isSourceOptionsLoading && stateOptions.length === 0 ? ( {isSourceOptionsLoading && stateOptions.length === 0 ? (
<div className="px-2.5 py-2 text-12 text-tertiary">Загрузка статусов...</div> <div className="px-2.5 py-2 text-12 text-tertiary">Загрузка статусов...</div>
) : ( ) : (
@ -370,7 +380,9 @@ export const ExternalContoursBoardItem = observer(function ExternalContoursBoard
</div> </div>
<div className="space-y-1 border-t border-white/8 pt-2"> <div className="space-y-1 border-t border-white/8 pt-2">
<div className="px-2 text-[10px] font-semibold tracking-[0.16em] text-tertiary uppercase">Быстрые действия</div> <div className="px-2 text-[10px] font-semibold tracking-[0.16em] text-tertiary uppercase">
Быстрые действия
</div>
<button <button
type="button" type="button"
className={menuItemClasses} className={menuItemClasses}
@ -422,41 +434,27 @@ export const ExternalContoursBoardItem = observer(function ExternalContoursBoard
)} )}
/> />
</div> </div>
</div>
<div className={cn("-mt-0.5 truncate pl-8 text-[11px] leading-4 font-medium", subtleTextClasses)}> <div className="min-w-0 pr-[58px] pl-[58px] pt-1">
<div className="flex min-w-0 items-center gap-1.5">
<div className={cn("truncate text-body-sm-medium leading-5", foregroundClasses)}>{requester}</div>
{request.has_unread_updates && (
<span
className={cn("size-2 shrink-0 rounded-full", isActive ? "bg-black/70" : "bg-accent-primary")}
title={t("external_contours_page.list.unread_updates")}
/>
)}
</div>
<div className={cn("truncate text-[10px] leading-3.5 font-medium", subtleTextClasses)}>
{counterpartContourName || t("common.none")} {counterpartContourName || t("common.none")}
</div> </div>
</div> </div>
<div className="flex flex-1 items-center justify-center px-5 py-4 text-center"> <div className="flex flex-1 items-center justify-start px-1 pt-7 pb-4 text-left">
<div className="text-lg line-clamp-4 max-w-full leading-6 font-semibold">{issue.name}</div> <div className="line-clamp-5 max-w-full text-[15px] leading-5 font-medium">{issue.name}</div>
</div> </div>
<div className="flex items-center justify-between gap-3" onClick={stopCardPropagation}> <div className="flex items-center justify-between gap-3" onClick={stopCardPropagation}>
{direction === "outgoing" && (
<DateDropdown
value={issue.target_date}
rangePreview={{
from: issue.start_date,
to: issue.target_date,
}}
onChange={(targetDate) =>
void handleCardUpdate({
target_date: targetDate ? renderFormattedPayloadDate(targetDate) : null,
})
}
disabled={!canEditCard || isUpdating}
buttonVariant="transparent-without-text"
button={
<div className={cn(basePillClasses, pillBackgroundClasses)}>
<CalendarDays className="h-3.5 w-3.5" />
<span className="truncate">{dueDateLabel}</span>
</div>
}
/>
)}
{canEditTargetIssue ? ( {canEditTargetIssue ? (
<MemberDropdown <MemberDropdown
multiple multiple
@ -466,8 +464,8 @@ export const ExternalContoursBoardItem = observer(function ExternalContoursBoard
disabled={!canEditCard || isUpdating} disabled={!canEditCard || isUpdating}
buttonVariant="transparent-without-text" buttonVariant="transparent-without-text"
button={ button={
<div className={cn(basePillClasses, pillBackgroundClasses, "pr-2 pl-1")}> <div className={assigneeButtonClasses}>
<ButtonAvatars showTooltip={false} userIds={issue.assignee_ids ?? []} size="sm" /> <ButtonAvatars showTooltip={false} userIds={issue.assignee_ids ?? []} size={26} />
</div> </div>
} }
/> />
@ -484,14 +482,13 @@ export const ExternalContoursBoardItem = observer(function ExternalContoursBoard
}} }}
buttonVariant="transparent-without-text" buttonVariant="transparent-without-text"
button={ button={
<div className={cn(basePillClasses, pillBackgroundClasses, "pr-2 pl-1")}> <div className={assigneeButtonClasses}>
<ButtonAvatars showTooltip={false} userIds={issue.assignee_ids ?? []} size="sm" /> <ButtonAvatars showTooltip={false} userIds={issue.assignee_ids ?? []} size={26} />
</div> </div>
} }
/> />
)} )}
{direction !== "outgoing" && (
<DateDropdown <DateDropdown
value={issue.target_date} value={issue.target_date}
rangePreview={{ rangePreview={{
@ -507,12 +504,11 @@ export const ExternalContoursBoardItem = observer(function ExternalContoursBoard
buttonVariant="transparent-without-text" buttonVariant="transparent-without-text"
button={ button={
<div className={cn(basePillClasses, pillBackgroundClasses)}> <div className={cn(basePillClasses, pillBackgroundClasses)}>
<CalendarDays className="h-3.5 w-3.5" /> <CalendarDays className="h-3 w-3" />
<span className="truncate">{dueDateLabel}</span> <span className="truncate">{dueDateLabel}</span>
</div> </div>
} }
/> />
)}
</div> </div>
</div> </div>
</div> </div>