UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: auth screens, project actions и полировка внешних контуров
This commit is contained in:
@@ -90,7 +90,7 @@ export const ExternalContoursContentRoot = observer(function ExternalContoursCon
|
||||
hasDirectTargetAccess={hasDirectTargetAccess}
|
||||
/>
|
||||
</div>
|
||||
<ContentWrapper className="divide-y-2 divide-subtle-1">
|
||||
<ContentWrapper className="space-y-4 px-4 py-4">
|
||||
<ExternalContoursIssueMainContent
|
||||
workspaceSlug={workspaceSlug}
|
||||
sourceProjectId={projectId}
|
||||
|
||||
@@ -39,7 +39,7 @@ export function ExternalContourDeclineModal(props: Props) {
|
||||
handleClose={handleClose}
|
||||
position={EModalPosition.CENTER}
|
||||
width={EModalWidth.LG}
|
||||
className="rounded-lg"
|
||||
className="nodedc-glass-modal rounded-[1.75rem]"
|
||||
>
|
||||
<div className="space-y-4 p-6">
|
||||
<div className="space-y-1">
|
||||
@@ -54,13 +54,14 @@ export function ExternalContourDeclineModal(props: Props) {
|
||||
rows={5}
|
||||
disabled={isSubmitting}
|
||||
autoFocus
|
||||
className="nodedc-modal-input min-h-[8rem] resize-none"
|
||||
/>
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="secondary" onClick={handleClose} disabled={isSubmitting}>
|
||||
<Button variant="secondary" onClick={handleClose} disabled={isSubmitting} className="nodedc-modal-secondary-button">
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
<Button variant="primary" onClick={handleSubmit} disabled={isSubmitting || !comment.trim()}>
|
||||
<Button variant="primary" onClick={handleSubmit} disabled={isSubmitting || !comment.trim()} className="nodedc-modal-primary-button">
|
||||
{t("external_contours_page.decline_modal.submit")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -135,7 +135,7 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont
|
||||
onSubmit={(comment) => handleDecision("decline", comment)}
|
||||
/>
|
||||
|
||||
<Row className="relative z-15 hidden h-full w-full items-center justify-between gap-2 border-b border-subtle bg-surface-1 lg:flex">
|
||||
<Row className="relative z-15 hidden h-full w-full items-center justify-between gap-2 px-4 lg:flex">
|
||||
<div className="flex items-center gap-4">
|
||||
{issue?.project_id && issue.sequence_id && (
|
||||
<h3 className="flex-shrink-0 text-14 font-medium text-tertiary">
|
||||
@@ -157,11 +157,11 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{canReviewClosedRequest && (
|
||||
<>
|
||||
<Button variant="secondary" size="lg" onClick={() => handleDecision("accept")}>
|
||||
<Button variant="primary" size="lg" onClick={() => handleDecision("accept")} className="nodedc-external-primary-button">
|
||||
<CheckCircleFilledIcon className="size-4 shrink-0 text-success-secondary" />
|
||||
{t("external_contours_page.actions.accept")}
|
||||
</Button>
|
||||
<Button variant="secondary" size="lg" onClick={() => setIsDeclineModalOpen(true)}>
|
||||
<Button variant="secondary" size="lg" onClick={() => setIsDeclineModalOpen(true)} className="nodedc-external-action-button">
|
||||
<CloseCircleFilledIcon className="size-4 shrink-0 text-danger-secondary" />
|
||||
{t("external_contours_page.actions.decline")}
|
||||
</Button>
|
||||
@@ -172,12 +172,18 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont
|
||||
<Badge variant="neutral">{t("external_contours_page.traceability.source_decision_accepted")}</Badge>
|
||||
)}
|
||||
|
||||
<Button variant="secondary" size="lg" prependIcon={<LinkIcon className="h-2.5 w-2.5" />} onClick={handleCopyLink}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
prependIcon={<LinkIcon className="h-2.5 w-2.5" />}
|
||||
onClick={handleCopyLink}
|
||||
className="nodedc-external-action-button"
|
||||
>
|
||||
{t("external_contours_page.actions.copy")}
|
||||
</Button>
|
||||
{hasDirectTargetAccess && (
|
||||
<ControlLink href={workItemLink} onClick={() => router.push(workItemLink)} target="_self">
|
||||
<Button variant="secondary" size="lg" prependIcon={<NewTabIcon className="h-2.5 w-2.5" />}>
|
||||
<Button variant="secondary" size="lg" prependIcon={<NewTabIcon className="h-2.5 w-2.5" />} className="nodedc-external-action-button">
|
||||
{t("external_contours_page.actions.open")}
|
||||
</Button>
|
||||
</ControlLink>
|
||||
@@ -196,10 +202,10 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
{canReviewClosedRequest && (
|
||||
<>
|
||||
<Button variant="secondary" size="sm" onClick={() => handleDecision("accept")}>
|
||||
<Button variant="primary" size="sm" onClick={() => handleDecision("accept")} className="nodedc-external-primary-button">
|
||||
{t("external_contours_page.actions.accept")}
|
||||
</Button>
|
||||
<Button variant="secondary" size="sm" onClick={() => setIsDeclineModalOpen(true)}>
|
||||
<Button variant="secondary" size="sm" onClick={() => setIsDeclineModalOpen(true)} className="nodedc-external-action-button">
|
||||
{t("external_contours_page.actions.decline")}
|
||||
</Button>
|
||||
</>
|
||||
@@ -209,7 +215,7 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont
|
||||
)}
|
||||
{hasDirectTargetAccess && (
|
||||
<ControlLink href={workItemLink} onClick={() => router.push(workItemLink)} target="_self">
|
||||
<Button variant="secondary" size="sm">
|
||||
<Button variant="secondary" size="sm" className="nodedc-external-action-button">
|
||||
{t("external_contours_page.actions.open")}
|
||||
</Button>
|
||||
</ControlLink>
|
||||
|
||||
@@ -139,7 +139,7 @@ export const ExternalContoursIssueMainContent = observer(function ExternalContou
|
||||
if (!hasDirectTargetAccess) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="nodedc-external-section space-y-4 p-5">
|
||||
<div className="nodedc-external-content-shell space-y-4 p-5">
|
||||
{isSourceEditable ? (
|
||||
<>
|
||||
<IssueTitleInput
|
||||
@@ -239,8 +239,8 @@ export const ExternalContoursIssueMainContent = observer(function ExternalContou
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-4 pb-4">
|
||||
<div className="space-y-4">
|
||||
<div className="nodedc-external-content-shell space-y-4 p-5">
|
||||
{duplicateIssues.length > 0 && (
|
||||
<DeDupeIssuePopoverRoot
|
||||
workspaceSlug={workspaceSlug}
|
||||
@@ -313,15 +313,13 @@ export const ExternalContoursIssueMainContent = observer(function ExternalContou
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-4">
|
||||
<ExternalContoursRequestTraceability contourRequest={contourRequest} />
|
||||
</div>
|
||||
<ExternalContoursRequestTraceability contourRequest={contourRequest} />
|
||||
|
||||
<div className="py-4">
|
||||
<div className="nodedc-external-section px-4 py-4">
|
||||
<IssueAttachmentRoot workspaceSlug={workspaceSlug} projectId={targetProjectId} issueId={issue.id} disabled={!isEditable} />
|
||||
</div>
|
||||
|
||||
<div className="py-4">
|
||||
<div className="nodedc-external-section px-4 py-4">
|
||||
<ExternalContoursIssueContentProperties
|
||||
workspaceSlug={workspaceSlug}
|
||||
targetProjectId={targetProjectId}
|
||||
@@ -331,9 +329,9 @@ export const ExternalContoursIssueMainContent = observer(function ExternalContou
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="pt-4">
|
||||
<div className="nodedc-external-section px-4 py-4">
|
||||
<IssueActivity workspaceSlug={workspaceSlug} projectId={targetProjectId} issueId={issue.id} />
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -42,8 +42,11 @@ export const ExternalContoursListItem = observer(function ExternalContoursListIt
|
||||
if (!request || !issue) return <></>;
|
||||
|
||||
const assigneeDetails = issue.assignee_details?.slice(0, 2) ?? [];
|
||||
const visibleLabels = issue.label_details?.slice(0, 3) ?? [];
|
||||
const lastUpdatedAt = issue.updated_at || request.updated_at;
|
||||
const requester = issue.created_by_detail;
|
||||
const requesterName =
|
||||
request.requested_by_name || requester?.display_name || t("external_contours_page.mirror.system_actor");
|
||||
const contourName = request.target_project_name || issue.project_detail?.name || t("common.none");
|
||||
|
||||
return (
|
||||
<Link
|
||||
@@ -55,38 +58,71 @@ export const ExternalContoursListItem = observer(function ExternalContoursListIt
|
||||
<Row
|
||||
data-active={selectedInboxIssueId === request.id}
|
||||
className={cn(
|
||||
"nodedc-external-card relative flex cursor-pointer flex-col gap-4 px-4 py-4 transition-all hover:bg-white/5",
|
||||
"nodedc-external-card relative flex min-h-[15rem] cursor-pointer flex-col gap-5 px-5 py-5 transition-all hover:bg-white/5",
|
||||
{ "ring-1 ring-accent-primary/35": selectedInboxIssueId === request.id }
|
||||
)}
|
||||
>
|
||||
<div className="space-y-2">
|
||||
<div className="relative flex items-center justify-between gap-2">
|
||||
<div className="flex min-w-0 items-center gap-2 text-11 font-medium text-tertiary">
|
||||
<span>
|
||||
{issue.project_detail?.identifier || "REQ"}-{issue.sequence_id}
|
||||
</span>
|
||||
{issue.project_detail?.name && <span className="truncate text-placeholder">{issue.project_detail.name}</span>}
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<Avatar
|
||||
src={requester?.avatar_url || ""}
|
||||
name={requesterName}
|
||||
size="lg"
|
||||
showTooltip
|
||||
/>
|
||||
<div className="min-w-0">
|
||||
<div className="truncate text-15 font-semibold text-primary">{requesterName}</div>
|
||||
<div className="truncate text-13 text-secondary">{contourName}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
<div className="text-11 font-medium text-tertiary">
|
||||
{issue.project_detail?.identifier || "REQ"}-{issue.sequence_id}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{request.has_unread_updates && (
|
||||
<Tooltip tooltipHeading={t("external_contours_page.list.unread_updates")} isMobile={isMobile}>
|
||||
<span className="size-2 rounded-full bg-accent-primary" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
<ExternalContourStatePill request={request} />
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="w-full text-15 leading-6 font-semibold text-primary">{issue.name}</h3>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<div className="flex flex-1 items-center justify-center px-2">
|
||||
<h3 className="line-clamp-3 w-full max-w-[18rem] text-center text-16 leading-7 font-semibold text-primary">
|
||||
{issue.name}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
{assigneeDetails.length > 0 ? (
|
||||
assigneeDetails.map((assignee) => (
|
||||
<Avatar
|
||||
key={assignee.id}
|
||||
src={assignee.avatar_url || ""}
|
||||
name={assignee.display_name || "NODE.DC"}
|
||||
size="lg"
|
||||
showTooltip
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<Tooltip tooltipHeading={t("assignee")} tooltipContent={t("external_contours_page.list.unassigned")} isMobile={isMobile}>
|
||||
<div className="text-11 text-placeholder">{t("external_contours_page.list.unassigned")}</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center justify-end gap-2">
|
||||
<Tooltip
|
||||
tooltipHeading={t("external_contours_page.list.last_updated")}
|
||||
tooltipContent={`${renderFormattedDate(lastUpdatedAt ?? "")}`}
|
||||
isMobile={isMobile}
|
||||
>
|
||||
<div className="text-11 text-secondary">{renderFormattedDate(lastUpdatedAt ?? "")}</div>
|
||||
<div className="rounded-full bg-white/6 px-3 py-1.5 text-12 text-secondary">
|
||||
{renderFormattedDate(lastUpdatedAt ?? "")}
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
{issue.priority && issue.priority !== "none" && (
|
||||
@@ -94,32 +130,6 @@ export const ExternalContoursListItem = observer(function ExternalContoursListIt
|
||||
<PriorityIcon priority={issue.priority} withContainer className="h-3 w-3" />
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{visibleLabels.map((label) => (
|
||||
<div key={label.id} className="relative flex h-6 items-center gap-1 rounded-full bg-white/6 px-2 text-11">
|
||||
<span className="h-2 w-2 rounded-full" style={{ backgroundColor: label.color }} />
|
||||
<span className="max-w-28 truncate normal-case">{label.name}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{assigneeDetails.length > 0 ? (
|
||||
<>
|
||||
{assigneeDetails.map((assignee) => (
|
||||
<Avatar
|
||||
key={assignee.id}
|
||||
src={assignee.avatar_url || ""}
|
||||
name={assignee.display_name || "NODE.DC"}
|
||||
size="lg"
|
||||
showTooltip
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<Tooltip tooltipHeading={t("assignee")} tooltipContent={t("external_contours_page.list.unassigned")} isMobile={isMobile}>
|
||||
<div className="text-11 text-placeholder">{t("external_contours_page.list.unassigned")}</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Row>
|
||||
|
||||
Reference in New Issue
Block a user