Skip to content

Commit a8385e2

Browse files
author
catlog22
committed
feat: Enhance Project Overview and Review Session pages with improved UI and functionality
- Updated ProjectOverviewPage to enhance the guidelines section with better spacing, larger icons, and improved button styles. - Refactored ReviewSessionPage to unify filter controls, improve selection actions, and enhance the findings list with a new layout. - Added dimension tabs and severity filters to the SessionsPage for better navigation and filtering. - Improved SessionDetailPage to utilize a mapping for status labels, enhancing internationalization support. - Refactored TaskListTab to remove unused priority configuration code. - Updated store types to better reflect session metadata structure. - Added a temporary JSON file for future use.
1 parent 37ba849 commit a8385e2

18 files changed

Lines changed: 1623 additions & 677 deletions

ccw/frontend/src/components/dashboard/widgets/WorkflowTaskWidget.tsx

Lines changed: 207 additions & 230 deletions
Large diffs are not rendered by default.

ccw/frontend/src/components/shared/SessionCard.tsx

Lines changed: 129 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -76,18 +76,18 @@ const statusLabelKeys: Record<SessionMetadata['status'], string> = {
7676
paused: 'sessions.status.paused',
7777
};
7878

79-
// Type variant configuration for session type badges
79+
// Type variant configuration for session type badges (unique colors for each type)
8080
const typeVariantConfig: Record<
8181
SessionMetadata['type'],
82-
{ variant: 'default' | 'secondary' | 'destructive' | 'success' | 'warning' | 'info'; icon: React.ElementType }
82+
{ variant: 'default' | 'secondary' | 'destructive' | 'success' | 'warning' | 'info' | 'review'; icon: React.ElementType }
8383
> = {
84-
review: { variant: 'info', icon: Search },
85-
'tdd': { variant: 'success', icon: TestTube },
86-
test: { variant: 'default', icon: FileText },
87-
docs: { variant: 'warning', icon: File },
88-
workflow: { variant: 'secondary', icon: Settings },
89-
'lite-plan': { variant: 'default', icon: FileText },
90-
'lite-fix': { variant: 'warning', icon: Zap },
84+
review: { variant: 'review', icon: Search }, // Purple
85+
'tdd': { variant: 'success', icon: TestTube }, // Green
86+
test: { variant: 'info', icon: FileText }, // Blue
87+
docs: { variant: 'warning', icon: File }, // Orange/Yellow
88+
workflow: { variant: 'default', icon: Settings }, // Primary (blue-violet)
89+
'lite-plan': { variant: 'secondary', icon: FileText }, // Gray/Neutral
90+
'lite-fix': { variant: 'destructive', icon: Zap }, // Red
9191
};
9292

9393
// Type label keys for i18n
@@ -149,6 +149,43 @@ function calculateProgress(tasks: SessionMetadata['tasks']): TaskStatusBreakdown
149149
return { total, completed, failed, pending, inProgress, percentage };
150150
}
151151

