forked from cardosofelipe/pragma-stack
refactor(frontend): clean up code by consolidating multi-line JSX into single lines where feasible
- Refactored JSX elements to improve readability by collapsing multi-line props and attributes into single lines if their length permits. - Improved consistency in component imports by grouping and consolidating them. - No functional changes, purely restructuring for clarity and maintainability.
This commit is contained in:
@@ -248,12 +248,47 @@ const EVENT_TYPE_CONFIG: Record<
|
||||
};
|
||||
|
||||
const FILTER_CATEGORIES = [
|
||||
{ id: 'agent', label: 'Agent Actions', types: [EventType.AGENT_SPAWNED, EventType.AGENT_MESSAGE, EventType.AGENT_STATUS_CHANGED, EventType.AGENT_TERMINATED] },
|
||||
{ id: 'issue', label: 'Issues', types: [EventType.ISSUE_CREATED, EventType.ISSUE_UPDATED, EventType.ISSUE_ASSIGNED, EventType.ISSUE_CLOSED] },
|
||||
{
|
||||
id: 'agent',
|
||||
label: 'Agent Actions',
|
||||
types: [
|
||||
EventType.AGENT_SPAWNED,
|
||||
EventType.AGENT_MESSAGE,
|
||||
EventType.AGENT_STATUS_CHANGED,
|
||||
EventType.AGENT_TERMINATED,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'issue',
|
||||
label: 'Issues',
|
||||
types: [
|
||||
EventType.ISSUE_CREATED,
|
||||
EventType.ISSUE_UPDATED,
|
||||
EventType.ISSUE_ASSIGNED,
|
||||
EventType.ISSUE_CLOSED,
|
||||
],
|
||||
},
|
||||
{ id: 'sprint', label: 'Sprints', types: [EventType.SPRINT_STARTED, EventType.SPRINT_COMPLETED] },
|
||||
{ id: 'approval', label: 'Approvals', types: [EventType.APPROVAL_REQUESTED, EventType.APPROVAL_GRANTED, EventType.APPROVAL_DENIED] },
|
||||
{ id: 'workflow', label: 'Workflows', types: [EventType.WORKFLOW_STARTED, EventType.WORKFLOW_STEP_COMPLETED, EventType.WORKFLOW_COMPLETED, EventType.WORKFLOW_FAILED] },
|
||||
{ id: 'project', label: 'Projects', types: [EventType.PROJECT_CREATED, EventType.PROJECT_UPDATED, EventType.PROJECT_ARCHIVED] },
|
||||
{
|
||||
id: 'approval',
|
||||
label: 'Approvals',
|
||||
types: [EventType.APPROVAL_REQUESTED, EventType.APPROVAL_GRANTED, EventType.APPROVAL_DENIED],
|
||||
},
|
||||
{
|
||||
id: 'workflow',
|
||||
label: 'Workflows',
|
||||
types: [
|
||||
EventType.WORKFLOW_STARTED,
|
||||
EventType.WORKFLOW_STEP_COMPLETED,
|
||||
EventType.WORKFLOW_COMPLETED,
|
||||
EventType.WORKFLOW_FAILED,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'project',
|
||||
label: 'Projects',
|
||||
types: [EventType.PROJECT_CREATED, EventType.PROJECT_UPDATED, EventType.PROJECT_ARCHIVED],
|
||||
},
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
@@ -266,25 +301,60 @@ function getEventConfig(event: ProjectEvent) {
|
||||
|
||||
// Fallback based on event category
|
||||
if (isAgentEvent(event)) {
|
||||
return { icon: Bot, label: event.type, color: 'text-blue-500', bgColor: 'bg-blue-100 dark:bg-blue-900' };
|
||||
return {
|
||||
icon: Bot,
|
||||
label: event.type,
|
||||
color: 'text-blue-500',
|
||||
bgColor: 'bg-blue-100 dark:bg-blue-900',
|
||||
};
|
||||
}
|
||||
if (isIssueEvent(event)) {
|
||||
return { icon: FileText, label: event.type, color: 'text-green-500', bgColor: 'bg-green-100 dark:bg-green-900' };
|
||||
return {
|
||||
icon: FileText,
|
||||
label: event.type,
|
||||
color: 'text-green-500',
|
||||
bgColor: 'bg-green-100 dark:bg-green-900',
|
||||
};
|
||||
}
|
||||
if (isSprintEvent(event)) {
|
||||
return { icon: PlayCircle, label: event.type, color: 'text-indigo-500', bgColor: 'bg-indigo-100 dark:bg-indigo-900' };
|
||||
return {
|
||||
icon: PlayCircle,
|
||||
label: event.type,
|
||||
color: 'text-indigo-500',
|
||||
bgColor: 'bg-indigo-100 dark:bg-indigo-900',
|
||||
};
|
||||
}
|
||||
if (isApprovalEvent(event)) {
|
||||
return { icon: AlertTriangle, label: event.type, color: 'text-orange-500', bgColor: 'bg-orange-100 dark:bg-orange-900' };
|
||||
return {
|
||||
icon: AlertTriangle,
|
||||
label: event.type,
|
||||
color: 'text-orange-500',
|
||||
bgColor: 'bg-orange-100 dark:bg-orange-900',
|
||||
};
|
||||
}
|
||||
if (isWorkflowEvent(event)) {
|
||||
return { icon: Workflow, label: event.type, color: 'text-cyan-500', bgColor: 'bg-cyan-100 dark:bg-cyan-900' };
|
||||
return {
|
||||
icon: Workflow,
|
||||
label: event.type,
|
||||
color: 'text-cyan-500',
|
||||
bgColor: 'bg-cyan-100 dark:bg-cyan-900',
|
||||
};
|
||||
}
|
||||
if (isProjectEvent(event)) {
|
||||
return { icon: Folder, label: event.type, color: 'text-teal-500', bgColor: 'bg-teal-100 dark:bg-teal-900' };
|
||||
return {
|
||||
icon: Folder,
|
||||
label: event.type,
|
||||
color: 'text-teal-500',
|
||||
bgColor: 'bg-teal-100 dark:bg-teal-900',
|
||||
};
|
||||
}
|
||||
|
||||
return { icon: Activity, label: event.type, color: 'text-gray-500', bgColor: 'bg-gray-100 dark:bg-gray-800' };
|
||||
return {
|
||||
icon: Activity,
|
||||
label: event.type,
|
||||
color: 'text-gray-500',
|
||||
bgColor: 'bg-gray-100 dark:bg-gray-800',
|
||||
};
|
||||
}
|
||||
|
||||
function getEventSummary(event: ProjectEvent): string {
|
||||
@@ -304,7 +374,9 @@ function getEventSummary(event: ProjectEvent): string {
|
||||
case EventType.ISSUE_UPDATED:
|
||||
return `Issue ${payload.issue_id || ''} updated`;
|
||||
case EventType.ISSUE_ASSIGNED:
|
||||
return payload.assignee_name ? `Assigned to ${payload.assignee_name}` : 'Issue assignment changed';
|
||||
return payload.assignee_name
|
||||
? `Assigned to ${payload.assignee_name}`
|
||||
: 'Issue assignment changed';
|
||||
case EventType.ISSUE_CLOSED:
|
||||
return payload.resolution ? `Closed: ${payload.resolution}` : 'Issue closed';
|
||||
case EventType.SPRINT_STARTED:
|
||||
@@ -318,11 +390,15 @@ function getEventSummary(event: ProjectEvent): string {
|
||||
case EventType.APPROVAL_DENIED:
|
||||
return payload.reason ? `Denied: ${payload.reason}` : 'Approval denied';
|
||||
case EventType.WORKFLOW_STARTED:
|
||||
return payload.workflow_type ? `${payload.workflow_type} workflow started` : 'Workflow started';
|
||||
return payload.workflow_type
|
||||
? `${payload.workflow_type} workflow started`
|
||||
: 'Workflow started';
|
||||
case EventType.WORKFLOW_STEP_COMPLETED:
|
||||
return `Step ${payload.step_number}/${payload.total_steps}: ${payload.step_name || 'completed'}`;
|
||||
case EventType.WORKFLOW_COMPLETED:
|
||||
return payload.duration_seconds ? `Completed in ${payload.duration_seconds}s` : 'Workflow completed';
|
||||
return payload.duration_seconds
|
||||
? `Completed in ${payload.duration_seconds}s`
|
||||
: 'Workflow completed';
|
||||
case EventType.WORKFLOW_FAILED:
|
||||
return payload.error_message ? String(payload.error_message) : 'Workflow failed';
|
||||
default:
|
||||
@@ -391,11 +467,7 @@ function ConnectionIndicator({ state, onReconnect, className }: ConnectionIndica
|
||||
return (
|
||||
<div className={cn('flex items-center gap-2', className)} data-testid="connection-indicator">
|
||||
<span
|
||||
className={cn(
|
||||
'h-2 w-2 rounded-full',
|
||||
config.color,
|
||||
config.pulse && 'animate-pulse'
|
||||
)}
|
||||
className={cn('h-2 w-2 rounded-full', config.color, config.pulse && 'animate-pulse')}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="text-sm text-muted-foreground">{config.label}</span>
|
||||
@@ -475,7 +547,10 @@ function FilterPanel({
|
||||
checked={showPendingOnly}
|
||||
onCheckedChange={(checked) => onShowPendingOnlyChange(checked as boolean)}
|
||||
/>
|
||||
<Label htmlFor="filter-pending" className="flex items-center gap-1 text-sm font-normal cursor-pointer">
|
||||
<Label
|
||||
htmlFor="filter-pending"
|
||||
className="flex items-center gap-1 text-sm font-normal cursor-pointer"
|
||||
>
|
||||
Show only pending approvals
|
||||
{pendingCount > 0 && (
|
||||
<Badge variant="destructive" className="text-xs">
|
||||
@@ -598,77 +673,85 @@ function EventItem({
|
||||
}}
|
||||
aria-label={expanded ? 'Collapse details' : 'Expand details'}
|
||||
>
|
||||
{expanded ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />}
|
||||
{expanded ? (
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Expanded Details */}
|
||||
{expanded && (() => {
|
||||
const issueId = payload.issue_id as string | undefined;
|
||||
const pullRequest = payload.pullRequest as string | number | undefined;
|
||||
const documentUrl = payload.documentUrl as string | undefined;
|
||||
const progress = payload.progress as number | undefined;
|
||||
{expanded &&
|
||||
(() => {
|
||||
const issueId = payload.issue_id as string | undefined;
|
||||
const pullRequest = payload.pullRequest as string | number | undefined;
|
||||
const documentUrl = payload.documentUrl as string | undefined;
|
||||
const progress = payload.progress as number | undefined;
|
||||
|
||||
return (
|
||||
<div className="mt-3 rounded-md bg-muted/50 p-3 space-y-3" data-testid="event-details">
|
||||
{/* Issue/PR Links */}
|
||||
{issueId && (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<CircleDot className="h-4 w-4" aria-hidden="true" />
|
||||
<span>Issue #{issueId}</span>
|
||||
</div>
|
||||
)}
|
||||
{pullRequest && (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<GitPullRequest className="h-4 w-4" aria-hidden="true" />
|
||||
<span>PR #{String(pullRequest)}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Document Links */}
|
||||
{documentUrl && (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<ExternalLink className="h-4 w-4" aria-hidden="true" />
|
||||
<a href={documentUrl} className="text-primary hover:underline">
|
||||
{documentUrl}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Progress */}
|
||||
{progress !== undefined && (
|
||||
<div className="space-y-1">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span>Progress</span>
|
||||
<span>{progress}%</span>
|
||||
return (
|
||||
<div
|
||||
className="mt-3 rounded-md bg-muted/50 p-3 space-y-3"
|
||||
data-testid="event-details"
|
||||
>
|
||||
{/* Issue/PR Links */}
|
||||
{issueId && (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<CircleDot className="h-4 w-4" aria-hidden="true" />
|
||||
<span>Issue #{issueId}</span>
|
||||
</div>
|
||||
<div className="h-2 rounded-full bg-muted">
|
||||
<div
|
||||
className="h-full rounded-full bg-primary"
|
||||
style={{ width: `${progress}%` }}
|
||||
/>
|
||||
)}
|
||||
{pullRequest && (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<GitPullRequest className="h-4 w-4" aria-hidden="true" />
|
||||
<span>PR #{String(pullRequest)}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{/* Timestamp */}
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{new Date(event.timestamp).toLocaleString()}
|
||||
</p>
|
||||
{/* Document Links */}
|
||||
{documentUrl && (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<ExternalLink className="h-4 w-4" aria-hidden="true" />
|
||||
<a href={documentUrl} className="text-primary hover:underline">
|
||||
{documentUrl}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Raw Payload (for debugging) */}
|
||||
<details className="text-xs">
|
||||
<summary className="cursor-pointer text-muted-foreground hover:text-foreground">
|
||||
View raw payload
|
||||
</summary>
|
||||
<pre className="mt-2 overflow-x-auto whitespace-pre-wrap break-words rounded bg-muted p-2">
|
||||
{JSON.stringify(event.payload, null, 2)}
|
||||
</pre>
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
{/* Progress */}
|
||||
{progress !== undefined && (
|
||||
<div className="space-y-1">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span>Progress</span>
|
||||
<span>{progress}%</span>
|
||||
</div>
|
||||
<div className="h-2 rounded-full bg-muted">
|
||||
<div
|
||||
className="h-full rounded-full bg-primary"
|
||||
style={{ width: `${progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Timestamp */}
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{new Date(event.timestamp).toLocaleString()}
|
||||
</p>
|
||||
|
||||
{/* Raw Payload (for debugging) */}
|
||||
<details className="text-xs">
|
||||
<summary className="cursor-pointer text-muted-foreground hover:text-foreground">
|
||||
View raw payload
|
||||
</summary>
|
||||
<pre className="mt-2 overflow-x-auto whitespace-pre-wrap break-words rounded bg-muted p-2">
|
||||
{JSON.stringify(event.payload, null, 2)}
|
||||
</pre>
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* Approval Actions */}
|
||||
{isPendingApproval && (onApprove || onReject) && (
|
||||
@@ -680,7 +763,12 @@ function EventItem({
|
||||
</Button>
|
||||
)}
|
||||
{onReject && (
|
||||
<Button variant="outline" size="sm" onClick={handleReject} data-testid="reject-button">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleReject}
|
||||
data-testid="reject-button"
|
||||
>
|
||||
<XCircle className="mr-2 h-4 w-4" />
|
||||
Reject
|
||||
</Button>
|
||||
@@ -712,7 +800,10 @@ function LoadingSkeleton() {
|
||||
|
||||
function EmptyState({ hasFilters }: { hasFilters: boolean }) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground" data-testid="empty-state">
|
||||
<div
|
||||
className="flex flex-col items-center justify-center py-12 text-muted-foreground"
|
||||
data-testid="empty-state"
|
||||
>
|
||||
<Activity className="h-12 w-12 mb-4" aria-hidden="true" />
|
||||
<h3 className="font-semibold">No activity found</h3>
|
||||
<p className="text-sm">
|
||||
@@ -894,7 +985,10 @@ export function ActivityFeed({
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{groupedEvents.map((group) => (
|
||||
<div key={group.label} data-testid={`event-group-${group.label.toLowerCase().replace(' ', '-')}`}>
|
||||
<div
|
||||
key={group.label}
|
||||
data-testid={`event-group-${group.label.toLowerCase().replace(' ', '-')}`}
|
||||
>
|
||||
<div className="mb-3 flex items-center gap-2">
|
||||
<h3 className="text-sm font-medium text-muted-foreground">{group.label}</h3>
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
|
||||
Reference in New Issue
Block a user