ewdlop's picture
App.tsx
42ae0b9
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>
);
}