pappitti commited on
Commit
75959de
·
1 Parent(s): 9c04855
README.md CHANGED
@@ -7,9 +7,6 @@ run the app ;
7
  `npm dev`
8
 
9
  TODO:
10
- - clean unused modules and components
11
- - loading
12
  - DB indexing
13
- - click on waterfall
14
- - move all interfaces and types to types.ts
15
- - test on new device
 
7
  `npm dev`
8
 
9
  TODO:
 
 
10
  - DB indexing
11
+ - test on new device
12
+ - write README
 
package.json CHANGED
@@ -11,24 +11,16 @@
11
  "preview": "vite preview"
12
  },
13
  "dependencies": {
14
- "@emotion/react": "^11.14.0",
15
- "@emotion/styled": "^11.14.1",
16
- "@mui/icons-material": "^7.2.0",
17
- "@mui/material": "^7.2.0",
18
  "duckdb": "^1.3.1",
19
- "plotly.js": "^3.0.1",
20
  "react": "^19.1.0",
21
  "react-dom": "^19.1.0",
22
- "react-markdown": "8.0.6",
23
- "react-plotly.js": "^2.6.0"
24
  },
25
  "devDependencies": {
26
  "@eslint/js": "^9.29.0",
27
  "@types/node": "^24.0.7",
28
- "@types/plotly.js": "^3.0.2",
29
  "@types/react": "^19.1.8",
30
  "@types/react-dom": "^19.1.6",
31
- "@types/react-plotly.js": "^2.6.3",
32
  "@vitejs/plugin-react": "^4.5.2",
33
  "eslint": "^9.29.0",
34
  "eslint-plugin-react-hooks": "^5.2.0",
 
11
  "preview": "vite preview"
12
  },
13
  "dependencies": {
 
 
 
 
14
  "duckdb": "^1.3.1",
 
15
  "react": "^19.1.0",
16
  "react-dom": "^19.1.0",
17
+ "react-markdown": "8.0.6"
 
18
  },
