grantforge-api / frontend-react /src /components /project /TrustCertificatePanel.tsx
GrantForge Bot
Deploy sha-3d4ebe3261a5907e50617c6c64d42c59bd3d4877 — source build (no GHCR)
61d1e19
Raw
History Blame Contribute Delete
10.5 kB
import React, { useState, useEffect } from 'react';
import { ShieldCheck, ShieldAlert, FileText, Download, Eye, Link as LinkIcon } from 'lucide-react';
interface TrustCertificateProps {
projectId: string;
credibilityFlags?: any;
regulationGrounding?: string;
preciseRegulationUrl?: string;
regulationLinkQuality?: string;
snapshotData?: any;
onExportCertificate?: () => void;
onExportJSON?: () => void;
}
interface CredibilityHealth {
overall_credibility: string;
components: any;
}
export const TrustCertificatePanel: React.FC<TrustCertificateProps> = ({
projectId,
credibilityFlags,
regulationGrounding,
preciseRegulationUrl,
regulationLinkQuality = 'medium',
snapshotData,
onExportCertificate,
onExportJSON
}) => {
const [health, setHealth] = useState<CredibilityHealth | null>(null);
const [showCert, setShowCert] = useState(false);
const [showModal, setShowModal] = useState(false);
useEffect(() => {
fetch('/api/admin/credibility-health')
.then(r => r.json())
.then(setHealth)
.catch(() => {});
}, []);
const trustLevel = credibilityFlags?.overall_trust_level ||
(credibilityFlags?.msp_using_real_data ? 'high' : 'medium');
const badgeColor = trustLevel === 'high' ? '#10b981' : trustLevel === 'medium' ? '#f59e0b' : '#ef4444';
const qualityColor = regulationLinkQuality === 'high' ? '#10b981' : regulationLinkQuality === 'medium' ? '#f59e0b' : '#64748b';
const openFullCertificate = () => setShowModal(true);
return (
<>
<div style={{
border: `2px solid ${badgeColor}`,
borderRadius: 10,
padding: 14,
marginBottom: 16,
background: '#f8fafc',
boxShadow: '0 1px 3px rgba(0,0,0,0.08)'
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 8 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<span style={{
background: badgeColor,
color: 'white',
padding: '3px 12px',
borderRadius: 999,
fontSize: 11,
fontWeight: 700,
display: 'inline-flex',
alignItems: 'center',
gap: 4
}}>
<ShieldCheck size={14} /> {trustLevel.toUpperCase()} TRUST
</span>
<span style={{
background: qualityColor,
color: 'white',
padding: '2px 8px',
borderRadius: 6,
fontSize: 10,
fontWeight: 600
}}>
REG LINK: {regulationLinkQuality.toUpperCase()}
</span>
<span style={{ fontWeight: 600, fontSize: 14, color: '#1e2937' }}>
Regulation Grounding Certificate
</span>
</div>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
<button
onClick={() => setShowCert(!showCert)}
style={{
background: '#334155',
color: 'white',
border: 'none',
padding: '5px 11px',
borderRadius: 6,
fontSize: 12,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: 4
}}
>
<Eye size={14} /> {showCert ? 'Ukryj' : 'Pokaż'} Szczegóły
</button>
<button
onClick={openFullCertificate}
style={{
background: '#1e40af',
color: 'white',
border: 'none',
padding: '5px 11px',
borderRadius: 6,
fontSize: 12,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: 4
}}
>
<FileText size={14} /> Pełne Świadectwo
</button>
{onExportCertificate && (
<button
onClick={onExportCertificate}
style={{
background: '#059669',
color: 'white',
border: 'none',
padding: '5px 11px',
borderRadius: 6,
fontSize: 12,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: 4
}}
>
<Download size={14} /> PDF
</button>
)}
{onExportJSON && (
<button
onClick={onExportJSON}
style={{
background: '#7c3aed',
color: 'white',
border: 'none',
padding: '5px 11px',
borderRadius: 6,
fontSize: 12,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: 4
}}
>
JSON
</button>
)}
</div>
</div>
{preciseRegulationUrl && (
<div style={{ marginTop: 8, fontSize: 12, display: 'flex', alignItems: 'center', gap: 6 }}>
<LinkIcon size={13} style={{ color: '#64748b' }} />
<a href={preciseRegulationUrl} target="_blank" rel="noreferrer"
style={{ color: '#1e40af', textDecoration: 'underline', fontSize: 12 }}>
Oficjalny dokument regulaminu (najwyższa precyzja)
</a>
</div>
)}
{showCert && (
<div style={{ marginTop: 12, padding: 12, background: 'white', borderRadius: 6, fontSize: 12.5, border: '1px solid #e2e8f0' }}>
<div style={{ marginBottom: 6, fontWeight: 600, color: '#334155' }}>Regulation Grounding Certificate v1</div>
{snapshotData && (
<div style={{ fontSize: 11.5, lineHeight: 1.45, color: '#334155' }}>
<strong>Program:</strong> {snapshotData.program}<br />
<strong>Version Hash:</strong> <code style={{ fontSize: 10 }}>{snapshotData.version_hash}</code><br />
<strong>Snapshot ID:</strong> {snapshotData.id}<br />
<strong>Effective:</strong> {snapshotData.effective_date || 'N/A'} | <strong>Institution:</strong> {snapshotData.source_institution || 'N/A'}
</div>
)}
{regulationGrounding && (
<pre style={{
marginTop: 8,
fontSize: 10,
background: '#f1f5f9',
padding: 8,
borderRadius: 4,
maxHeight: 140,
overflow: 'auto',
whiteSpace: 'pre-wrap'
}}>
{regulationGrounding.slice(0, 650)}
</pre>
)}
{credibilityFlags && (
<div style={{ marginTop: 8, fontSize: 11, color: '#475569' }}>
<strong>Trust Flags:</strong> MSP Real={!!credibilityFlags.msp_using_real_data} | GUS Enriched={!!credibilityFlags.gus_enriched} | Level: {credibilityFlags.overall_trust_level}
</div>
)}
{health && (
<div style={{ marginTop: 8, padding: 6, background: '#ecfdf5', borderRadius: 4, fontSize: 11 }}>
<strong>System Health:</strong> {health.overall_credibility}
{health.components?.acquisition_regulation_link_quality && (
<> | Link Quality: {health.components.acquisition_regulation_link_quality.high_quality_links_pct}% high</>
)}
</div>
)}
<div style={{ marginTop: 8, fontSize: 10.5, color: '#64748b' }}>
Pełna audytowalność: hash wersji + snapshot + EUR-Lex + GUS. Dokument nadaje się do załączenia instytucji finansującej.
</div>
</div>
)}
</div>
{/* Full Certificate Modal */}
{showModal && (
<div style={{
position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
background: 'rgba(15,23,42,0.65)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000
}} onClick={() => setShowModal(false)}>
<div style={{
background: 'white', width: '92%', maxWidth: 720, maxHeight: '88vh', overflow: 'auto',
borderRadius: 12, padding: 24, boxShadow: '0 20px 25px -5px rgb(0 0 0 / 0.1)'
}} onClick={e => e.stopPropagation()}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
<h3 style={{ margin: 0, fontSize: 18 }}>Pełne Świadectwo Zgodności — Regulation Grounding Certificate</h3>
<button onClick={() => setShowModal(false)} style={{ fontSize: 20, border: 'none', background: 'none', cursor: 'pointer' }}>×</button>
</div>
<div style={{ fontSize: 13, lineHeight: 1.55, color: '#334155' }}>
<p><strong>Projekt ID:</strong> {projectId}</p>
{preciseRegulationUrl && <p><strong>Najbardziej precyzyjny dokument:</strong> <a href={preciseRegulationUrl} target="_blank">{preciseRegulationUrl}</a></p>}
{regulationGrounding && <pre style={{ background: '#f8fafc', padding: 12, fontSize: 11, whiteSpace: 'pre-wrap' }}>{regulationGrounding}</pre>}
{snapshotData && <pre style={{ background: '#f1f5f9', padding: 10, fontSize: 11 }}>{JSON.stringify(snapshotData, null, 2)}</pre>}
</div>
<div style={{ marginTop: 20, display: 'flex', gap: 8 }}>
{onExportCertificate && <button onClick={onExportCertificate} style={{ background: '#059669', color: 'white', padding: '8px 16px', borderRadius: 6, border: 'none' }}>Pobierz PDF Świadectwo</button>}
{onExportJSON && <button onClick={onExportJSON} style={{ background: '#7c3aed', color: 'white', padding: '8px 16px', borderRadius: 6, border: 'none' }}>Pobierz JSON</button>}
<button onClick={() => setShowModal(false)} style={{ padding: '8px 16px', borderRadius: 6, border: '1px solid #cbd5e1' }}>Zamknij</button>
</div>
</div>
</div>
)}
</>
);
};
export default TrustCertificatePanel;