[2f388f42] Implementierung der UI-Anpassungen zur Anzeige von ausstehenden Fehlerberichten (rote Flagge in der Unternehmensliste, Anzeige im Inspector) und zur Ermöglichung weiterer Fehlerberichte. Backend-APIs wurden entsprechend erweitert.
Implementierung der UI-Anpassungen zur Anzeige von ausstehenden Fehlerberichten (rote Flagge in der Unternehmensliste, Anzeige im Inspector) und zur Ermöglichung weiterer Fehlerberichte. Backend-APIs wurden entsprechend erweitert.
This commit is contained in:
@@ -1 +1 @@
|
||||
{"task_id": "2f488f42-8544-802f-8311-ee72ef1aac2f", "token": "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", "session_start_time": "2026-01-27T10:44:04.509525"}
|
||||
{"task_id": "2f388f42-8544-80b7-9c71-e7c8f319990a", "token": "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", "session_start_time": "2026-01-27T11:18:14.201030"}
|
||||
@@ -107,6 +107,22 @@ def list_companies(
|
||||
query = query.order_by(Company.name.asc())
|
||||
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
|
||||
# Efficiently check for pending mistakes
|
||||
company_ids = [c.id for c in items]
|
||||
if company_ids:
|
||||
pending_mistakes = db.query(ReportedMistake.company_id).filter(
|
||||
ReportedMistake.company_id.in_(company_ids),
|
||||
ReportedMistake.status == 'PENDING'
|
||||
).distinct().all()
|
||||
companies_with_pending_mistakes = {row[0] for row in pending_mistakes}
|
||||
else:
|
||||
companies_with_pending_mistakes = set()
|
||||
|
||||
# Add the flag to each company object
|
||||
for company in items:
|
||||
company.has_pending_mistakes = company.id in companies_with_pending_mistakes
|
||||
|
||||
return {"total": total, "items": items}
|
||||
except Exception as e:
|
||||
logger.error(f"List Companies Error: {e}", exc_info=True)
|
||||
@@ -251,6 +267,7 @@ def list_job_roles(db: Session = Depends(get_db)):
|
||||
@app.get("/api/mistakes")
|
||||
def list_reported_mistakes(
|
||||
status: Optional[str] = Query(None),
|
||||
company_id: Optional[int] = Query(None),
|
||||
skip: int = 0,
|
||||
limit: int = 50,
|
||||
db: Session = Depends(get_db)
|
||||
@@ -260,6 +277,9 @@ def list_reported_mistakes(
|
||||
if status:
|
||||
query = query.filter(ReportedMistake.status == status.upper())
|
||||
|
||||
if company_id:
|
||||
query = query.filter(ReportedMistake.company_id == company_id)
|
||||
|
||||
total = query.count()
|
||||
items = query.order_by(ReportedMistake.created_at.desc()).offset(skip).limit(limit).all()
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'
|
||||
import axios from 'axios'
|
||||
import {
|
||||
Building, Search, Upload, Globe, MapPin, Play, Search as SearchIcon, Loader2,
|
||||
LayoutGrid, List, ChevronLeft, ChevronRight, ArrowDownUp
|
||||
LayoutGrid, List, ChevronLeft, ChevronRight, ArrowDownUp, Flag
|
||||
} from 'lucide-react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
@@ -16,6 +16,7 @@ interface Company {
|
||||
industry_ai: string | null
|
||||
created_at: string
|
||||
updated_at: string
|
||||
has_pending_mistakes: boolean
|
||||
}
|
||||
|
||||
interface CompanyTableProps {
|
||||
@@ -124,7 +125,10 @@ export function CompanyTable({ apiBase, onRowClick, refreshKey, onImportClick }:
|
||||
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-2">
|
||||
<Flag className={clsx("h-3 w-3 text-slate-300 dark:text-slate-600", c.has_pending_mistakes && "text-red-500 fill-red-500")} />
|
||||
<div className="font-bold text-slate-900 dark:text-white text-sm truncate" title={c.name}>{c.name}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-[10px] text-slate-500 dark:text-slate-400 font-medium">
|
||||
{c.city && c.country ? (<><MapPin className="h-3 w-3" /> {c.city} <span className="text-slate-400">({c.country})</span></>) : (<span className="italic opacity-50">-</span>)}
|
||||
</div>
|
||||
@@ -163,7 +167,12 @@ export function CompanyTable({ apiBase, onRowClick, refreshKey, onImportClick }:
|
||||
<tbody className="divide-y divide-slate-200 dark:divide-slate-800 bg-white dark:bg-slate-900">
|
||||
{data.map((c) => (
|
||||
<tr key={c.id} onClick={() => onRowClick(c.id)} className="hover:bg-slate-50 dark:hover:bg-slate-800/50 cursor-pointer">
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm font-medium text-slate-900 dark:text-white">{c.name}</td>
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm font-medium text-slate-900 dark:text-white">
|
||||
<div className="flex items-center gap-2">
|
||||
<Flag className={clsx("h-3 w-3 text-slate-300 dark:text-slate-600", c.has_pending_mistakes && "text-red-500 fill-red-500")} />
|
||||
<span>{c.name}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-slate-500 dark:text-slate-400">
|
||||
{c.city && c.country ? `${c.city}, (${c.country})` : '-'}
|
||||
</td>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import axios from 'axios'
|
||||
import { X, ExternalLink, Bot, Briefcase, Calendar, Globe, Users, DollarSign, MapPin, Tag, RefreshCw as RefreshCwIcon, Search as SearchIcon, Pencil, Check, Download, Clock, Lock, Unlock, Calculator, Ruler, Database, Trash2, Flag } from 'lucide-react'
|
||||
import { X, ExternalLink, Bot, Briefcase, Calendar, Globe, Users, DollarSign, MapPin, Tag, RefreshCw as RefreshCwIcon, Search as SearchIcon, Pencil, Check, Download, Clock, Lock, Unlock, Calculator, Ruler, Database, Trash2, Flag, AlertTriangle } from 'lucide-react'
|
||||
import clsx from 'clsx'
|
||||
import { ContactsManager, Contact } from './ContactsManager'
|
||||
|
||||
@@ -48,6 +48,20 @@ type CompanyDetail = {
|
||||
metric_confidence_reason: string | null
|
||||
}
|
||||
|
||||
// NEW
|
||||
type ReportedMistake = {
|
||||
id: number;
|
||||
field_name: string;
|
||||
wrong_value: string | null;
|
||||
corrected_value: string | null;
|
||||
source_url: string | null;
|
||||
quote: string | null;
|
||||
user_comment: string | null;
|
||||
status: 'PENDING' | 'APPROVED' | 'REJECTED';
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
|
||||
export function Inspector({ companyId, initialContactId, onClose, apiBase }: InspectorProps) {
|
||||
const [data, setData] = useState<CompanyDetail | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
@@ -56,6 +70,7 @@ export function Inspector({ companyId, initialContactId, onClose, apiBase }: Ins
|
||||
|
||||
// NEW: Report Mistake State
|
||||
const [isReportingMistake, setIsReportingMistake] = useState(false)
|
||||
const [existingMistakes, setExistingMistakes] = useState<ReportedMistake[]>([])
|
||||
const [reportedFieldName, setReportedFieldName] = useState("")
|
||||
const [reportedWrongValue, setReportedWrongValue] = useState("")
|
||||
const [reportedCorrectedValue, setReportedCorrectedValue] = useState("")
|
||||
@@ -100,11 +115,14 @@ export function Inspector({ companyId, initialContactId, onClose, apiBase }: Ins
|
||||
if (!companyId) return
|
||||
if (!silent) setLoading(true)
|
||||
|
||||
axios.get(`${apiBase}/companies/${companyId}`)
|
||||
.then(res => {
|
||||
const newData = res.data
|
||||
console.log("FETCHED COMPANY DATA:", newData) // DEBUG: Log raw data from API
|
||||
const companyRequest = axios.get(`${apiBase}/companies/${companyId}`)
|
||||
const mistakesRequest = axios.get(`${apiBase}/mistakes?company_id=${companyId}`)
|
||||
|
||||
Promise.all([companyRequest, mistakesRequest])
|
||||
.then(([companyRes, mistakesRes]) => {
|
||||
const newData = companyRes.data
|
||||
setData(newData)
|
||||
setExistingMistakes(mistakesRes.data.items)
|
||||
|
||||
// Auto-stop processing if status changes to ENRICHED or we see data
|
||||
if (isProcessing) {
|
||||
@@ -296,7 +314,15 @@ export function Inspector({ companyId, initialContactId, onClose, apiBase }: Ins
|
||||
}
|
||||
}
|
||||
|
||||
const handleLockToggle = async (sourceType: string, currentLockStatus: boolean) => {\n if (!companyId) return\n try {\n await axios.post(`${apiBase}/enrichment/${companyId}/${sourceType}/lock?locked=${!currentLockStatus}`)\n fetchData(true) // Silent refresh\n } catch (e) {\n console.error(\"Lock toggle failed\", e)\n }\n }\n\n // NEW: Interface for reporting mistakes\n interface ReportedMistakeRequest {\n field_name: string;\n wrong_value?: string | null;\n corrected_value?: string | null;\n source_url?: string | null;\n quote?: string | null;\n user_comment?: string | null;\n }\n\n const handleReportMistake = async () => {\n if (!companyId) return;\n if (!reportedFieldName) {\n alert(\"Field Name is required.\");\n return;\n }\n\n setIsProcessing(true);\n try {\n const payload: ReportedMistakeRequest = {\n field_name: reportedFieldName,\n wrong_value: reportedWrongValue || null,\n corrected_value: reportedCorrectedValue || null,\n source_url: reportedSourceUrl || null,\n quote: reportedQuote || null,\n user_comment: reportedComment || null,\n };\n\n await axios.post(`${apiBase}/companies/${companyId}/report-mistake`, payload);\n alert(\"Mistake reported successfully!\");\n setIsReportingMistake(false);\n // Reset form fields\n setReportedFieldName(\"\");\n setReportedWrongValue(\"\");\ setReportedCorrectedValue(\"\");\n setReportedSourceUrl(\"\");\n setReportedQuote(\"\");\n setReportedComment(\"\");\n } catch (e) {\n alert(\"Failed to report mistake.\");\n console.error(e);\n } finally {\n setIsProcessing(false);\n }\n };
|
||||
const handleLockToggle = async (sourceType: string, currentLockStatus: boolean) => {
|
||||
if (!companyId) return
|
||||
try {
|
||||
await axios.post(`${apiBase}/enrichment/${companyId}/${sourceType}/lock?locked=${!currentLockStatus}`)
|
||||
fetchData(true) // Silent refresh
|
||||
} catch (e) {
|
||||
console.error("Lock toggle failed", e)
|
||||
}
|
||||
}
|
||||
|
||||
// NEW: Interface for reporting mistakes
|
||||
interface ReportedMistakeRequest {
|
||||
@@ -336,6 +362,7 @@ export function Inspector({ companyId, initialContactId, onClose, apiBase }: Ins
|
||||
setReportedSourceUrl("");
|
||||
setReportedQuote("");
|
||||
setReportedComment("");
|
||||
fetchData(true); // Re-fetch to show the new mistake
|
||||
} catch (e) {
|
||||
alert("Failed to report mistake.");
|
||||
console.error(e);
|
||||
@@ -478,6 +505,36 @@ export function Inspector({ companyId, initialContactId, onClose, apiBase }: Ins
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Reported Mistakes Section */}
|
||||
{existingMistakes.length > 0 && (
|
||||
<div className="mt-4 p-4 bg-orange-50 dark:bg-orange-900/20 border border-orange-200 dark:border-orange-800/50 rounded-lg">
|
||||
<h4 className="flex items-center gap-2 text-sm font-bold text-orange-800 dark:text-orange-300 mb-3">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
Existing Correction Proposals
|
||||
</h4>
|
||||
<div className="space-y-3 max-h-40 overflow-y-auto pr-2">
|
||||
{existingMistakes.map(mistake => (
|
||||
<div key={mistake.id} className="text-xs p-3 bg-white dark:bg-slate-800/50 rounded border border-slate-200 dark:border-slate-700/50">
|
||||
<div className="flex justify-between items-start">
|
||||
<span className="font-bold text-slate-800 dark:text-slate-200">{mistake.field_name}</span>
|
||||
<span className={clsx("px-2 py-0.5 rounded-full text-[9px] font-medium", {
|
||||
'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/50 dark:text-yellow-300': mistake.status === 'PENDING',
|
||||
'bg-green-100 text-green-800 dark:bg-green-900/50 dark:text-green-300': mistake.status === 'APPROVED',
|
||||
'bg-red-100 text-red-800 dark:bg-red-900/50 dark:text-red-300': mistake.status === 'REJECTED'
|
||||
})}>
|
||||
{mistake.status}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-slate-600 dark:text-slate-400 mt-1">
|
||||
<span className="line-through text-red-500/80">{mistake.wrong_value || 'N/A'}</span> → <strong className="text-green-600 dark:text-green-400">{mistake.corrected_value || 'N/A'}</strong>
|
||||
</p>
|
||||
{mistake.user_comment && <p className="mt-2 text-slate-500 italic">"{mistake.user_comment}"</p>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tab Navigation */}
|
||||
<div className="mt-6 flex border-b border-slate-200 dark:border-slate-800">
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user