feat: refine workbench visuals and map controls
Unify the Agent history extension with the header acrylic surface, preserve the full conversation body, and consolidate shared control and status styling. Restore map flow and SCADA controller behavior, remove obsolete rendering paths, and extend regression coverage. Button press coverage now releases outside the target so state assertions cannot accidentally toggle the control.
This commit is contained in:
@@ -21,6 +21,7 @@ import {
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger
|
||||
} from "@/shared/ui/dropdown-menu";
|
||||
import { StatusIcon as SemanticStatusIcon } from "@/shared/ui/status";
|
||||
import { ConditionDetailPanel, StatusPill } from "./scheduled-condition-detail-panel";
|
||||
import {
|
||||
CONDITION_FILTER_ALL,
|
||||
@@ -28,8 +29,12 @@ import {
|
||||
createTaskFilterOptions,
|
||||
formatTime,
|
||||
getConditionTime,
|
||||
getStatusActivity,
|
||||
getStatusIcon,
|
||||
getStatusIconClassName,
|
||||
getStatusTone,
|
||||
getWorkOrderCurrentStage,
|
||||
getWorkOrderStageActivity,
|
||||
getWorkOrderStageTone,
|
||||
groupScheduledConditions,
|
||||
type ConditionStatusFilterOption,
|
||||
type ConditionStatusFilterValue,
|
||||
@@ -286,7 +291,12 @@ export function ScheduledConditionFeed({
|
||||
MAP_TOOL_PANEL_SURFACE_CLASS_NAME,
|
||||
"max-w-[calc(100vw-7rem)]",
|
||||
desktopFloating
|
||||
? "max-h-[calc(100dvh-8rem)] rounded-2xl"
|
||||
? cn(
|
||||
"rounded-2xl",
|
||||
expanded
|
||||
? "h-[calc(100dvh-8rem)]"
|
||||
: "max-h-[calc(100dvh-8rem)]"
|
||||
)
|
||||
: "h-full rounded-l-2xl rounded-r-none",
|
||||
expanded
|
||||
? "scheduled-feed-panel-expanded flex flex-col"
|
||||
@@ -334,11 +344,9 @@ export function ScheduledConditionFeed({
|
||||
aria-label="关闭工况任务"
|
||||
title="关闭工况任务"
|
||||
onClick={onClose}
|
||||
className="group grid h-10 w-10 shrink-0 place-items-center rounded-full text-slate-500"
|
||||
className="agent-panel-icon-button grid h-10 w-10 shrink-0 place-items-center rounded-xl text-slate-600"
|
||||
>
|
||||
<span className="surface-control grid h-9 w-9 place-items-center rounded-full shadow-[inset_0_0_0_1px_rgba(148,163,184,0.26),0_4px_12px_rgba(15,33,55,0.10)] transition-[color,background-color,scale] group-active:scale-95 [@media(hover:hover)]:group-hover:bg-blue-50 [@media(hover:hover)]:group-hover:text-blue-700">
|
||||
<X size={17} strokeWidth={2.25} aria-hidden="true" />
|
||||
</span>
|
||||
<X size={17} aria-hidden="true" />
|
||||
</button>
|
||||
) : null}
|
||||
</header>
|
||||
@@ -349,19 +357,19 @@ export function ScheduledConditionFeed({
|
||||
mobileSheet
|
||||
? "flex flex-1"
|
||||
: expanded
|
||||
? "scheduled-feed-layout-expanded"
|
||||
? "scheduled-feed-layout-expanded flex-1"
|
||||
: "scheduled-feed-layout-collapsed"
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"min-h-0 min-w-0 overflow-hidden",
|
||||
"relative min-h-0 min-w-0 overflow-hidden",
|
||||
mobileSheet
|
||||
? expanded
|
||||
? "scheduled-feed-detail-enter h-full w-full"
|
||||
: "hidden"
|
||||
: expanded
|
||||
? "scheduled-feed-detail-enter"
|
||||
? "scheduled-feed-detail-enter h-full"
|
||||
: "pointer-events-none opacity-0"
|
||||
)}
|
||||
aria-hidden={!expanded}
|
||||
@@ -459,7 +467,7 @@ function ConditionTimelinePanel({
|
||||
presentation === "mobile-sheet"
|
||||
? "h-full max-h-none"
|
||||
: expanded
|
||||
? "h-[calc(100dvh-8.75rem)] max-h-[calc(100dvh-8.75rem)]"
|
||||
? "h-full max-h-none"
|
||||
: "h-[420px] max-h-[420px]",
|
||||
CONDITION_CONTENT_SURFACE_CLASS_NAME
|
||||
)}
|
||||
@@ -486,7 +494,7 @@ function ConditionTimelinePanel({
|
||||
count={futureWorkOrders.length}
|
||||
compact
|
||||
/>
|
||||
<div className="scheduled-feed-scroll max-h-[132px] overflow-y-auto pr-1">
|
||||
<div className="scheduled-feed-scroll -mr-2 max-h-[132px] overflow-y-auto pl-0.5 pr-2 pt-0.5">
|
||||
<ConditionRows
|
||||
conditions={futureWorkOrders}
|
||||
selectedConditionId={selectedConditionId}
|
||||
@@ -583,7 +591,7 @@ function ConditionFilterBar({
|
||||
<button
|
||||
type="button"
|
||||
onClick={onResetFilters}
|
||||
className="surface-control mt-2 inline-flex h-8 w-full items-center justify-center gap-1.5 rounded-lg border border-slate-200/70 px-2 text-xs font-semibold text-blue-700 transition-[background-color,transform] hover:bg-blue-50 active:scale-95"
|
||||
className="surface-control relative mt-2 inline-flex h-8 w-full items-center justify-center gap-1.5 rounded-lg border border-slate-200/70 px-2 text-xs font-semibold text-blue-700 after:absolute after:-inset-1 after:content-[''] hover:bg-blue-50"
|
||||
>
|
||||
<XCircle size={12} aria-hidden="true" />
|
||||
重置筛选
|
||||
@@ -722,10 +730,7 @@ function ConditionDetailSkeleton() {
|
||||
return (
|
||||
<DetailScroll>
|
||||
<div
|
||||
className={cn(
|
||||
"surface-reading relative min-h-[420px] overflow-hidden border border-slate-200/70 px-4 py-3",
|
||||
"rounded-xl"
|
||||
)}
|
||||
className="relative min-h-[420px] overflow-hidden px-4 py-3"
|
||||
role="status"
|
||||
aria-label="工况详情加载中"
|
||||
>
|
||||
@@ -760,8 +765,18 @@ function ConditionDetailSkeleton() {
|
||||
|
||||
function DetailScroll({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<section className="scheduled-feed-scroll scheduled-feed-detail-scroll max-h-[calc(100dvh-14rem)] min-h-0 overflow-y-scroll py-0.5 pl-0.5 pr-3">
|
||||
{children}
|
||||
<section
|
||||
className={cn(
|
||||
CONDITION_CONTENT_SURFACE_CLASS_NAME,
|
||||
"scheduled-feed-detail-viewport absolute inset-0 overflow-hidden"
|
||||
)}
|
||||
>
|
||||
<div
|
||||
data-testid="scheduled-condition-detail-scroll"
|
||||
className="scheduled-feed-scroll absolute inset-x-0 inset-y-2 overflow-y-auto pb-4 pl-0.5 pr-2 pt-0.5"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -830,8 +845,15 @@ function ConditionTimelineRow({
|
||||
isLast,
|
||||
onSelect
|
||||
}: ConditionTimelineRowProps) {
|
||||
const StatusIcon = getStatusIcon(condition.status);
|
||||
const Icon = getStatusIcon(condition.status);
|
||||
const isWorkOrder = condition.kind === "work_order";
|
||||
const workOrderStage = isWorkOrder ? getWorkOrderCurrentStage(condition)?.id : undefined;
|
||||
const tone = isWorkOrder
|
||||
? getWorkOrderStageTone(workOrderStage)
|
||||
: getStatusTone(condition.status);
|
||||
const activity = isWorkOrder
|
||||
? getWorkOrderStageActivity(workOrderStage)
|
||||
: getStatusActivity(condition.status);
|
||||
|
||||
return (
|
||||
<button
|
||||
@@ -855,22 +877,18 @@ function ConditionTimelineRow({
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : null}
|
||||
<span
|
||||
<SemanticStatusIcon
|
||||
tone={tone}
|
||||
activity={activity}
|
||||
shape="circle"
|
||||
data-selected={selected ? "true" : undefined}
|
||||
className={cn(
|
||||
"relative z-10 grid h-6 w-6 place-items-center rounded-full ring-4 ring-white",
|
||||
selected
|
||||
? "bg-blue-600 text-white group-focus-visible:ring-blue-100"
|
||||
: isWorkOrder
|
||||
? "bg-blue-50 text-blue-700 group-focus-visible:bg-blue-100 group-focus-visible:text-blue-800"
|
||||
: getStatusIconClassName(condition.status)
|
||||
"relative z-10 ring-4 ring-white",
|
||||
selected && "group-focus-visible:ring-blue-100"
|
||||
)}
|
||||
>
|
||||
<StatusIcon
|
||||
size={12}
|
||||
aria-hidden="true"
|
||||
className={condition.status === "running" ? "animate-spin" : undefined}
|
||||
/>
|
||||
</span>
|
||||
<Icon size={12} />
|
||||
</SemanticStatusIcon>
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
|
||||
Reference in New Issue
Block a user