feat(analyzer): browse-tickets page + analysis-view typography
- /analyzer/tickets — period chips (today/yesterday/this+last week/30d/60d/all), client + issue-type Selects, debounced search, per-row Analyze/Re-analyze plus View shortcut when an analysis already exists. - API: /api/analyzer/tickets/list (period/companyId/issueType/search, paginated via COUNT(*) OVER) and /filter-options (companies that actually have tickets, active issue types). - ProseText helper in analysis-view splits on blank lines and renders each chunk with leading-7 — Summary, Next Step, rationale, and Post-Resolution now have proper paragraph rhythm. Next Step card re-styled with bg-primary/5 tint, ArrowRight icon, and an indented rationale block. - Top-level "Analyzer" nav menu (Browse Tickets + Needs Review). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
966376e6b6
commit
b20c94ea1a
6 changed files with 879 additions and 19 deletions
|
|
@ -11,7 +11,13 @@ import {
|
|||
CollapsibleTrigger,
|
||||
} from '@/components/ui/collapsible';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { ChevronRight, ChevronDown, ExternalLink, AlertTriangle } from 'lucide-react';
|
||||
import {
|
||||
ChevronRight,
|
||||
ChevronDown,
|
||||
ExternalLink,
|
||||
AlertTriangle,
|
||||
ArrowRight,
|
||||
} from 'lucide-react';
|
||||
import { ShareModal } from './share-modal';
|
||||
import { AnalyzeButton } from './analyze-button';
|
||||
import type { PersistedAnalysis, Visibility } from '@/lib/types/analyzer';
|
||||
|
|
@ -38,6 +44,29 @@ const SEVERITY_TONE: Record<string, string> = {
|
|||
low: 'border-blue-500 bg-blue-500/5',
|
||||
};
|
||||
|
||||
function ProseText({
|
||||
text,
|
||||
className = '',
|
||||
}: {
|
||||
text: string;
|
||||
className?: string;
|
||||
}) {
|
||||
const paragraphs = text
|
||||
.split(/\n\s*\n/)
|
||||
.map((p) => p.trim())
|
||||
.filter(Boolean);
|
||||
if (paragraphs.length === 0) return null;
|
||||
return (
|
||||
<div className={`space-y-3 ${className}`}>
|
||||
{paragraphs.map((p, i) => (
|
||||
<p key={i} className="leading-7 whitespace-pre-line">
|
||||
{p}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ConfidenceBadge({ score }: { score: number | null }) {
|
||||
if (score === null) return null;
|
||||
const pct = Math.round(score * 100);
|
||||
|
|
@ -135,42 +164,51 @@ export function AnalysisView({ analysis: a }: AnalysisViewProps) {
|
|||
{a.summary && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">Summary</CardTitle>
|
||||
<CardTitle className="text-base text-muted-foreground uppercase tracking-wide font-semibold">
|
||||
Summary
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm leading-relaxed whitespace-pre-wrap">
|
||||
{a.summary}
|
||||
</p>
|
||||
<ProseText text={a.summary} className="text-base text-foreground" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 3. Next step */}
|
||||
{a.nextStep && (
|
||||
<Card className="border-primary/40">
|
||||
<Card className="border-primary/40 bg-primary/5">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
Next step
|
||||
<CardTitle className="text-base text-primary uppercase tracking-wide font-semibold flex items-center gap-2">
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
Recommended Next Step
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm font-medium">{a.nextStep}</p>
|
||||
<ProseText
|
||||
text={a.nextStep}
|
||||
className="text-base font-medium text-foreground"
|
||||
/>
|
||||
{a.nextStepRationale && (
|
||||
<Collapsible open={nextStepOpen} onOpenChange={setNextStepOpen} className="mt-3">
|
||||
<Collapsible
|
||||
open={nextStepOpen}
|
||||
onOpenChange={setNextStepOpen}
|
||||
className="mt-4 pt-4 border-t border-primary/20"
|
||||
>
|
||||
<CollapsibleTrigger asChild>
|
||||
<Button variant="ghost" size="sm" className="-ml-3">
|
||||
<Button variant="ghost" size="sm" className="-ml-2 h-8">
|
||||
{nextStepOpen ? (
|
||||
<ChevronDown className="w-4 h-4 mr-1" />
|
||||
) : (
|
||||
<ChevronRight className="w-4 h-4 mr-1" />
|
||||
)}
|
||||
Rationale
|
||||
{nextStepOpen ? 'Hide rationale' : 'Show rationale'}
|
||||
</Button>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<p className="text-sm text-muted-foreground mt-2">
|
||||
{a.nextStepRationale}
|
||||
</p>
|
||||
<ProseText
|
||||
text={a.nextStepRationale}
|
||||
className="text-sm text-muted-foreground mt-3 pl-4 border-l-2 border-primary/30"
|
||||
/>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
)}
|
||||
|
|
@ -308,12 +346,15 @@ export function AnalysisView({ analysis: a }: AnalysisViewProps) {
|
|||
{a.postResolutionAnalysis && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">Post-resolution analysis</CardTitle>
|
||||
<CardTitle className="text-base text-muted-foreground uppercase tracking-wide font-semibold">
|
||||
Post-Resolution Analysis
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm whitespace-pre-wrap">
|
||||
{a.postResolutionAnalysis}
|
||||
</p>
|
||||
<ProseText
|
||||
text={a.postResolutionAnalysis}
|
||||
className="text-base text-foreground"
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue