Skip to content

Commit 424cf57

Browse files
committed
feat: add JWT authentication and projects API endpoints; update package.json and refactor Overview component
1 parent c8af336 commit 424cf57

File tree

10 files changed

+651
-1433
lines changed

10 files changed

+651
-1433
lines changed

client/package-lock.json

Lines changed: 43 additions & 1418 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"react-day-picker": "^8.10.1",
6868
"react-dom": "^18.2.0",
6969
"react-hook-form": "^7.54.1",
70+
"react-papaparse": "^4.4.0",
7071
"react-resizable-panels": "^2.1.7",
7172
"react-router-dom": "^6.23.0",
7273
"reactflow": "^11.10.3",

client/src/App.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import Table from './components/View/TableView/Table'
33
import Home from './pages/Home/Home'
44
import ProjectView from './pages/ProjectView/ProjectView'
55
import Project from './components/View/DataGrid/Project'
6-
import MainPage from './components/View/DataGrid/MainPage'
76
import Overview from './pages/Dashboard/Overview'
87

98
function App() {
@@ -15,14 +14,7 @@ function App() {
1514
<Route path='/table' element={<Table />} />
1615
<Route path='/projects/:project_id' element={<ProjectView />} />
1716
<Route path='/dashboard' element={<Overview />} />
18-
<Route
19-
path='/grid/:project_id'
20-
element={
21-
<MainPage breadcrumbs={[]} sidebarOpen={false}>
22-
<Project />
23-
</MainPage>
24-
}
25-
/>
17+
<Route path='/grid/:project_id' element={<Project />} />
2618
</Routes>
2719
</>
2820
)

client/src/components/View/DataGrid/Project.tsx

Lines changed: 176 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,32 @@ import Contenteditable from '../../../pages/ProjectView/Contenteditable'
77
import { debounce } from 'lodash'
88
import api from '../../../service/api'
99
import { toast } from 'sonner'
10+
import { Button } from '@/components/ui/button'
11+
import {
12+
Dialog,
13+
DialogContent,
14+
DialogDescription,
15+
DialogFooter,
16+
DialogHeader,
17+
DialogTitle,
18+
DialogTrigger,
19+
} from '@/components/ui/dialog'
20+
import { FolderDown } from 'lucide-react'
21+
import { MultiSelect } from '@/components/ui/multi-select'
22+
import {
23+
Form,
24+
FormControl,
25+
FormDescription,
26+
FormField,
27+
FormItem,
28+
FormLabel,
29+
FormMessage,
30+
} from '@/components/ui/form'
31+
import { useForm } from 'react-hook-form'
32+
import { Separator } from '@/components/ui/separator'
33+
import { Input } from '@/components/ui/input'
34+
import { usePapaParse } from 'react-papaparse'
35+
import { flattenObject, nestFlatKeys } from '../TableView/hooks/data-handler'
1036

1137
type ProjectDetails = {
1238
name: string
@@ -90,7 +116,6 @@ const Project: React.FC = () => {
90116
},
91117
})
92118
const response_data = response.data
93-
console.log('projectResults:', response_data.results)
94119
setProjectResults(response_data.results)
95120
} catch (error) {
96121
console.error('Error:', error)
@@ -187,6 +212,85 @@ const Project: React.FC = () => {
187212
}
188213
}
189214

