Skip to content

Commit 9de7603

Browse files
committed
UI/UX and validation logic fixes: improved row reporting, highlighting, and results header status indicator
1 parent 5b3868d commit 9de7603

File tree

8 files changed

+1442
-517
lines changed

8 files changed

+1442
-517
lines changed

.babelrc.bckp

Lines changed: 0 additions & 7 deletions
This file was deleted.

components/code-editor.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,10 @@ export default function CodeEditor({
179179
}
180180
/* Style for highlighted line */
181181
.editor-highlight-line {
182-
background-color: rgba(56, 189, 248, 0.5) !important;
183-
border-left: 4px solid #38bdf8 !important;
182+
background-color: rgba(56, 189, 248, 0.15) !important; // much less opaque
183+
border-left: 2px solid #38bdf8 !important;
184184
z-index: 10;
185+
pointer-events: none; // allow editing even when highlighted
185186
}
186187
`}</style>
187188
<Editor

components/csv-validator.tsx

Lines changed: 241 additions & 388 deletions
Large diffs are not rendered by default.

components/validation-results.tsx

Lines changed: 94 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
"use client"
22

3-
import { AlertTriangle, CheckCircle, Info, AlertCircle as AlertIcon } from "lucide-react"
3+
import React, { memo, useCallback } from 'react';
4+
import { AlertTriangle, CheckCircle, Info, AlertCircle as AlertIcon, XCircle, ChevronDown } from "lucide-react"
45
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
56
import { Badge } from "@/components/ui/badge"
67
import { useState } from "react"
78
import { ErrorObject } from "ajv"
89
import * as jsonc from 'jsonc-parser';
10+
import { cn } from "@/lib/utils";
911

1012
// Add _range to ErrorObject type
1113
interface ErrorObjectWithRange extends ErrorObject {
@@ -59,95 +61,100 @@ const SchemaErrorItem: React.FC<SchemaErrorItemProps> = ({ error, index }) => {
5961
// --- ValidationResults Component (Updated) ---
6062
interface ValidationResultsProps {
6163
results: {
62-
schemaIsValid: boolean;
63-
schemaErrors?: ErrorObjectWithRange[]; // Use extended type
64-
parseError?: string | null;
65-
};
66-
schemaData?: string; // schemaData no longer strictly needed by SchemaErrorItem
64+
row: number;
65+
errors: { property?: string; message: string }[];
66+
warnings: { property?: string; message: string }[];
67+
}[];
68+
openAccordionValue: string | undefined;
69+
setOpenAccordionValue: (value: string | undefined) => void;
70+
setHighlightedCsvLine: (line: number | undefined) => void;
71+
setScrollToLine: (line: number | undefined) => void;
6772
}
6873

69-
export default function ValidationResults({ results }: ValidationResultsProps) { // Remove schemaData prop if unused
70-
const { schemaIsValid, schemaErrors = [], parseError } = results;
71-
72-
// Create a unified error array
73-
let displayableErrors: { message: string; instancePath?: string; isParseError?: boolean; rawError?: ErrorObjectWithRange }[] = []; // Use extended type
74-
75-
if (parseError) {
76-
displayableErrors.push({ message: parseError, isParseError: true });
77-
} else {
78-
displayableErrors = schemaErrors.map(err => ({
79-
message: err.message || 'Unknown error',
80-
instancePath: err.instancePath || err.schemaPath || '',
81-
isParseError: false,
82-
rawError: err // Pass the raw error object for SchemaErrorItem
83-
}));
84-
}
85-
86-
const totalErrors = displayableErrors.length;
87-
const overallValid = !parseError && schemaIsValid;
88-
89-
// No need to render anything if valid and no errors
90-
// if (overallValid && totalErrors === 0) {
91-
// return (
92-
// <div className="flex items-center p-3 rounded-md bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-200">
93-
// <CheckCircle className="h-5 w-5 mr-2" />
94-
// <span className="font-medium">Schema is VALID according to the selected official draft</span>
95-
// </div>
96-
// );
97-
// }
74+
const ValidationResults = memo(function ValidationResults({
75+
results,
76+
openAccordionValue,
77+
setOpenAccordionValue,
78+
setHighlightedCsvLine,
79+
setScrollToLine,
80+
}: ValidationResultsProps) {
81+
const renderRow = useCallback((result, idx) => {
82+
const rowSeverity = result.errors.length > 0 ? 'error' : 'warning';
83+
const displayRowNumber = result.row;
84+
return (
85+
<Accordion
86+
key={result.row}
87+
type="single"
88+
collapsible
89+
className="w-full border-b border-muted/20 px-4"
90+
value={openAccordionValue}
91+
onValueChange={(val) => {
92+
setOpenAccordionValue(val);
93+
if (val && val.startsWith('item-')) {
94+
const rowIdx = parseInt(val.replace('item-', ''), 10);
95+
// Highlight the same row as reported (no +2 offset)
96+
setHighlightedCsvLine(rowIdx);
97+
setScrollToLine(rowIdx);
98+
} else {
99+
setHighlightedCsvLine(undefined);
100+
setScrollToLine(undefined);
101+
}
102+
}}
103+
>
104+
<AccordionItem
105+
value={`item-${result.row}`}
106+
className="border-b-0"
107+
>
108+
<AccordionTrigger className={cn(
109+
"text-sm text-left hover:no-underline py-2 group flex items-center",
110+
rowSeverity === 'error'
111+
? 'data-[state=open]:text-red-700 dark:data-[state=open]:text-red-300'
112+
: 'data-[state=open]:text-yellow-700 dark:data-[state=open]:text-yellow-300'
113+
)}>
114+
<div className="flex items-center space-x-2 flex-grow truncate">
115+
{rowSeverity === 'error' ?
116+
<XCircle className="h-4 w-4 text-red-500 flex-shrink-0" /> :
117+
<AlertTriangle className="h-4 w-4 text-yellow-500 flex-shrink-0" />}
118+
<span className="font-semibold">Row {displayRowNumber}:</span>
119+
<span className="truncate flex-grow text-muted-foreground">
120+
{result.errors[0]?.message || result.warnings[0]?.message || 'Unknown issue'}
121+
{(result.errors.length + result.warnings.length) > 1 ? ` (+${result.errors.length + result.warnings.length - 1} more)` : ''}
122+
</span>
123+
</div>
124+
<ChevronDown className={cn(
125+
"h-4 w-4 ml-2 transition-transform duration-200",
126+
openAccordionValue === `item-${result.row}` ? 'rotate-180' : ''
127+
)} />
128+
</AccordionTrigger>
129+
<AccordionContent className="text-xs px-4 pt-2 pb-3 space-y-1 bg-muted/30 rounded-b">
130+
{result.errors.map((err, index) => (
131+
<div key={`err-${index}`} className="flex items-start text-red-600 dark:text-red-400">
132+
<XCircle className="h-3 w-3 mr-1.5 mt-0.5 flex-shrink-0" />
133+
<div>
134+
<span className="font-semibold">Error:</span> <span className="font-medium">{err.property || 'N/A'}</span> - {err.message}
135+
</div>
136+
</div>
137+
))}
138+
{result.warnings.map((warn, index) => (
139+
<div key={`warn-${index}`} className="flex items-start text-yellow-600 dark:text-yellow-400">
140+
<AlertTriangle className="h-3 w-3 mr-1.5 mt-0.5 flex-shrink-0" />
141+
<div>
142+
<span className="font-semibold">Warning:</span> <span className="font-medium">{warn.property || 'N/A'}</span> - {warn.message}
143+
</div>
144+
</div>
145+
))}
146+
</AccordionContent>
147+
</AccordionItem>
148+
</Accordion>
149+
);
150+
}, [openAccordionValue, setOpenAccordionValue, setHighlightedCsvLine, setScrollToLine]);
98151

99152
return (
100-
<div className="space-y-4">
101-
{/* Always show Overall Status */}
102-
<div className={`flex items-center p-3 rounded-md ${overallValid ? 'bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-200' : 'bg-red-100 dark:bg-red-900/30 text-red-800 dark:text-red-200'}`}>
103-
{overallValid ? <CheckCircle className="h-5 w-5 mr-2" /> : <AlertTriangle className="h-5 w-5 mr-2" />}
104-
<span className="font-medium">{
105-
parseError
106-
? 'Schema has Syntax Errors'
107-
: (schemaIsValid ? 'Schema is VALID according to the selected official draft' : 'Schema is INVALID according to the selected official draft')
108-
}</span>
109-
</div>
110-
111-
{/* Always render Accordion if there are *any* errors (parse or schema) */}
112-
{totalErrors > 0 && (
113-
<Accordion type="single" collapsible defaultValue="schema-errors" className="w-full">
114-
<AccordionItem value="schema-errors">
115-
<AccordionTrigger className="text-base font-medium px-3 py-2 hover:bg-muted/50 dark:hover:bg-zinc-700/30 rounded-md">
116-
<div className="flex items-center justify-between w-full">
117-
{/* Adjust title slightly based on error type? Or keep generic? */}
118-
<span className="flex items-center">
119-
<AlertIcon className={`h-4 w-4 mr-2 ${parseError ? 'text-red-500' : 'text-orange-500'}`} />
120-
{parseError ? 'Syntax Errors' : 'Schema Validation Errors'}
121-
</span>
122-
<Badge variant="destructive" className="ml-auto">{totalErrors}</Badge>
123-
</div>
124-
</AccordionTrigger>
125-
<AccordionContent className="pt-2 px-1">
126-
<div className="space-y-2">
127-
{/* Map over unified displayableErrors */}
128-
{displayableErrors.map((error, index) => {
129-
// Render slightly differently for parse error vs schema error
130-
if (error.isParseError) {
131-
return (
132-
<div key={`parse-${index}`} className="py-2 px-3 border-l-4 border-red-500 dark:border-red-400 bg-red-50 dark:bg-red-900/20 rounded-r-md text-sm">
133-
<pre className="whitespace-pre-wrap break-words font-sans font-semibold">{error.message}</pre>
134-
</div>
135-
);
136-
} else if (error.rawError) {
137-
return (
138-
// Pass only error and index, schemaData is removed
139-
<SchemaErrorItem key={`schema-${index}`} error={error.rawError} index={index} />
140-
);
141-
} else {
142-
return null; // Should not happen
143-
}
144-
})}
145-
</div>
146-
</AccordionContent>
147-
</AccordionItem>
148-
</Accordion>
149-
)}
150-
</div>
153+
<>
154+
{results.map(renderRow)}
155+
</>
151156
);
152-
}
157+
});
158+
159+
export default ValidationResults;
153160

jest.config.cjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
module.exports = {
2+
preset: 'ts-jest',
23
testEnvironment: 'jsdom',
4+
transform: {
5+
'^.+\\.[jt]sx?$': 'ts-jest',
6+
},
37
moduleNameMapper: {
48
'^@/(.*)$': '<rootDir>/$1',
59
},

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"@testing-library/jest-dom": "^6.6.3",
6464
"@testing-library/react": "^16.3.0",
6565
"@testing-library/user-event": "^14.6.1",
66+
"@types/jest": "^29.5.14",
6667
"@types/node": "^22.14.0",
6768
"@types/papaparse": "^5.3.15",
6869
"@types/react": "^19",
@@ -82,6 +83,7 @@
8283
"prettier": "^3.5.3",
8384
"tailwindcss": "^3.4.17",
8485
"tailwindcss-animate": "^1.0.7",
86+
"ts-jest": "^29.3.2",
8587
"typescript": "^5"
8688
},
8789
"pnpm": {

0 commit comments

Comments
 (0)