File size: 6,555 Bytes
4b1a31e 57e048e 4b1a31e 57e048e 4b1a31e 57e048e 4b1a31e 57e048e 4b1a31e 57e048e 4b1a31e 57e048e 4b1a31e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | import React, { useState } from 'react';
import { Innovation, Classification, ResultResponse } from '../types';
import { ChevronDown, ChevronUp, Trash2, Check, Zap, FileText } from 'lucide-react';
import { COLOR_MAP } from '../constants';
import { format } from 'path';
interface InnovationCardProps {
innovation: ResultResponse;
onClassify: (id: string, classification: Classification) => void;
}
const InnovationCard: React.FC<InnovationCardProps> = ({ innovation, onClassify }) => {
const [isExpanded, setIsExpanded] = useState(false);
const getBorderColor = (cls: Classification) => {
return cls === Classification.UNCLASSIFIED ? 'border-l-4 border-l-slate-300' : `border-l-4 border-l-[${COLOR_MAP[cls]}]`;
};
// Inline style for border color since Tailwind arbitrary values in template literals can be tricky
const borderStyle = { borderLeftColor: COLOR_MAP[innovation.classification] };
// Extraction Logic
console.log(innovation)
const contextText = innovation.context;
const problemText = innovation.problem;
const methodologyText = formatToHTML(innovation.methodology)
return (
<div
className={`bg-white rounded-lg shadow-sm border border-slate-200 mb-4 overflow-hidden transition-all duration-200 ${innovation.classification === Classification.DELETE ? 'opacity-50' : ''}`}
style={borderStyle}
>
<div className="p-4">
<div className="flex justify-between items-start">
<div className="flex-1 cursor-pointer" onClick={() => setIsExpanded(!isExpanded)}>
<div className="px-4 pb-4 bg-slate-50 border-t border-slate-100 pt-3 text-sm">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<p className="font-semibold text-slate-700 mb-1">Context</p>
<p className="text-slate-600 text-xs">{contextText}</p>
</div>
<div>
<p className="font-semibold text-slate-700 mb-1">Problem Description</p>
<p className="text-slate-600 text-xs">{problemText}</p>
</div>
</div>
</div>
</div>
<button
onClick={() => setIsExpanded(!isExpanded)}
className="ml-4 text-slate-400 hover:text-slate-600 p-1"
>
{isExpanded ? <ChevronUp size={20} /> : <ChevronDown size={20} />}
</button>
</div>
{/* Quick Actions (Always visible) */}
<div className="mt-4 flex gap-2 border-t border-slate-100 pt-3">
<button
onClick={() => onClassify(innovation.id.toString(), Classification.DELETE)}
className={`flex-1 flex items-center justify-center px-3 py-1.5 text-xs font-medium rounded transition-colors ${innovation.classification === Classification.DELETE ? 'bg-slate-800 text-white' : 'bg-slate-50 text-slate-600 hover:bg-slate-100'}`}
>
<Trash2 className="w-3 h-3 mr-1.5" /> Delete
</button>
<button
onClick={() => onClassify(innovation.id.toString(), Classification.LOW)}
className={`flex-1 flex items-center justify-center px-3 py-1.5 text-xs font-medium rounded transition-colors ${innovation.classification === Classification.LOW ? 'bg-blue-600 text-white' : 'bg-blue-50 text-blue-700 hover:bg-blue-100'}`}
>
Low
</button>
<button
onClick={() => onClassify(innovation.id.toString(), Classification.MEDIUM)}
className={`flex-1 flex items-center justify-center px-3 py-1.5 text-xs font-medium rounded transition-colors ${innovation.classification === Classification.MEDIUM ? 'bg-yellow-500 text-white' : 'bg-yellow-50 text-yellow-700 hover:bg-yellow-100'}`}
>
Medium
</button>
<button
onClick={() => onClassify(innovation.id.toString(), Classification.HIGH)}
className={`flex-1 flex items-center justify-center px-3 py-1.5 text-xs font-medium rounded transition-colors ${innovation.classification === Classification.HIGH ? 'bg-green-600 text-white' : 'bg-green-50 text-green-700 hover:bg-green-100'}`}
>
<Zap className="w-3 h-3 mr-1.5" /> High
</button>
</div>
</div>
{isExpanded && (
<div className='px-4 pb-4 bg-slate-50 border-t border-slate-100 pt-3 text-sm'>
<p className="font-semibold text-slate-700 mb-1">Methodology</p>
<div
className="flex flex-col gap-2 m-2"
dangerouslySetInnerHTML={{ __html: methodologyText }}
/>
</div>
)}
</div>
);
};
function formatToHTML(rawInput) {
if (!rawInput) return "";
// Convert bold markdown (**text**) to <strong>text</strong>
let formatted = rawInput.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>");
// Split into lines (handle cases where everything is in one line)
formatted = formatted.replace(/(\d+\.)/g, "\n$1");
formatted = formatted.replace(/\*/g, "\n*");
const lines = formatted.split("\n").map(line => line.trim()).filter(Boolean);
let html = "";
let inOrderedList = false;
let inUnorderedList = false;
lines.forEach(line => {
// Ordered list (e.g., "1. Something")
if (/^\d+\.\s/.test(line)) {
if (!inOrderedList) {
html += "<ol>";
inOrderedList = true;
}
if (inUnorderedList) {
html += "</ul>";
inUnorderedList = false;
}
html += `<li>${line.replace(/^\d+\.\s/, "")}</li>`;
}
// Unordered list (e.g., "* Something")
else if (/^\*\s/.test(line)) {
if (!inUnorderedList) {
html += "<ul>";
inUnorderedList = true;
}
if (inOrderedList) {
html += "</ol>";
inOrderedList = false;
}
html += `<li>${line.replace(/^\*\s/, "")}</li>`;
}
// Paragraph fallback
else {
if (inOrderedList) {
html += "</ol>";
inOrderedList = false;
}
if (inUnorderedList) {
html += "</ul>";
inUnorderedList = false;
}
html += `<p>${line}</p>`;
}
});
// Close any open lists
if (inOrderedList) html += "</ol>";
if (inUnorderedList) html += "</ul>";
return html;
}
export default InnovationCard; |