215+
const form = useForm()
216+
const [selectableHeaders, setSelectableHeaders] = useState<string[]>([])
217+
218+
useEffect(() => {
219+
const allKeys = new Set<string>()
220+
flattenObject(projectResults).forEach((obj) => {
221+
Object.keys(obj).forEach((key) => allKeys.add(key))
222+
})
223+
setSelectableHeaders(Array.from(allKeys))
224+
}, [projectResults])
225+
226+
const { jsonToCSV, readString } = usePapaParse()
227+
228+
const handleJsonToCSV = () => {
229+
const selectedColumns = form.watch('frameworks') || []
230+
const data = flattenObject(projectResults)
231+
if (!data.length) return
232+
233+
const finalCols = selectableHeaders.reduce((acc, col) => {
234+
acc.push(col)
235+
if (selectedColumns.includes(col)) acc.push(`${col}_truth`)
236+
return acc
237+
}, [] as string[])
238+
239+
const finalData = data.map((row) =>
240+
finalCols.reduce(
241+
(acc, col) => {
242+
acc[col] = row[col] ?? (col.endsWith('_truth') ? 'Fill in ground truth' : 'NaN')
243+
return acc
244+
},
245+
{} as Record<string, unknown>,
246+
),
247+
)
248+
249+
const csvString = jsonToCSV(finalData)
250+
const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8;' })
251+
const url = URL.createObjectURL(blob)
252+
const link = document.createElement('a')
253+
link.href = url
254+
link.setAttribute('download', 'truth_template.csv')
255+
document.body.appendChild(link)
256+
link.click()
257+
document.body.removeChild(link)
258+
}
259+
260+
// State and handlers for reading user’s CSV
261+
const [truthFile, setTruthFile] = useState<File | null>(null)
262+
263+
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
264+
if (!e.target.files?.length) {
265+
setTruthFile(null)
266+
return
267+
}
268+
setTruthFile(e.target.files[0])
269+
}
270+
271+
const handleLoadCSV = () => {
272+
if (!truthFile) return
273+
274+
// Read file contents
275+
const reader = new FileReader()
276+
reader.onload = (e) => {
277+
if (!e.target?.result) return
278+
279+
const csvString = e.target.result as string
280+
readString(csvString, {
281+
header: true,
282+
complete: (results) => {
283+
results.data.forEach((value: unknown) => {
284+
const row = value as Record<string, unknown>
285+
const nestedRow = nestFlatKeys(row)
286+
console.log(nestedRow)
287+
})
288+
},
289+
})
290+
}
291+
reader.readAsText(truthFile)
292+
}
293+
190294
return (
191295
<main className={`${loading ? 'blur-sm' : ''}`}>
192296
<div className='navbar bg-base-100 flex flex-col sm:flex-row'>
@@ -205,7 +309,77 @@ const Project: React.FC = () => {
205309
<div className='navbar-center text-center'>
206310
<div className='flex flex-row justify-center '>{project.id}</div>
207311
</div>
208-
<div className='md:navbar-end z-10 max-sm:pt-4'></div>
312+
<div className='md:navbar-end z-10 max-md:pt-4'>
313+
<Dialog>
314+
<DialogTrigger asChild>
315+
<Button>
316+
<FolderDown />
317+
&nbsp;Load truth
318+
</Button>
319+
</DialogTrigger>
320+
<DialogContent className='sm:max-w-[600px]'>
321+
<DialogHeader>
322+
<DialogTitle>Create truth input</DialogTitle>
323+
<DialogDescription>
324+
Select the columns you want to input a ground truth for and then export the CSV
325+
file. After editing, upload your modified ground truth to obtain an accuracy score
326+
on your features.
327+
</DialogDescription>
328+
</DialogHeader>
329+
330+
<Form {...form}>
331+
<form onSubmit={form.handleSubmit(() => {})} className='space-y-8'>
332+
<FormField
333+
control={form.control}
334+
name='frameworks'
335+
render={({ field }) => (
336+
<FormItem>
337+
<FormLabel>Columns</FormLabel>
338+
<FormControl>
339+
<MultiSelect
340+
options={selectableHeaders.map((feature) => ({
341+
label: feature.replace(/\s/g, ' → ').toLowerCase(),
342+
value: feature,
343+
}))}
344+
onValueChange={field.onChange}
345+
defaultValue={field.value}
346+
placeholder='Select columns'
347+
variant='inverted'
348+
maxCount={3}
349+
/>
350+
</FormControl>
351+
<FormDescription>
352+
Choose the columns/features you are interested in.
353+
</FormDescription>
354+
<FormMessage />
355+
</FormItem>
356+
)}
357+
/>
358+
<DialogFooter>
359+
<Button type='submit' onClick={handleJsonToCSV}>
360+
Export CSV Base Template
361+
</Button>
362+
</DialogFooter>
363+
</form>
364+
</Form>
365+
366+
<Separator />
367+
368+
<div className='space-y-2'>
369+
<div className='font-semibold'>Import Ground Truth</div>
370+
<p className='text-sm text-muted-foreground'>
371+
Upload your modified ground truth CSV to score your features.
372+
</p>
373+
<Input type='file' accept='.csv' onChange={handleFileChange} />
374+
<DialogFooter>
375+
<Button type='submit' onClick={handleLoadCSV}>
376+
Load CSV Ground Truth
377+
</Button>
378+
</DialogFooter>
379+
</div>
380+
</DialogContent>
381+
</Dialog>
382+
</div>
209383
</div>
210384
<hr></hr>
211385

client/src/components/View/TableView/hooks/data-handler.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,3 +460,29 @@ export function flattenObject(
460460

461461
return [obj]
462462
}
463+
464+
export function nestFlatKeys(flatObj: Record<string, unknown>) {
465+
const nested: Record<string, unknown> = {}
466+
467+
for (const [key, val] of Object.entries(flatObj)) {
468+
const segments = key.split(' ')
469+
let curr = nested
470+
471+
for (let i = 0; i < segments.length; i++) {
472+
const segment = segments[i].endsWith('_truth')
473+
? segments[i].slice(0, -6) // remove "_truth"
474+
: segments[i]
475+
476+
if (i === segments.length - 1) {
477+
curr[segment] = val
478+
} else {
479+
if (curr[segment] == null) {
480+
curr[segment] = {}
481+
}
482+
curr = curr[segment] as Record<string, unknown>
483+
}
484+
}
485+
}
486+
487+
return nested
488+
}

0 commit comments

Comments
 (0)