Files
Brancheneinstufung2/company-explorer/frontend/src/components/CompanyTable.tsx

188 lines
8.1 KiB
TypeScript

import { useState, useEffect } from 'react'
import axios from 'axios'
import {
Building, Search, ChevronLeft, ChevronRight, Upload,
Globe, MapPin, Play, Search as SearchIcon, Loader2
} from 'lucide-react'
interface Company {
id: number
name: string
city: string | null
country: string
website: string | null
status: string
industry_ai: string | null
}
interface CompanyTableProps {
apiBase: string
onRowClick: (companyId: number) => void
refreshKey: number
onImportClick: () => void
}
export function CompanyTable({ apiBase, onRowClick, refreshKey, onImportClick }: CompanyTableProps) {
const [data, setData] = useState<Company[]>([])
const [total, setTotal] = useState(0)
const [page, setPage] = useState(0)
const [search, setSearch] = useState("")
const [loading, setLoading] = useState(false)
const [processingId, setProcessingId] = useState<number | null>(null)
const limit = 50
const fetchData = async () => {
setLoading(true)
try {
const res = await axios.get(`${apiBase}/companies?skip=${page * limit}&limit=${limit}&search=${search}`)
setData(res.data.items)
setTotal(res.data.total)
} catch (e) {
console.error(e)
} finally {
setLoading(false)
}
}
useEffect(() => {
fetchData()
}, [page, search, refreshKey])
const triggerDiscovery = async (id: number) => {
setProcessingId(id)
try {
await axios.post(`${apiBase}/enrich/discover`, { company_id: id })
setTimeout(fetchData, 2000)
} catch (e) {
alert("Discovery Error")
} finally {
setProcessingId(null)
}
}
const triggerAnalysis = async (id: number) => {
setProcessingId(id)
try {
await axios.post(`${apiBase}/enrich/analyze`, { company_id: id })
setTimeout(fetchData, 2000)
} catch (e) {
alert("Analysis Error")
} finally {
setProcessingId(null)
}
}
return (
<div className="flex flex-col h-full bg-white dark:bg-slate-900 transition-colors">
{/* Toolbar - Same style as Contacts */}
<div className="flex flex-col md:flex-row gap-4 p-4 border-b border-slate-200 dark:border-slate-800 items-center justify-between bg-slate-50 dark:bg-slate-950/50">
<div className="flex items-center gap-2 text-slate-700 dark:text-slate-300 font-bold text-lg">
<Building className="h-5 w-5" />
<h2>Companies ({total})</h2>
</div>
<div className="flex flex-1 w-full md:w-auto gap-2 max-w-xl">
<div className="relative flex-1">
<Search className="absolute left-3 top-2.5 h-4 w-4 text-slate-400" />
<input
type="text"
placeholder="Search companies, cities, industries..."
className="w-full pl-10 pr-4 py-2 bg-white dark:bg-slate-800 border border-slate-300 dark:border-slate-700 rounded-md text-sm text-slate-900 dark:text-slate-200 focus:ring-2 focus:ring-blue-500 outline-none"
value={search}
onChange={e => { setSearch(e.target.value); setPage(0); }}
/>
</div>
<button
onClick={onImportClick}
className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-500 text-white text-sm font-bold rounded-md shadow-sm transition-colors"
>
<Upload className="h-4 w-4" /> <span className="hidden md:inline">Import</span>
</button>
</div>
</div>
{/* Grid View - Same as Contacts */}
<div className="flex-1 overflow-auto bg-slate-50 dark:bg-slate-950/30">
{loading && <div className="p-4 text-center text-slate-500">Loading companies...</div>}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 p-4">
{data.map((c) => (
<div
key={c.id}
onClick={() => onRowClick(c.id)}
className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-lg p-4 hover:shadow-lg transition-all flex flex-col gap-3 group cursor-pointer border-l-4"
style={{ borderLeftColor: c.status === 'ENRICHED' ? '#22c55e' : c.status === 'DISCOVERED' ? '#3b82f6' : '#94a3b8' }}
>
<div className="flex items-start justify-between">
<div className="min-w-0 flex-1">
<div className="font-bold text-slate-900 dark:text-white text-sm truncate" title={c.name}>
{c.name}
</div>
<div className="flex items-center gap-1 text-[10px] text-slate-500 dark:text-slate-400 font-medium">
<MapPin className="h-3 w-3" /> {c.city || 'Unknown'}, {c.country}
</div>
</div>
<div className="flex gap-1 ml-2">
{processingId === c.id ? (
<Loader2 className="h-4 w-4 animate-spin text-blue-500" />
) : c.status === 'NEW' || !c.website || c.website === 'k.A.' ? (
<button
onClick={(e) => { e.stopPropagation(); triggerDiscovery(c.id); }}
className="p-1.5 bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 rounded hover:bg-blue-600 hover:text-white transition-colors"
>
<SearchIcon className="h-3.5 w-3.5" />
</button>
) : (
<button
onClick={(e) => { e.stopPropagation(); triggerAnalysis(c.id); }}
className="p-1.5 bg-blue-50 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400 rounded hover:bg-blue-600 hover:text-white transition-colors"
>
<Play className="h-3.5 w-3.5 fill-current" />
</button>
)}
</div>
</div>
<div className="space-y-2 pt-2 border-t border-slate-100 dark:border-slate-800/50">
{c.website && c.website !== "k.A." ? (
<div className="flex items-center gap-2 text-xs text-blue-600 dark:text-blue-400 font-medium truncate">
<Globe className="h-3 w-3" />
<span>{new URL(c.website).hostname.replace('www.', '')}</span>
</div>
) : (
<div className="text-xs text-slate-400 italic">No website found</div>
)}
<div className="text-[10px] text-slate-500 uppercase font-bold tracking-wider truncate">
{c.industry_ai || "Industry Pending"}
</div>
</div>
</div>
))}
</div>
</div>
{/* Pagination */}
<div className="p-3 border-t border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900 flex justify-between items-center text-xs text-slate-500 dark:text-slate-400">
<span>{total} Companies total</span>
<div className="flex gap-1">
<button
disabled={page === 0}
onClick={() => setPage(p => Math.max(0, p - 1))}
className="p-1 rounded hover:bg-slate-100 dark:hover:bg-slate-800 disabled:opacity-30"
>
<ChevronLeft className="h-4 w-4" />
</button>
<span className="px-2 py-1">Page {page + 1}</span>
<button
disabled={(page + 1) * limit >= total}
onClick={() => setPage(p => p + 1)}
className="p-1 rounded hover:bg-slate-100 dark:hover:bg-slate-800 disabled:opacity-30"
>
<ChevronRight className="h-4 w-4" />
</button>
</div>
</div>
</div>
)
}