feat(Explorer): Enhance metric extraction, source transparency, and UI display
- **Standardization & Formula Logic:** Fixed NameError/SyntaxError in formula parser; added support for comments and capitalized placeholders.
- **Source URL Tracking:** Extended DB schema and cascade logic to store and track specific source URLs.
- **Frontend & UI:**
- Added 'Standardized Potential' display in Inspector.
- Added clickable source link with icon.
- Fixed Settings tab layout collapse (flex-shrink-0).
- **Export Capabilities:**
- Single-company JSON export now includes full quantitative metadata.
- New global CSV export endpoint /api/companies/export.
- **System Integrity:**
- Fixed Notion sync typo ('Stanardization').
- Corrected Nginx proxy routing and FastAPI route ordering.
- Ensured DB persistence via explicit docker-compose volume mapping.
This commit is contained in:
@@ -104,7 +104,7 @@ export function RoboticsSettings({ isOpen, onClose, apiBase }: RoboticsSettingsP
|
||||
</div>
|
||||
|
||||
{/* Tab Nav */}
|
||||
<div className="flex border-b border-slate-200 dark:border-slate-800 px-6 bg-white dark:bg-slate-900 overflow-x-auto">
|
||||
<div className="flex flex-shrink-0 border-b border-slate-200 dark:border-slate-800 px-6 bg-white dark:bg-slate-900 overflow-x-auto">
|
||||
{[
|
||||
{ id: 'robotics', label: 'Robotics Potential', icon: Bot },
|
||||
{ id: 'industries', label: 'Industry Focus', icon: Target },
|
||||
@@ -130,72 +130,66 @@ export function RoboticsSettings({ isOpen, onClose, apiBase }: RoboticsSettingsP
|
||||
|
||||
{isLoading && <div className="text-center py-12 text-slate-500">Loading...</div>}
|
||||
|
||||
{!isLoading && activeTab === 'robotics' && (
|
||||
<div key="robotics-content" className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{roboticsCategories.map(cat => ( <CategoryCard key={cat.id} category={cat} onSave={handleUpdateRobotics} /> ))}
|
||||
</div>
|
||||
)}
|
||||
<div key="robotics-content" className={clsx("grid grid-cols-1 md:grid-cols-2 gap-6", { 'hidden': isLoading || activeTab !== 'robotics' })}>
|
||||
{roboticsCategories.map(cat => ( <CategoryCard key={cat.id} category={cat} onSave={handleUpdateRobotics} /> ))}
|
||||
</div>
|
||||
|
||||
{!isLoading && activeTab === 'industries' && (
|
||||
<div key="industries-content" className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<h3 className="text-sm font-bold text-slate-700 dark:text-slate-300">Industry Verticals (Synced from Notion)</h3>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3">
|
||||
{industries.map(ind => (
|
||||
<div key={ind.id} className="bg-slate-50 dark:bg-slate-950 border border-slate-200 dark:border-slate-800 rounded-lg p-4 flex flex-col gap-3 group relative overflow-hidden">
|
||||
{ind.notion_id && (
|
||||
<div className="absolute top-0 right-0 bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400 text-[9px] font-bold px-2 py-0.5 rounded-bl">SYNCED</div>
|
||||
)}
|
||||
<div className="flex gap-4 items-start pr-12">
|
||||
<div className="flex-1">
|
||||
<h4 className="font-bold text-slate-900 dark:text-white text-sm">{ind.name}</h4>
|
||||
<div className="flex flex-wrap gap-2 mt-1">
|
||||
{ind.status_notion && <span className="text-[10px] border border-slate-300 dark:border-slate-700 px-1.5 rounded text-slate-500">{ind.status_notion}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="flex items-center gap-1.5 justify-end">
|
||||
<span className={clsx("w-2 h-2 rounded-full", ind.is_focus ? "bg-green-500" : "bg-slate-300 dark:bg-slate-700")} />
|
||||
<span className="text-xs text-slate-500">{ind.is_focus ? "Focus" : "Standard"}</span>
|
||||
</div>
|
||||
<div key="industries-content" className={clsx("space-y-4", { 'hidden': isLoading || activeTab !== 'industries' })}>
|
||||
<div className="flex justify-between items-center">
|
||||
<h3 className="text-sm font-bold text-slate-700 dark:text-slate-300">Industry Verticals (Synced from Notion)</h3>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3">
|
||||
{industries.map(ind => (
|
||||
<div key={ind.id} className="bg-slate-50 dark:bg-slate-950 border border-slate-200 dark:border-slate-800 rounded-lg p-4 flex flex-col gap-3 group relative overflow-hidden">
|
||||
{ind.notion_id && (
|
||||
<div className="absolute top-0 right-0 bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400 text-[9px] font-bold px-2 py-0.5 rounded-bl">SYNCED</div>
|
||||
)}
|
||||
<div className="flex gap-4 items-start pr-12">
|
||||
<div className="flex-1">
|
||||
<h4 className="font-bold text-slate-900 dark:text-white text-sm">{ind.name}</h4>
|
||||
<div className="flex flex-wrap gap-2 mt-1">
|
||||
{ind.status_notion && <span className="text-[10px] border border-slate-300 dark:border-slate-700 px-1.5 rounded text-slate-500">{ind.status_notion}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-slate-600 dark:text-slate-300 italic whitespace-pre-wrap">{ind.description || "No definition"}</p>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2 text-[10px] bg-white dark:bg-slate-900 p-2 rounded border border-slate-200 dark:border-slate-800">
|
||||
<div><span className="block text-slate-400 font-bold uppercase">Whale ></span><span className="text-slate-700 dark:text-slate-200">{ind.whale_threshold || "-"}</span></div>
|
||||
<div><span className="block text-slate-400 font-bold uppercase">Min Req</span><span className="text-slate-700 dark:text-slate-200">{ind.min_requirement || "-"}</span></div>
|
||||
<div><span className="block text-slate-400 font-bold uppercase">Unit</span><span className="text-slate-700 dark:text-slate-200 truncate">{ind.scraper_search_term || "-"}</span></div>
|
||||
<div><span className="block text-slate-400 font-bold uppercase">Product</span><span className="text-slate-700 dark:text-slate-200 truncate">{roboticsCategories.find(c => c.id === ind.primary_category_id)?.name || "-"}</span></div>
|
||||
<div className="text-right">
|
||||
<div className="flex items-center gap-1.5 justify-end">
|
||||
<span className={clsx("w-2 h-2 rounded-full", ind.is_focus ? "bg-green-500" : "bg-slate-300 dark:bg-slate-700")} />
|
||||
<span className="text-xs text-slate-500">{ind.is_focus ? "Focus" : "Standard"}</span>
|
||||
</div>
|
||||
</div>
|
||||
{ind.scraper_keywords && <div className="text-[10px]"><span className="text-slate-400 font-bold uppercase mr-2">Keywords:</span><span className="text-slate-600 dark:text-slate-400 font-mono">{ind.scraper_keywords}</span></div>}
|
||||
{ind.standardization_logic && <div className="text-[10px]"><span className="text-slate-400 font-bold uppercase mr-2">Standardization:</span><span className="text-slate-600 dark:text-slate-400 font-mono">{ind.standardization_logic}</span></div>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xs text-slate-600 dark:text-slate-300 italic whitespace-pre-wrap">{ind.description || "No definition"}</p>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2 text-[10px] bg-white dark:bg-slate-900 p-2 rounded border border-slate-200 dark:border-slate-800">
|
||||
<div><span className="block text-slate-400 font-bold uppercase">Whale ></span><span className="text-slate-700 dark:text-slate-200">{ind.whale_threshold || "-"}</span></div>
|
||||
<div><span className="block text-slate-400 font-bold uppercase">Min Req</span><span className="text-slate-700 dark:text-slate-200">{ind.min_requirement || "-"}</span></div>
|
||||
<div><span className="block text-slate-400 font-bold uppercase">Unit</span><span className="text-slate-700 dark:text-slate-200 truncate">{ind.scraper_search_term || "-"}</span></div>
|
||||
<div><span className="block text-slate-400 font-bold uppercase">Product</span><span className="text-slate-700 dark:text-slate-200 truncate">{roboticsCategories.find(c => c.id === ind.primary_category_id)?.name || "-"}</span></div>
|
||||
</div>
|
||||
{ind.scraper_keywords && <div className="text-[10px]"><span className="text-slate-400 font-bold uppercase mr-2">Keywords:</span><span className="text-slate-600 dark:text-slate-400 font-mono">{ind.scraper_keywords}</span></div>}
|
||||
{ind.standardization_logic && <div className="text-[10px]"><span className="text-slate-400 font-bold uppercase mr-2">Standardization:</span><span className="text-slate-600 dark:text-slate-400 font-mono">{ind.standardization_logic}</span></div>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!isLoading && activeTab === 'roles' && (
|
||||
<div key="roles-content" className="space-y-4">
|
||||
<div className="flex justify-between items-center"><h3 className="text-sm font-bold text-slate-700 dark:text-slate-300">Job Title Mapping Patterns</h3><button onClick={handleAddJobRole} className="flex items-center gap-1 px-3 py-1.5 bg-blue-600 hover:bg-blue-500 text-white text-xs font-bold rounded"><Plus className="h-3 w-3" /> ADD PATTERN</button></div>
|
||||
<div className="bg-slate-50 dark:bg-slate-950 border border-slate-200 dark:border-slate-800 rounded-lg overflow-hidden">
|
||||
<table className="w-full text-left text-xs">
|
||||
<thead className="bg-slate-100 dark:bg-slate-900 border-b border-slate-200 dark:border-slate-800 text-slate-500 font-bold uppercase"><tr><th className="p-3">Job Title Pattern (Regex/Text)</th><th className="p-3">Mapped Role</th><th className="p-3 w-10"></th></tr></thead>
|
||||
<tbody className="divide-y divide-slate-200 dark:divide-slate-800">
|
||||
{jobRoles.map(role => (
|
||||
<tr key={role.id} className="group">
|
||||
<td className="p-2"><input className="w-full bg-transparent border border-transparent hover:border-slate-300 dark:hover:border-slate-700 rounded px-2 py-1 text-slate-900 dark:text-slate-200 outline-none focus:border-blue-500" defaultValue={role.pattern} /></td>
|
||||
<td className="p-2"><select className="w-full bg-transparent border border-transparent hover:border-slate-300 dark:hover:border-slate-700 rounded px-2 py-1 text-slate-900 dark:text-slate-200 outline-none focus:border-blue-500" defaultValue={role.role}><option>Operativer Entscheider</option><option>Infrastruktur-Verantwortlicher</option><option>Wirtschaftlicher Entscheider</option><option>Innovations-Treiber</option></select></td>
|
||||
<td className="p-2 text-center"><button onClick={() => handleDeleteJobRole(role.id)} className="text-slate-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity"><Trash2 className="h-4 w-4" /></button></td>
|
||||
</tr>
|
||||
))}
|
||||
{jobRoles.length === 0 && (<tr><td colSpan={3} className="p-8 text-center text-slate-500 italic">No patterns defined yet.</td></tr>)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div key="roles-content" className={clsx("space-y-4", { 'hidden': isLoading || activeTab !== 'roles' })}>
|
||||
<div className="flex justify-between items-center"><h3 className="text-sm font-bold text-slate-700 dark:text-slate-300">Job Title Mapping Patterns</h3><button onClick={handleAddJobRole} className="flex items-center gap-1 px-3 py-1.5 bg-blue-600 hover:bg-blue-500 text-white text-xs font-bold rounded"><Plus className="h-3 w-3" /> ADD PATTERN</button></div>
|
||||
<div className="bg-slate-50 dark:bg-slate-950 border border-slate-200 dark:border-slate-800 rounded-lg overflow-hidden">
|
||||
<table className="w-full text-left text-xs">
|
||||
<thead className="bg-slate-100 dark:bg-slate-900 border-b border-slate-200 dark:border-slate-800 text-slate-500 font-bold uppercase"><tr><th className="p-3">Job Title Pattern (Regex/Text)</th><th className="p-3">Mapped Role</th><th className="p-3 w-10"></th></tr></thead>
|
||||
<tbody className="divide-y divide-slate-200 dark:divide-slate-800">
|
||||
{jobRoles.map(role => (
|
||||
<tr key={role.id} className="group">
|
||||
<td className="p-2"><input className="w-full bg-transparent border border-transparent hover:border-slate-300 dark:hover:border-slate-700 rounded px-2 py-1 text-slate-900 dark:text-slate-200 outline-none focus:border-blue-500" defaultValue={role.pattern} /></td>
|
||||
<td className="p-2"><select className="w-full bg-transparent border border-transparent hover:border-slate-300 dark:hover:border-slate-700 rounded px-2 py-1 text-slate-900 dark:text-slate-200 outline-none focus:border-blue-500" defaultValue={role.role}><option>Operativer Entscheider</option><option>Infrastruktur-Verantwortlicher</option><option>Wirtschaftlicher Entscheider</option><option>Innovations-Treiber</option></select></td>
|
||||
<td className="p-2 text-center"><button onClick={() => handleDeleteJobRole(role.id)} className="text-slate-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity"><Trash2 className="h-4 w-4" /></button></td>
|
||||
</tr>
|
||||
))}
|
||||
{jobRoles.length === 0 && (<tr><td colSpan={3} className="p-8 text-center text-slate-500 italic">No patterns defined yet.</td></tr>)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user