19
  "devDependencies": {
20
  "@eslint/js": "^9.29.0",
21
  "@types/node": "^24.0.7",
 
22
  "@types/react": "^19.1.8",
23
  "@types/react-dom": "^19.1.6",
 
24
  "@vitejs/plugin-react": "^4.5.2",
25
  "eslint": "^9.29.0",
26
  "eslint-plugin-react-hooks": "^5.2.0",
src/App.tsx CHANGED
@@ -1,5 +1,4 @@
1
  import { useState, useEffect } from 'react';
2
- // import { Container, Typography, Box } from '@mui/material';
3
  import Waterfall from './components/Waterfall.js';
4
  import Heatmap from './components/Heatmap.js';
5
  import AssessmentItems from './components/itemList.js';
@@ -14,14 +13,14 @@ function App() {
14
  const [matrix, setMatrix] = useState<TransitionMatrix | null>(null);
15
  const [error, setError] = useState<string | null>(null);
16
  const [isLoading, setIsLoading] = useState(false);
 
17
 
18
  const [selectedTheme, setSelectedTheme] = useState<string>('');
19
  const [selectedJudge1, setSelectedJudge1] = useState<string>('');
20
  const [selectedJudge2, setSelectedJudge2] = useState<string>('');
21
- const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
22
  const [selectedItems, setSelectedItems] = useState<AssessmentItem[]>([]);
23
 
24
-
25
  // Fetch initial data when the component mounts
26
  useEffect(() => {
27
  const loadFilters = async () => {
@@ -73,13 +72,17 @@ function App() {
73
  // Handle cell click to fetch assessment items
74
  const handleCellClick = (fromCategory: string, toCategory: string) => {
75
  if (selectedJudge1 && selectedJudge2 && fromCategory && toCategory) {
 
76
  getAssessmentItems(selectedJudge1, selectedJudge2, fromCategory, toCategory, selectedTheme)
77
  .then(setSelectedItems)
78
  .catch(err => {
79
  setError(err instanceof Error ? err.message : 'An unknown error occurred.');
 
 
 
80
  });
81
 
82
- setSelectedCategory(`${fromCategory} → ${toCategory}`);
83
  }
84
 
85
  return;
@@ -113,21 +116,18 @@ function App() {
113
 
114
  {isLoading && (
115
  <div className="loading-indicator">
116
- <svg className="loading-spinner" viewBox="0 0 50 50">
117
- <circle cx="25" cy="25" r="20" fill="none" stroke="#10b981" strokeWidth="5" />
118
- </svg>
119
  <p>Loading data...</p>
120
  </div>
121
  )}
122
 
123
  {!isLoading && matrix && (
124
-
125
  <div className="charts-container">
126
  <Waterfall
127
  matrix={matrix}
128
  judge1={selectedJudge1}
129
  judge2={selectedJudge2}
130
- // onCellClick={handleCellClick}
131
  />
132
 
133
  <Heatmap
@@ -137,16 +137,21 @@ function App() {
137
  onCellClick={handleCellClick}
138
  />
139
  </div>
140
-
141
- // </div>
142
  )}
143
- {selectedItems.length > 0 && (
 
144
  <AssessmentItems
145
  judge1={selectedJudge1}
146
  judge2={selectedJudge2}
147
  items={selectedItems}
148
  selectedCategory={selectedCategory}
149
  />
 
 
 
 
 
 
150
  )}
151
  </main>
152
  </div>
 
1
  import { useState, useEffect } from 'react';
 
2
  import Waterfall from './components/Waterfall.js';
3
  import Heatmap from './components/Heatmap.js';
4
  import AssessmentItems from './components/itemList.js';
 
13
  const [matrix, setMatrix] = useState<TransitionMatrix | null>(null);
14
  const [error, setError] = useState<string | null>(null);
15
  const [isLoading, setIsLoading] = useState(false);
16
+ const [loadingItems, setLoadingItems] = useState(false);
17
 
18
  const [selectedTheme, setSelectedTheme] = useState<string>('');
19
  const [selectedJudge1, setSelectedJudge1] = useState<string>('');
20
  const [selectedJudge2, setSelectedJudge2] = useState<string>('');
21
+ const [selectedCategory, setSelectedCategory] = useState<string[] | null>(null);
22
  const [selectedItems, setSelectedItems] = useState<AssessmentItem[]>([]);
23
 
 
24
  // Fetch initial data when the component mounts
25
  useEffect(() => {
26
  const loadFilters = async () => {
 
72
  // Handle cell click to fetch assessment items
73
  const handleCellClick = (fromCategory: string, toCategory: string) => {
74
  if (selectedJudge1 && selectedJudge2 && fromCategory && toCategory) {
75
+ setLoadingItems(true);
76
  getAssessmentItems(selectedJudge1, selectedJudge2, fromCategory, toCategory, selectedTheme)
77
  .then(setSelectedItems)
78
  .catch(err => {
79
  setError(err instanceof Error ? err.message : 'An unknown error occurred.');
80
+ })
81
+ .finally(() => {
82
+ setLoadingItems(false);
83
  });
84
 
85
+ setSelectedCategory([fromCategory, toCategory]);
86
  }
87
 
88
  return;
 
116
 
117
  {isLoading && (
118
  <div className="loading-indicator">
119
+ <div className="loading-spinner"></div>
 
 
120
  <p>Loading data...</p>
121
  </div>
122
  )}
123
 
124
  {!isLoading && matrix && (
 
125
  <div className="charts-container">
126
  <Waterfall
127
  matrix={matrix}
128
  judge1={selectedJudge1}
129
  judge2={selectedJudge2}
130
+ onCellClick={handleCellClick}
131
  />
132
 
133
  <Heatmap
 
137
  onCellClick={handleCellClick}
138
  />
139
  </div>
 
 
140
  )}
141
+
142
+ {!loadingItems && selectedItems.length > 0 && (
143
  <AssessmentItems
144
  judge1={selectedJudge1}
145
  judge2={selectedJudge2}
146
  items={selectedItems}
147
  selectedCategory={selectedCategory}
148
  />
149
+ )}
150
+ {loadingItems && (
151
+ <div className="loading-indicator">
152
+ <div className="loading-spinner"></div>
153
+ <p>Loading data...</p>
154
+ </div>
155
  )}
156
  </main>
157
  </div>
src/components/Filterbar.tsx CHANGED
@@ -1,15 +1,4 @@
1
- import type { Theme } from '../types.js';
2
-
3
- interface FilterBarProps {
4
- themes: Theme[];
5
- judges: string[];
6
- selectedTheme: string;
7
- onThemeChange: (value: string) => void;
8
- selectedJudge1: string;
9
- onJudge1Change: (value: string) => void;
10
- selectedJudge2: string;
11
- onJudge2Change: (value: string) => void;
12
- }
13
 
14
  const FilterBar: React.FC<FilterBarProps> = ({
15
  themes,
 
1
+ import type { FilterBarProps } from '../types.js';
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  const FilterBar: React.FC<FilterBarProps> = ({
4
  themes,
src/components/Heatmap.tsx CHANGED
@@ -1,12 +1,7 @@
1
  import { CATEGORIES, COLOR_MAP } from '../utils/chartUtils.js';
2
- import type { TransitionMatrix } from '../types';
 
3
 
4
- interface HeatmapProps {
5
- matrix: TransitionMatrix;
6
- judge1: string;
7
- judge2: string;
8
- onCellClick: (fromCategory: string, toCategory: string) => void;
9
- }
10
 
11
  const Heatmap: React.FC<HeatmapProps> = ({ matrix, judge1, judge2, onCellClick }) => {
12
  const maxValue = Math.max(
 
1
  import { CATEGORIES, COLOR_MAP } from '../utils/chartUtils.js';
2
+ import type { HeatmapProps } from '../types';
3
+
4
 
 
 
 
 
 
 
5
 
6
  const Heatmap: React.FC<HeatmapProps> = ({ matrix, judge1, judge2, onCellClick }) => {
7
  const maxValue = Math.max(
src/components/Waterfall.tsx CHANGED
@@ -1,16 +1,10 @@
1
  import { COLOR_MAP, generateWaterfallData } from '../utils/chartUtils.js';
2
- import type { TransitionMatrix } from '../types';
3
-
4
- interface WaterfallProps {
5
- matrix: TransitionMatrix;
6
- judge1: string;
7
- judge2: string;
8
- }
9
 
10
  // Shorten judge names for display if they are long
11
  const shortenName = (name: string) => name.split('/')[1] || name;
12
 
13
- const Waterfall: React.FC<WaterfallProps> = ({ matrix, judge1, judge2 }) => {
14
  const totalCount = Object.values(matrix).reduce((sum, fromCat) => {
15
  return sum + Object.values(fromCat).reduce((innerSum, count) => innerSum + count, 0);
16
  }, 0);
@@ -30,7 +24,7 @@ const Waterfall: React.FC<WaterfallProps> = ({ matrix, judge1, judge2 }) => {
30
  return (
31
  <div key={stage_name} className="waterfall-bar-container">
32
  {stage.segments.map(segment => {
33
- const { category_label, value } = segment;
34
  const count = value || 0;
35
  const height = (count / totalCount) * 100;
36
  const color = COLOR_MAP[category_label] || 'rgba(0,0,0,0)'; // Default to transparent if not found
@@ -41,8 +35,10 @@ const Waterfall: React.FC<WaterfallProps> = ({ matrix, judge1, judge2 }) => {
41
  style={{
42
  height: `${height}%`,
43
  backgroundColor: color,
 
44
  }}
45
- title={`${category_label} (${count})`}
 
46
  >
47
  {(count > 0 && category_label != 'BASE' && stage_name.includes('→')) && <span className="bar-value">{count}</span>}
48
  </div>
 
1
  import { COLOR_MAP, generateWaterfallData } from '../utils/chartUtils.js';
2
+ import type { WaterfallProps } from '../types';
 
 
 
 
 
 
3
 
4
  // Shorten judge names for display if they are long
5
  const shortenName = (name: string) => name.split('/')[1] || name;
6
 
7
+ const Waterfall: React.FC<WaterfallProps> = ({ matrix, judge1, judge2, onCellClick }) => {
8
  const totalCount = Object.values(matrix).reduce((sum, fromCat) => {
9
  return sum + Object.values(fromCat).reduce((innerSum, count) => innerSum + count, 0);
10
  }, 0);
 
24
  return (
25
  <div key={stage_name} className="waterfall-bar-container">
26
  {stage.segments.map(segment => {
27
+ const { category_label, value, fromCategory } = segment;
28
  const count = value || 0;
29
  const height = (count / totalCount) * 100;
30
  const color = COLOR_MAP[category_label] || 'rgba(0,0,0,0)'; // Default to transparent if not found
 
35
  style={{
36
  height: `${height}%`,
37
  backgroundColor: color,
38
+ cursor: fromCategory ? 'pointer' : 'default',
39
  }}
40
+ title={`${category_label} (${count})`}
41
+ onClick={() => fromCategory && onCellClick(fromCategory, category_label)}
42
  >
43
  {(count > 0 && category_label != 'BASE' && stage_name.includes('→')) && <span className="bar-value">{count}</span>}
44
  </div>
src/components/itemList.tsx CHANGED
@@ -1,12 +1,5 @@
1
  import ReactMarkdown from 'react-markdown';
2
- import type { AssessmentItem } from '../types';
3
-
4
- interface AssessmentItemsProps {
5
- judge1: string;
6
- judge2: string;
7
- items: AssessmentItem[];
8
- selectedCategory: string | null;
9
- }
10
 
11
  const AssessmentItems: React.FC<AssessmentItemsProps> = ({ judge1, judge2, items, selectedCategory }) => {
12
  if (!selectedCategory || items.length === 0) {
@@ -20,7 +13,7 @@ const AssessmentItems: React.FC<AssessmentItemsProps> = ({ judge1, judge2, items
20
 
21
  return (
22
  <div className="assessment-items">
23
- <h3>Assessment Details - {selectedCategory}</h3>
24
  <div className="items-list">
25
  {items.map((item, index) => (
26
  <div key={index} className="assessment-item">
 
1
  import ReactMarkdown from 'react-markdown';
2
+ import type { AssessmentItemsProps } from '../types';
 
 
 
 
 
 
 
3
 
4
  const AssessmentItems: React.FC<AssessmentItemsProps> = ({ judge1, judge2, items, selectedCategory }) => {
5
  if (!selectedCategory || items.length === 0) {
 
13
 
14
  return (
15
  <div className="assessment-items">
16
+ <h3>Assessment Details - {`${selectedCategory[0]} → ${selectedCategory[1]}`}</h3>
17
  <div className="items-list">
18
  {items.map((item, index) => (
19
  <div key={index} className="assessment-item">
src/index.css CHANGED
@@ -603,6 +603,27 @@ body {
603
  margin : 1rem 0;
604
  }
605
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
606
  /* Responsive Design */
607
  @media (max-width: 1200px) {
608
  .charts-container {
@@ -654,8 +675,19 @@ body {
654
  }
655
  }
656
 
657
- .loading {
658
- animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
 
 
 
 
 
 
 
 
 
 
 
659
  }
660
 
661
  /* Smooth transitions */
 
603
  margin : 1rem 0;
604
  }
605
 
606
+ /* Loading Indicator */
607
+ .loading-indicator {
608
+ display: flex;
609
+ flex-direction: column;
610
+ align-items: center;
611
+ justify-content: center;
612
+ height: 100%;
613
+ color: #6b7280;
614
+ gap: 1rem;
615
+ }
616
+
617
+ .loading-spinner {
618
+ width: 40px;
619
+ height: 40px;
620
+ border: 4px solid #e5e7eb;
621
+ border-top: 4px solid #10b981;
622
+ background: transparent;
623
+ border-radius: 50%;
624
+ animation: spin-and-pulse 2s linear infinite;
625
+ }
626
+
627
  /* Responsive Design */
628
  @media (max-width: 1200px) {
629
  .charts-container {
 
675
  }
676
  }
677
 
678
+ @keyframes spin-and-pulse {
679
+ 0%{
680
+ opacity: 1;
681
+ transform: rotate(0deg);
682
+ }
683
+ 50% {
684
+ opacity: 0.5;
685
+ transform: rotate(180deg);
686
+ }
687
+ 100% {
688
+ opacity: 1;
689
+ transform: rotate(360deg);
690
+ }
691
  }
692
 
693
  /* Smooth transitions */
src/types.ts CHANGED
@@ -51,3 +51,50 @@ export interface AssessmentItem {
51
  model: string;
52
  assessments: Record<string,JudgeAssessment>;
53
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  model: string;
52
  assessments: Record<string,JudgeAssessment>;
53
  }
54
+
55
+ export interface FilterBarProps {
56
+ themes: Theme[];
57
+ judges: string[];
58
+ selectedTheme: string;
59
+ onThemeChange: (value: string) => void;
60
+ selectedJudge1: string;
61
+ onJudge1Change: (value: string) => void;
62
+ selectedJudge2: string;
63
+ onJudge2Change: (value: string) => void;
64
+ }
65
+
66
+ export interface AssessmentItemsProps {
67
+ judge1: string;
68
+ judge2: string;
69
+ items: AssessmentItem[];
70
+ selectedCategory: string[] | null;
71
+ }
72
+
73
+ export interface Segment {
74
+ category_label: string;
75
+ value: number;
76
+ fromCategory?: string; // Optional for flow bars
77
+ }
78
+
79
+ export interface PlotStage {
80
+ stage_name: string;
81
+ segments: Segment[];
82
+ }
83
+
84
+ export interface HeatmapProps {
85
+ matrix: TransitionMatrix;
86
+ judge1: string;
87
+ judge2: string;
88
+ onCellClick: (fromCategory: string, toCategory: string) => void;
89
+ }
90
+
91
+ export interface WaterfallProps {
92
+ matrix: TransitionMatrix;
93
+ judge1: string;
94
+ judge2: string;
95
+ onCellClick: (fromCategory: string, toCategory: string) => void;
96
+ }
97
+
98
+ export interface ApiError {
99
+ error: string;
100
+ }
src/utils/apiUtils.ts CHANGED
@@ -1,8 +1,6 @@
1
- import type { Theme, TransitionMatrix, AssessmentItem } from '../types.js';
 
2
 
3
- interface ApiError {
4
- error: string;
5
- }
6
 
7
  // This helper centralizes our fetch logic and error handling.
8
  async function fetchAPI<T>(url: string, options?: RequestInit): Promise<T> {
 
1
+ import type { Theme, TransitionMatrix, AssessmentItem, ApiError } from '../types.js';
2
+
3
 
 
 
 
4
 
5
  // This helper centralizes our fetch logic and error handling.
6
  async function fetchAPI<T>(url: string, options?: RequestInit): Promise<T> {
src/utils/chartUtils.ts CHANGED
@@ -1,5 +1,4 @@
1
- import type { TransitionMatrix } from '../types.js';
2
-
3
 
4
  export const CATEGORIES = ["COMPLETE", "EVASIVE", "DENIAL", "ERROR" /*, "UNKNOWN"*/];
5
  export const COLOR_MAP: Record<string, string> = {
@@ -11,17 +10,6 @@ export const COLOR_MAP: Record<string, string> = {
11
  /*"UNKNOWN": "#6b7280"*/
12
  };
13
 
14
-
15
- interface Segment {
16
- category_label: string;
17
- value: number;
18
- }
19
-
20
- interface PlotStage {
21
- stage_name: string;
22
- segments: Segment[];
23
- }
24
-
25
  export function generateWaterfallData(
26
  matrix: TransitionMatrix,
27
  judge1Name: string,
@@ -74,7 +62,7 @@ export function generateWaterfallData(
74
  stage_name: `${j1Cat} → ${j2Cat}`,
75
  segments: [
76
  { category_label: 'BASE', value: baseValue },
77
- { category_label: j2Cat, value: flowCount },
78
  ],
79
  });
80
  }
 
1
+ import type { TransitionMatrix, PlotStage, Segment } from '../types.js';
 
2
 
3
  export const CATEGORIES = ["COMPLETE", "EVASIVE", "DENIAL", "ERROR" /*, "UNKNOWN"*/];
4
  export const COLOR_MAP: Record<string, string> = {
 
10
  /*"UNKNOWN": "#6b7280"*/
11
  };
12
 
 
 
 
 
 
 
 
 
 
 
 
13
  export function generateWaterfallData(
14
  matrix: TransitionMatrix,
15
  judge1Name: string,
 
62
  stage_name: `${j1Cat} → ${j2Cat}`,
63
  segments: [
64
  { category_label: 'BASE', value: baseValue },
65
+ { category_label: j2Cat, value: flowCount, fromCategory: j1Cat },
66
  ],
67
  });
68
  }