152+
/**
153+
* Severity breakdown for review sessions
154+
*/
155+
interface SeverityBreakdown {
156+
total: number;
157+
critical: number;
158+
high: number;
159+
medium: number;
160+
low: number;
161+
}
162+
163+
/**
164+
* Calculate severity breakdown from review dimensions
165+
*/
166+
function calculateSeverityBreakdown(review: SessionMetadata['review']): SeverityBreakdown {
167+
if (!review?.dimensions || review.dimensions.length === 0) {
168+
return { total: 0, critical: 0, high: 0, medium: 0, low: 0 };
169+
}
170+
171+
let critical = 0, high = 0, medium = 0, low = 0;
172+
173+
review.dimensions.forEach(dim => {
174+
if (dim.findings) {
175+
dim.findings.forEach(finding => {
176+
const severity = finding.severity?.toLowerCase();
177+
if (severity === 'critical') critical++;
178+
else if (severity === 'high') high++;
179+
else if (severity === 'medium') medium++;
180+
else if (severity === 'low') low++;
181+
});
182+
}
183+
});
184+
185+
const total = critical + high + medium + low;
186+
return { total, critical, high, medium, low };
187+
}
188+
152189
/**
153190
* SessionCard component for displaying session information
154191
*
@@ -188,6 +225,7 @@ export function SessionCard({
188225
: null;
189226

190227
const progress = calculateProgress(session.tasks);
228+
const severity = calculateSeverityBreakdown(session.review);
191229
const isPlanning = session.status === 'planning';
192230
const isArchived = session.status === 'archived' || session.location === 'archived';
193231

@@ -227,21 +265,24 @@ export function SessionCard({
227265
onClick={handleCardClick}
228266
>
229267
<CardContent className="p-4">
230-
{/* Header - Session ID as title */}
268+
{/* Header - Type badge + Session ID as title */}
231269
<div className="flex items-start justify-between gap-2 mb-2">
232270
<div className="flex-1 min-w-0">
233-
<h3 className="font-bold text-card-foreground text-sm tracking-wide uppercase truncate">
234-
{session.session_id}
235-
</h3>
271+
<div className="flex items-center gap-2 mb-1">
272+
{/* Type badge BEFORE title */}
273+
{typeConfig && typeLabel && (
274+
<Badge variant={typeConfig.variant} className="gap-1 flex-shrink-0">
275+
<typeConfig.icon className="h-3 w-3" />
276+
{typeLabel}
277+
</Badge>
278+
)}
279+
<h3 className="font-bold text-card-foreground text-sm tracking-wide uppercase truncate">
280+
{session.session_id}
281+
</h3>
282+
</div>
236283
</div>
237284
<div className="flex items-center gap-2 flex-shrink-0">
238285
<Badge variant={statusVariant}>{statusLabel}</Badge>
239-
{typeConfig && typeLabel && (
240-
<Badge variant={typeConfig.variant} className="gap-1">
241-
<typeConfig.icon className="h-3 w-3" />
242-
{typeLabel}
243-
</Badge>
244-
)}
245286
{showActions && (
246287
<DropdownMenu>
247288
<DropdownMenuTrigger asChild>
@@ -291,21 +332,46 @@ export function SessionCard({
291332
</p>
292333
)}
293334

294-
{/* Meta info - enriched */}
335+
{/* Meta info - different based on session type */}
295336
<div className="flex flex-wrap items-center gap-x-4 gap-y-2 text-xs text-muted-foreground">
296337
<span className="flex items-center gap-1">
297338
<Calendar className="h-3.5 w-3.5" />
298339
{formatDate(session.created_at)}
299340
</span>
300-
<span className="flex items-center gap-1">
301-
<ListChecks className="h-3.5 w-3.5" />
302-
{progress.total} {formatMessage({ id: 'sessions.card.tasks' })}
303-
</span>
304-
{progress.total > 0 && (
305-
<span className="flex items-center gap-1">
306-
<CheckCircle2 className="h-3.5 w-3.5 text-success" />
307-
{progress.completed} {formatMessage({ id: 'sessions.card.completed' })}
308-
</span>
341+
342+
{/* Review sessions: Show findings and dimensions */}
343+
{session.type === 'review' ? (
344+
<>
345+
{session.review?.dimensions && session.review.dimensions.length > 0 && (
346+
<span className="flex items-center gap-1">
347+
<Search className="h-3.5 w-3.5" />
348+
{session.review.dimensions.length} {formatMessage({ id: 'sessions.card.dimensions' })}
349+
</span>
350+
)}
351+
{session.review?.findings !== undefined && (
352+
<span className="flex items-center gap-1">
353+
<FileText className="h-3.5 w-3.5" />
354+
{typeof session.review.findings === 'number'
355+
? session.review.findings
356+
: session.review.dimensions?.reduce((sum, dim) => sum + (dim.findings?.length || 0), 0) || 0
357+
} {formatMessage({ id: 'sessions.card.findings' })}
358+
</span>
359+
)}
360+
</>
361+
) : (
362+
<>
363+
{/* Workflow/other sessions: Show tasks */}
364+
<span className="flex items-center gap-1">
365+
<ListChecks className="h-3.5 w-3.5" />
366+
{progress.total} {formatMessage({ id: 'sessions.card.tasks' })}
367+
</span>
368+
{progress.total > 0 && (
369+
<span className="flex items-center gap-1">
370+
<CheckCircle2 className="h-3.5 w-3.5 text-success" />
371+
{progress.completed} {formatMessage({ id: 'sessions.card.completed' })}
372+
</span>
373+
)}
374+
</>
309375
)}
310376
{session.updated_at && session.updated_at !== session.created_at && (
311377
<span className="flex items-center gap-1">
@@ -315,15 +381,9 @@ export function SessionCard({
315381
)}
316382
</div>
317383

318-
{/* Task status badges */}
319-
{progress.total > 0 && (
384+
{/* Task status badges - only for non-review sessions */}
385+
{session.type !== 'review' && progress.total > 0 && (
320386
<div className="flex flex-wrap items-center gap-1.5 mt-2">
321-
{progress.pending > 0 && (
322-
<Badge variant="warning" className="gap-1 px-1.5 py-0 text-[10px]">
323-
<Clock className="h-3 w-3" />
324-
{progress.pending} {formatMessage({ id: 'sessions.taskStatus.pending' })}
325-
</Badge>
326-
)}
327387
{progress.inProgress > 0 && (
328388
<Badge variant="info" className="gap-1 px-1.5 py-0 text-[10px]">
329389
<RefreshCw className="h-3 w-3" />
@@ -345,8 +405,38 @@ export function SessionCard({
345405
</div>
346406
)}
347407

348-
{/* Progress bar (only show if not planning and has tasks) */}
349-
{progress.total > 0 && !isPlanning && (
408+
{/* Severity badges - only for review sessions */}
409+
{session.type === 'review' && severity.total > 0 && (
410+
<div className="flex flex-wrap items-center gap-1.5 mt-2">
411+
{severity.critical > 0 && (
412+
<Badge variant="destructive" className="gap-1 px-1.5 py-0 text-[10px]">
413+
<AlertCircle className="h-3 w-3" />
414+
{severity.critical} Critical
415+
</Badge>
416+
)}
417+
{severity.high > 0 && (
418+
<Badge variant="warning" className="gap-1 px-1.5 py-0 text-[10px]">
419+
<AlertCircle className="h-3 w-3" />
420+
{severity.high} High
421+
</Badge>
422+
)}
423+
{severity.medium > 0 && (
424+
<Badge variant="info" className="gap-1 px-1.5 py-0 text-[10px]">
425+
<Search className="h-3 w-3" />
426+
{severity.medium} Medium
427+
</Badge>
428+
)}
429+
{severity.low > 0 && (
430+
<Badge variant="secondary" className="gap-1 px-1.5 py-0 text-[10px]">
431+
<FileText className="h-3 w-3" />
432+
{severity.low} Low
433+
</Badge>
434+
)}
435+
</div>
436+
)}
437+
438+
{/* Progress bar (only show for non-review sessions with tasks) */}
439+
{session.type !== 'review' && progress.total > 0 && !isPlanning && (
350440
<div className="mt-3">
351441
<div className="flex items-center justify-between text-xs mb-1">
352442
<span className="text-muted-foreground">{formatMessage({ id: 'sessions.card.progress' })}</span>

ccw/frontend/src/lib/api.ts

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -248,10 +248,54 @@ function transformBackendSession(
248248
}
249249

250250
// Preserve type field from backend, or infer from session_id pattern
251-
// Multi-level type detection: backend.type > infer from name
252-
const sessionType = (backendSession.type as SessionMetadata['type']) ||
251+
// Multi-level type detection: backend.type > hasReview (for review sessions) > infer from name
252+
let sessionType = (backendSession.type as SessionMetadata['type']) ||
253253
inferTypeFromName(backendSession.session_id);
254254

255+
// Transform backend review data to frontend format
256+
// Backend has: hasReview, reviewSummary, reviewDimensions (separate fields)
257+
// Frontend expects: review object with dimensions, findings count, etc.
258+
const backendData = backendSession as unknown as {
259+
hasReview?: boolean;
260+
reviewSummary?: {
261+
phase?: string;
262+
severityDistribution?: Record<string, number>;
263+
criticalFiles?: string[];
264+
status?: string;
265+
};
266+
reviewDimensions?: Array<{
267+
name: string;
268+
findings?: Array<{ severity?: string }>;
269+
summary?: unknown;
270+
status?: string;
271+
}>;
272+
};
273+
274+
let review: SessionMetadata['review'] | undefined;
275+
if (backendData.hasReview) {
276+
// If session has review data but type is not 'review', auto-fix the type
277+
if (sessionType !== 'review') {
278+
sessionType = 'review';
279+
}
280+
281+
// Build review object from backend data
282+
const dimensions = backendData.reviewDimensions || [];
283+
const totalFindings = dimensions.reduce(
284+
(sum, dim) => sum + (dim.findings?.length || 0), 0
285+
);
286+
287+
review = {
288+
dimensions: dimensions.map(dim => ({
289+
name: dim.name,
290+
findings: dim.findings || []
291+
})),
292+
dimensions_count: dimensions.length,
293+
findings: totalFindings,
294+
iterations: undefined,
295+
fixes: undefined
296+
};
297+
}
298+
255299
return {
256300
session_id: backendSession.session_id,
257301
type: sessionType,
@@ -265,8 +309,8 @@ function transformBackendSession(
265309
// Preserve additional fields if they exist
266310
has_plan: (backendSession as unknown as { has_plan?: boolean }).has_plan,
267311
plan_updated_at: (backendSession as unknown as { plan_updated_at?: string }).plan_updated_at,
268-
has_review: (backendSession as unknown as { has_review?: boolean }).has_review,
269-
review: (backendSession as unknown as { review?: SessionMetadata['review'] }).review,
312+
has_review: backendData.hasReview,
313+
review,
270314
summaries: (backendSession as unknown as { summaries?: SessionMetadata['summaries'] }).summaries,
271315
tasks: (backendSession as unknown as { tasks?: TaskData[] }).tasks,
272316
};
@@ -1904,14 +1948,47 @@ export interface ReviewSession {
19041948

19051949
export interface ReviewSessionsResponse {
19061950
reviewSessions?: ReviewSession[];
1951+
reviewData?: {
1952+
sessions?: Array<{
1953+
session_id: string;
1954+
dimensions: Array<{ name: string; findings?: Array<ReviewFinding> }>;
1955+
findings?: Array<ReviewFinding & { dimension: string }>;
1956+
progress?: unknown;
1957+
}>;
1958+
};
19071959
}
19081960

19091961
/**
19101962
* Fetch all review sessions
19111963
*/
19121964
export async function fetchReviewSessions(): Promise<ReviewSession[]> {
19131965
const data = await fetchApi<ReviewSessionsResponse>('/api/data');
1914-
return data.reviewSessions || [];
1966+
1967+
// If reviewSessions field exists (legacy format), use it
1968+
if (data.reviewSessions && data.reviewSessions.length > 0) {
1969+
return data.reviewSessions;
1970+
}
1971+
1972+
// Otherwise, transform reviewData.sessions into ReviewSession format
1973+
if (data.reviewData?.sessions) {
1974+
return data.reviewData.sessions.map(session => ({
1975+
session_id: session.session_id,
1976+
title: session.session_id,
1977+
description: '',
1978+
type: 'review' as const,
1979+
phase: 'in-progress',
1980+
reviewDimensions: session.dimensions.map(dim => ({
1981+
name: dim.name,
1982+
findings: dim.findings || []
1983+
})),
1984+
_isActive: true,
1985+
created_at: undefined,
1986+
updated_at: undefined,
1987+
status: 'active'
1988+
}));
1989+
}
1990+
1991+
return [];
19151992
}
19161993

19171994
/**

0 commit comments

Comments
 (0)