refactor(automations): enhance UI layout and styling for automation components, including adjustments to spacing, alignment, and badge presentation

This commit is contained in:
Anish Sarkar 2026-06-03 19:47:33 +05:30
parent 75c8063bea
commit 14f339bba0
10 changed files with 50 additions and 75 deletions

View file

@ -8,7 +8,6 @@ import { updateAutomationMutationAtom } from "@/atoms/automations/automations-mu
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner"; import { Spinner } from "@/components/ui/spinner";
import type { Automation } from "@/contracts/types/automation.types"; import type { Automation } from "@/contracts/types/automation.types";
import { AutomationStatusBadge } from "../../components/automation-status-badge";
import { DeleteAutomationDialog } from "../../components/delete-automation-dialog"; import { DeleteAutomationDialog } from "../../components/delete-automation-dialog";
interface AutomationDetailHeaderProps { interface AutomationDetailHeaderProps {
@ -70,12 +69,9 @@ export function AutomationDetailHeader({
<div className="flex items-start justify-between gap-4 flex-wrap"> <div className="flex items-start justify-between gap-4 flex-wrap">
<div className="space-y-2 min-w-0 flex-1"> <div className="space-y-2 min-w-0 flex-1">
<div className="flex items-center gap-3 flex-wrap"> <h1 className="text-xl md:text-2xl font-semibold text-foreground break-words">
<h1 className="text-xl md:text-2xl font-semibold text-foreground break-words"> {automation.name}
{automation.name} </h1>
</h1>
<AutomationStatusBadge status={automation.status} />
</div>
{automation.description && ( {automation.description && (
<p className="text-sm text-muted-foreground max-w-3xl">{automation.description}</p> <p className="text-sm text-muted-foreground max-w-3xl">{automation.description}</p>
)} )}

View file

@ -27,7 +27,6 @@ export function AutomationRunsSection({ automationId }: AutomationRunsSectionPro
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4">
<div className="space-y-1"> <div className="space-y-1">
<CardTitle className="text-base font-semibold inline-flex items-center gap-2"> <CardTitle className="text-base font-semibold inline-flex items-center gap-2">
<History className="h-4 w-4 text-muted-foreground" aria-hidden />
Recent runs Recent runs
</CardTitle> </CardTitle>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">

View file

@ -8,7 +8,6 @@ import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import type { AutomationSummary } from "@/contracts/types/automation.types"; import type { AutomationSummary } from "@/contracts/types/automation.types";
@ -58,25 +57,21 @@ export function AutomationRowActions({
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
className="h-8 w-8" className="h-6 w-6 hover:bg-transparent"
aria-label={`Actions for ${automation.name}`} aria-label={`Actions for ${automation.name}`}
> >
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontal className="h-3.5 w-3.5 text-muted-foreground" />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-40"> <DropdownMenuContent align="end" className="w-32 z-80">
{canToggle && ( {canToggle && (
<DropdownMenuItem onSelect={handleTogglePause} disabled={updating}> <DropdownMenuItem onSelect={handleTogglePause} disabled={updating}>
<PauseIcon className="mr-2 h-4 w-4" /> <PauseIcon className="mr-2 h-4 w-4" />
{pauseLabel} {pauseLabel}
</DropdownMenuItem> </DropdownMenuItem>
)} )}
{canToggle && canDelete && <DropdownMenuSeparator />}
{canDelete && ( {canDelete && (
<DropdownMenuItem <DropdownMenuItem onSelect={() => setDeleteOpen(true)}>
onSelect={() => setDeleteOpen(true)}
className="text-destructive focus:text-destructive"
>
<Trash2 className="mr-2 h-4 w-4" /> <Trash2 className="mr-2 h-4 w-4" />
Delete Delete
</DropdownMenuItem> </DropdownMenuItem>

View file

@ -26,35 +26,30 @@ export function AutomationRow({
canDelete, canDelete,
}: AutomationRowProps) { }: AutomationRowProps) {
return ( return (
<TableRow className="border-b border-border/60 hover:bg-muted/40"> <TableRow className="h-12 border-b border-border/60 hover:bg-muted/40">
<TableCell className="px-4 md:px-6 py-3 border-r border-border/60"> <TableCell className="px-4 md:px-6 py-2.5 border-r border-border/60 align-middle">
<div className="flex flex-col gap-0.5 min-w-0"> <Link
<Link href={`/dashboard/${searchSpaceId}/automations/${automation.id}`}
href={`/dashboard/${searchSpaceId}/automations/${automation.id}`} className="block truncate text-sm font-medium text-foreground hover:underline"
className="text-sm font-medium text-foreground hover:underline truncate" >
> {automation.name}
{automation.name} </Link>
</Link>
{automation.description && (
<span className="text-xs text-muted-foreground line-clamp-1">
{automation.description}
</span>
)}
</div>
</TableCell> </TableCell>
<TableCell className="px-4 py-3 border-r border-border/60 w-32"> <TableCell className="px-4 py-2.5 border-r border-border/60 w-32 align-middle">
<AutomationStatusBadge status={automation.status} /> <AutomationStatusBadge status={automation.status} />
</TableCell> </TableCell>
<TableCell className="hidden md:table-cell px-4 py-3 border-r border-border/60 w-40 text-xs text-muted-foreground"> <TableCell className="hidden md:table-cell px-4 py-2.5 border-r border-border/60 w-40 align-middle text-xs text-muted-foreground">
{formatRelativeDate(automation.updated_at)} {formatRelativeDate(automation.updated_at)}
</TableCell> </TableCell>
<TableCell className="px-4 md:px-6 py-3 w-16 text-right"> <TableCell className="px-4 md:px-6 py-2.5 w-16 align-middle">
<AutomationRowActions <div className="flex justify-end">
automation={automation} <AutomationRowActions
searchSpaceId={searchSpaceId} automation={automation}
canUpdate={canUpdate} searchSpaceId={searchSpaceId}
canDelete={canDelete} canUpdate={canUpdate}
/> canDelete={canDelete}
/>
</div>
</TableCell> </TableCell>
</TableRow> </TableRow>
); );

View file

@ -1,5 +1,4 @@
"use client"; "use client";
import { Archive, CircleDot, Pause } from "lucide-react";
import type { AutomationStatus } from "@/contracts/types/automation.types"; import type { AutomationStatus } from "@/contracts/types/automation.types";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@ -8,41 +7,37 @@ interface AutomationStatusBadgeProps {
className?: string; className?: string;
} }
// Color + icon per status. Active = green, paused = amber, archived = muted. // Small borderless status pills, matching model-selector badges.
const STATUS_STYLES: Record< const STATUS_STYLES: Record<
AutomationStatus, AutomationStatus,
{ label: string; icon: typeof CircleDot; classes: string } { label: string; classes: string }
> = { > = {
active: { active: {
label: "Active", label: "Active",
icon: CircleDot,
classes: classes:
"bg-emerald-50 text-emerald-700 border border-emerald-200 dark:bg-emerald-950/40 dark:text-emerald-300 dark:border-emerald-900/50", "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/50 dark:text-emerald-300",
}, },
paused: { paused: {
label: "Paused", label: "Paused",
icon: Pause,
classes: classes:
"bg-amber-50 text-amber-700 border border-amber-200 dark:bg-amber-950/40 dark:text-amber-300 dark:border-amber-900/50", "bg-amber-100 text-amber-700 dark:bg-amber-900/50 dark:text-amber-300",
}, },
archived: { archived: {
label: "Archived", label: "Archived",
icon: Archive, classes: "bg-muted text-muted-foreground",
classes: "bg-muted text-muted-foreground border border-border/60",
}, },
}; };
export function AutomationStatusBadge({ status, className }: AutomationStatusBadgeProps) { export function AutomationStatusBadge({ status, className }: AutomationStatusBadgeProps) {
const { label, icon: Icon, classes } = STATUS_STYLES[status]; const { label, classes } = STATUS_STYLES[status];
return ( return (
<span <span
className={cn( className={cn(
"inline-flex items-center gap-1.5 rounded-md px-2 py-0.5 text-xs font-medium", "inline-flex items-center rounded-md border-0 px-1.5 py-0 text-sm font-medium leading-5",
classes, classes,
className className
)} )}
> >
<Icon className="h-3 w-3" aria-hidden />
{label} {label}
</span> </span>
); );

View file

@ -1,5 +1,5 @@
"use client"; "use client";
import { Activity, CalendarDays, Workflow } from "lucide-react"; import { CalendarDays, Info, Workflow } from "lucide-react";
import { Table, TableBody, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Table, TableBody, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import type { AutomationSummary } from "@/contracts/types/automation.types"; import type { AutomationSummary } from "@/contracts/types/automation.types";
import { AutomationRow } from "./automation-row"; import { AutomationRow } from "./automation-row";
@ -37,7 +37,7 @@ export function AutomationsTable({
</TableHead> </TableHead>
<TableHead className="border-r border-border/60 w-32"> <TableHead className="border-r border-border/60 w-32">
<span className="flex items-center gap-1.5 text-sm font-medium text-muted-foreground/70"> <span className="flex items-center gap-1.5 text-sm font-medium text-muted-foreground/70">
<Activity size={14} className="opacity-60 text-muted-foreground" /> <Info size={14} className="opacity-60 text-muted-foreground" />
Status Status
</span> </span>
</TableHead> </TableHead>

View file

@ -364,16 +364,12 @@ export function AutomationBuilderForm({
)} )}
{activeMode === "json" ? ( {activeMode === "json" ? (
<Card className="rounded-md border-accent bg-accent/20"> <JsonModePanel
<CardContent className="pt-6"> value={jsonValue}
<JsonModePanel issues={jsonIssues}
value={jsonValue} notice={jsonNotice}
issues={jsonIssues} onChange={setJsonValue}
notice={jsonNotice} />
onChange={setJsonValue}
/>
</CardContent>
</Card>
) : ( ) : (
<div className="grid grid-cols-1 gap-4 lg:grid-cols-3"> <div className="grid grid-cols-1 gap-4 lg:grid-cols-3">
<div className="lg:col-span-2"> <div className="lg:col-span-2">

View file

@ -26,14 +26,13 @@ export function JsonModePanel({ value, issues, notice, onChange }: JsonModePanel
</Alert> </Alert>
)} )}
<div className="rounded-md border border-input bg-background px-3 py-2 max-h-144 overflow-auto"> <JsonView
<JsonView src={value}
src={value} editable
editable onChange={(next) => onChange(next as Record<string, unknown>)}
onChange={(next) => onChange(next as Record<string, unknown>)} collapsed={false}
collapsed={false} className="max-h-144 overflow-auto rounded-md border border-accent bg-accent/20"
/> />
</div>
{issues.length > 0 && ( {issues.length > 0 && (
<Alert variant="destructive"> <Alert variant="destructive">

View file

@ -81,7 +81,7 @@ export function ScheduleSection({
type="button" type="button"
variant="ghost" variant="ghost"
size="icon" size="icon"
className="h-6 w-6 shrink-0 text-muted-foreground hover:text-destructive" className="h-6 w-6 shrink-0 text-muted-foreground hover:text-foreground"
aria-label="Remove schedule" aria-label="Remove schedule"
onClick={() => onScheduleChange(null)} onClick={() => onScheduleChange(null)}
> >

View file

@ -8,7 +8,7 @@ export default async function AutomationsPage({
const { search_space_id } = await params; const { search_space_id } = await params;
return ( return (
<div className="w-full space-y-6"> <div className="mx-auto w-full max-w-5xl space-y-6">
<AutomationsContent searchSpaceId={Number(search_space_id)} /> <AutomationsContent searchSpaceId={Number(search_space_id)} />
</div> </div>
); );