| import { AlertCircle, AlertTriangle, CheckCircle2, Wrench } from "lucide-react"; |
| import type { LintResult, LintViolation } from "@/lib/api"; |
|
|
| interface LintPanelProps { |
| result: LintResult | null; |
| onJumpToLine?: (line: number) => void; |
| } |
|
|
| function SeverityBadge({ warning }: { warning: boolean }) { |
| if (warning) { |
| return ( |
| <span className="inline-flex items-center gap-1 text-[10px] font-medium px-1.5 py-0.5 rounded" |
| style={{ background: "oklch(0.78 0.18 55 / 0.15)", color: "oklch(0.78 0.18 55)", border: "1px solid oklch(0.78 0.18 55 / 0.3)" }}> |
| <AlertTriangle size={9} /> |
| WARN |
| </span> |
| ); |
| } |
| return ( |
| <span className="inline-flex items-center gap-1 text-[10px] font-medium px-1.5 py-0.5 rounded" |
| style={{ background: "oklch(0.60 0.20 25 / 0.15)", color: "oklch(0.65 0.20 25)", border: "1px solid oklch(0.60 0.20 25 / 0.3)" }}> |
| <AlertCircle size={9} /> |
| ERROR |
| </span> |
| ); |
| } |
|
|
| function RuleCodeBadge({ code }: { code: string }) { |
| return ( |
| <span className="text-[10px] font-mono font-medium px-1.5 py-0.5 rounded" |
| style={{ background: "oklch(0.68 0.16 210 / 0.12)", color: "oklch(0.68 0.16 210)", border: "1px solid oklch(0.68 0.16 210 / 0.25)" }}> |
| {code} |
| </span> |
| ); |
| } |
|
|
| function ViolationRow({ v, onJumpToLine }: { v: LintViolation; onJumpToLine?: (line: number) => void }) { |
| return ( |
| <div |
| className="group flex flex-col gap-1.5 px-3 py-2.5 border-b border-[oklch(0.18_0.010_264)] hover:bg-[oklch(0.16_0.010_264)] cursor-pointer transition-colors" |
| onClick={() => onJumpToLine?.(v.line_no)} |
| > |
| <div className="flex items-center gap-2 flex-wrap"> |
| <SeverityBadge warning={v.warning} /> |
| <RuleCodeBadge code={v.code} /> |
| <span className="text-[10px] font-mono text-[oklch(0.45_0.010_264)] ml-auto group-hover:text-[oklch(0.55_0.010_264)] transition-colors"> |
| L{v.line_no}:{v.line_pos} |
| </span> |
| {v.fixable && ( |
| <span className="inline-flex items-center gap-0.5 text-[10px]" |
| style={{ color: "oklch(0.72 0.17 160)" }}> |
| <Wrench size={9} /> |
| fixable |
| </span> |
| )} |
| </div> |
| <p className="text-xs text-[oklch(0.78_0.010_264)] leading-relaxed">{v.description}</p> |
| </div> |
| ); |
| } |
|
|
| export function LintPanel({ result, onJumpToLine }: LintPanelProps) { |
| if (!result) { |
| return ( |
| <div className="flex flex-col items-center justify-center h-full gap-3 text-[oklch(0.45_0.010_264)]"> |
| <AlertCircle size={32} className="opacity-30" /> |
| <p className="text-sm">Run analysis to see lint results</p> |
| </div> |
| ); |
| } |
|
|
| const { violations, passed, stats } = result; |
|
|
| return ( |
| <div className="flex flex-col h-full"> |
| {/* Summary bar */} |
| <div className="flex items-center gap-4 px-3 py-2 border-b border-[oklch(0.22_0.010_264)] flex-shrink-0"> |
| {passed ? ( |
| <div className="flex items-center gap-1.5 text-xs" style={{ color: "oklch(0.72 0.17 160)" }}> |
| <CheckCircle2 size={14} /> |
| <span className="font-medium">All checks passed</span> |
| </div> |
| ) : ( |
| <div className="flex items-center gap-1.5 text-xs" style={{ color: "oklch(0.65 0.20 25)" }}> |
| <AlertCircle size={14} /> |
| <span className="font-medium">{stats.total} violation{stats.total !== 1 ? "s" : ""}</span> |
| </div> |
| )} |
| <div className="flex items-center gap-3 ml-auto text-[10px] text-[oklch(0.45_0.010_264)]"> |
| {stats.errors > 0 && ( |
| <span style={{ color: "oklch(0.65 0.20 25)" }}>{stats.errors} error{stats.errors !== 1 ? "s" : ""}</span> |
| )} |
| {stats.warnings > 0 && ( |
| <span style={{ color: "oklch(0.78 0.18 55)" }}>{stats.warnings} warning{stats.warnings !== 1 ? "s" : ""}</span> |
| )} |
| {stats.fixable > 0 && ( |
| <span style={{ color: "oklch(0.72 0.17 160)" }}>{stats.fixable} fixable</span> |
| )} |
| </div> |
| </div> |
| |
| {/* Violations list */} |
| {passed ? ( |
| <div className="flex flex-col items-center justify-center h-full gap-3"> |
| <CheckCircle2 size={40} style={{ color: "oklch(0.72 0.17 160)" }} className="opacity-60" /> |
| <p className="text-sm font-medium" style={{ color: "oklch(0.72 0.17 160)" }}>No violations found</p> |
| <p className="text-xs text-[oklch(0.45_0.010_264)]">Your SQL is clean for dialect: {result.dialect}</p> |
| </div> |
| ) : ( |
| <div className="flex-1 overflow-auto"> |
| {violations.map((v, i) => ( |
| <ViolationRow key={`${v.code}-${v.line_no}-${v.line_pos}-${i}`} v={v} onJumpToLine={onJumpToLine} /> |
| ))} |
| </div> |
| )} |
| </div> |
| ); |
| } |
|
|