Spaces:
Sleeping
Sleeping
cleaning
Browse files- README.md +2 -5
- package.json +1 -9
- src/App.tsx +17 -12
- src/components/Filterbar.tsx +1 -12
- src/components/Heatmap.tsx +2 -7
- src/components/Waterfall.tsx +6 -10
- src/components/itemList.tsx +2 -9
- src/index.css +34 -2
- src/types.ts +47 -0
- src/utils/apiUtils.ts +2 -4
- src/utils/chartUtils.ts +2 -14
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 |
-
-
|
| 14 |
-
-
|
| 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(
|
| 83 |
}
|
| 84 |
|
| 85 |
return;
|
|
@@ -113,21 +116,18 @@ function App() {
|
|
| 113 |
|
| 114 |
{isLoading && (
|
| 115 |
<div className="loading-indicator">
|
| 116 |
-
<
|
| 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 |
-
|
| 131 |
/>
|
| 132 |
|
| 133 |
<Heatmap
|
|
@@ -137,16 +137,21 @@ function App() {
|
|
| 137 |
onCellClick={handleCellClick}
|
| 138 |
/>
|
| 139 |
</div>
|
| 140 |
-
|
| 141 |
-
// </div>
|
| 142 |
)}
|
| 143 |
-
|
|
|
|
| 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 {
|
| 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 {
|
|
|
|
| 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 {
|
| 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 {
|
| 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 |
-
|
| 658 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
}
|