This commit introduces the foundational elements for the new "Company Explorer" web application, marking a significant step away from the legacy Google Sheets / CLI system. Key changes include: - Project Structure: A new directory with separate (FastAPI) and (React/Vite) components. - Data Persistence: Migration from Google Sheets to a local SQLite database () using SQLAlchemy. - Core Utilities: Extraction and cleanup of essential helper functions (LLM wrappers, text utilities) into . - Backend Services: , , for AI-powered analysis, and logic. - Frontend UI: Basic React application with company table, import wizard, and dynamic inspector sidebar. - Docker Integration: Updated and for multi-stage builds and sideloading. - Deployment & Access: Integrated into central Nginx proxy and dashboard, accessible via . Lessons Learned & Fixed during development: - Frontend Asset Loading: Addressed issues with Vite's path and FastAPI's . - TypeScript Configuration: Added and . - Database Schema Evolution: Solved errors by forcing a new database file and correcting override. - Logging: Implemented robust file-based logging (). This new foundation provides a powerful and maintainable platform for future B2B robotics lead generation.
117 lines
4.1 KiB
TypeScript
117 lines
4.1 KiB
TypeScript
import { useState, useEffect } from 'react'
|
|
import axios from 'axios'
|
|
import { CompanyTable } from './components/CompanyTable'
|
|
import { ImportWizard } from './components/ImportWizard'
|
|
import { Inspector } from './components/Inspector' // NEW
|
|
import { LayoutDashboard, UploadCloud, Search, RefreshCw } from 'lucide-react'
|
|
|
|
// Base URL detection (Production vs Dev)
|
|
const API_BASE = import.meta.env.BASE_URL === '/ce/' ? '/ce/api' : '/api';
|
|
|
|
interface Stats {
|
|
total: number;
|
|
}
|
|
|
|
function App() {
|
|
const [stats, setStats] = useState<Stats>({ total: 0 })
|
|
const [refreshKey, setRefreshKey] = useState(0)
|
|
const [isImportOpen, setIsImportOpen] = useState(false)
|
|
const [selectedCompanyId, setSelectedCompanyId] = useState<number | null>(null) // NEW
|
|
|
|
const fetchStats = async () => {
|
|
try {
|
|
const res = await axios.get(`${API_BASE}/companies?limit=1`)
|
|
setStats({ total: res.data.total })
|
|
} catch (e) {
|
|
console.error("Failed to fetch stats", e)
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
fetchStats()
|
|
}, [refreshKey])
|
|
|
|
const handleCompanySelect = (id: number) => {
|
|
setSelectedCompanyId(id)
|
|
}
|
|
|
|
const handleCloseInspector = () => {
|
|
setSelectedCompanyId(null)
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-slate-950 text-slate-200 font-sans">
|
|
<ImportWizard
|
|
isOpen={isImportOpen}
|
|
onClose={() => setIsImportOpen(false)}
|
|
apiBase={API_BASE}
|
|
onSuccess={() => setRefreshKey(k => k + 1)}
|
|
/>
|
|
|
|
{/* Inspector Sidebar */}
|
|
<Inspector
|
|
companyId={selectedCompanyId}
|
|
onClose={handleCloseInspector}
|
|
apiBase={API_BASE}
|
|
/>
|
|
|
|
{/* Header */}
|
|
<header className="border-b border-slate-800 bg-slate-900/50 sticky top-0 z-10 backdrop-blur-md">
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-2 bg-blue-600 rounded-lg">
|
|
<LayoutDashboard className="h-6 w-6 text-white" />
|
|
</div>
|
|
<div>
|
|
<h1 className="text-xl font-bold text-white tracking-tight">Company Explorer</h1>
|
|
<p className="text-xs text-blue-400 font-medium">ROBOTICS EDITION <span className="text-slate-600 ml-2">v0.2.2 (New DB Path)</span></p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-4">
|
|
<div className="text-sm text-slate-400">
|
|
<span className="text-white font-bold">{stats.total}</span> Companies
|
|
</div>
|
|
|
|
<button
|
|
onClick={() => setRefreshKey(k => k + 1)}
|
|
className="p-2 hover:bg-slate-800 rounded-full transition-colors text-slate-400 hover:text-white"
|
|
title="Refresh Data"
|
|
>
|
|
<RefreshCw className="h-5 w-5" />
|
|
</button>
|
|
|
|
<button
|
|
className="flex items-center gap-2 bg-blue-600 hover:bg-blue-500 text-white px-4 py-2 rounded-md font-medium text-sm transition-all shadow-lg shadow-blue-900/20"
|
|
onClick={() => setIsImportOpen(true)}
|
|
>
|
|
<UploadCloud className="h-4 w-4" />
|
|
Import List
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
{/* Main Content */}
|
|
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
<div className="mb-6 flex gap-4">
|
|
<div className="relative flex-1 max-w-md">
|
|
<Search className="absolute left-3 top-2.5 h-5 w-5 text-slate-500" />
|
|
<input
|
|
type="text"
|
|
placeholder="Search companies..."
|
|
className="w-full bg-slate-900 border border-slate-700 text-slate-200 rounded-md pl-10 pr-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-slate-900 border border-slate-800 rounded-xl overflow-hidden shadow-xl">
|
|
<CompanyTable key={refreshKey} apiBase={API_BASE} onRowClick={handleCompanySelect} /> {/* NEW PROP */}
|
|
</div>
|
|
</main>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default App
|