Spaces:
Running
Running
| import React, { useState } from 'react'; | |
| import { Briefcase, Zap, AlertCircle } from 'lucide-react'; | |
| import AdvancedMatcherModal from './AdvancedMatcherModal'; | |
| import { updateProject } from '../../api/client'; | |
| import toast from 'react-hot-toast'; | |
| interface GrantMatchResult { | |
| program_id: string; | |
| program_name: string; | |
| score: number; | |
| rationale: string; | |
| is_recommended: boolean; | |
| } | |
| interface Props { | |
| projectId: string; | |
| projectContext?: any; | |
| onUpdateContext?: (matches: GrantMatchResult[]) => void; | |
| } | |
| export default function MatchingGrantsWidget({ projectId, projectContext, onUpdateContext }: Props) { | |
| const [isModalOpen, setIsModalOpen] = useState(false); | |
| // Pobierz zapisane wyniki z kontekstu | |
| const savedMatches: GrantMatchResult[] = projectContext?.ai_matches || []; | |
| // Posortuj od najwyższego wyniku i weź 3 najlepsze | |
| const displayMatches = [...savedMatches].sort((a, b) => b.score - a.score).slice(0, 3); | |
| const handleMatchesSaved = async (matches: GrantMatchResult[]) => { | |
| try { | |
| const newContext = { ...projectContext, ai_matches: matches }; | |
| await updateProject(projectId, { external_context: newContext }); | |
| if (onUpdateContext) { | |
| onUpdateContext(matches); | |
| } | |
| setIsModalOpen(false); | |
| toast.success("Zapisano dopasowane programy"); | |
| } catch (err) { | |
| console.error("Failed to save matches", err); | |
| toast.error("Wystąpił błąd podczas zapisywania"); | |
| } | |
| }; | |
| return ( | |
| <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}> | |
| {displayMatches.length > 0 ? ( | |
| <div style={{ display: 'flex', flexDirection: 'column', gap: '0.6rem' }}> | |
| {displayMatches.map((m, idx) => ( | |
| <div | |
| key={idx} | |
| style={{ | |
| background: 'rgba(255,255,255,0.025)', | |
| border: `1px solid ${m.is_recommended ? 'rgba(16, 185, 129, 0.3)' : 'rgba(255,255,255,0.06)'}`, | |
| borderRadius: 10, | |
| padding: '0.65rem 0.75rem', | |
| display: 'flex', flexDirection: 'column', gap: '0.4rem' | |
| }} | |
| > | |
| <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 6 }}> | |
| <div style={{ fontSize: '0.8rem', fontWeight: 700, color: 'var(--text-primary)', lineHeight: 1.3 }}> | |
| {m.program_name} | |
| </div> | |
| <div style={{ | |
| background: m.score >= 70 ? 'rgba(16, 185, 129, 0.1)' : 'rgba(255,255,255,0.05)', | |
| color: m.score >= 70 ? 'var(--accent-green)' : 'var(--text-secondary)', | |
| padding: '2px 6px', borderRadius: '4px', fontSize: '0.7rem', fontWeight: 700, flexShrink: 0 | |
| }}> | |
| {m.score}% Match | |
| </div> | |
| </div> | |
| <div style={{ fontSize: '0.75rem', color: 'var(--text-secondary)', lineHeight: 1.4, overflow: 'hidden', display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical' }}> | |
| {m.rationale} | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| ) : ( | |
| <div style={{ | |
| padding: '1rem', textAlign: 'center', background: 'rgba(255,255,255,0.02)', | |
| border: '1px dashed rgba(255,255,255,0.1)', borderRadius: '8px', | |
| display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '0.5rem' | |
| }}> | |
| <AlertCircle size={20} color="var(--text-muted)" /> | |
| <span style={{ color: 'var(--text-muted)', fontSize: '0.85rem' }}> | |
| Brak zapisanych rekomendacji. Uruchom AI Matcher, aby znaleźć najlepsze programy. | |
| </span> | |
| </div> | |
| )} | |
| <button | |
| onClick={() => setIsModalOpen(true)} | |
| className="btn" | |
| style={{ | |
| background: 'linear-gradient(to right, rgba(139, 92, 246, 0.15), rgba(59, 130, 246, 0.15))', | |
| border: '1px solid rgba(139,92,246,0.3)', | |
| borderRadius: 8, | |
| padding: '0.5rem 0.75rem', | |
| color: '#c4b5fd', | |
| cursor: 'pointer', | |
| fontSize: '0.8rem', | |
| fontWeight: 600, | |
| display: 'flex', | |
| alignItems: 'center', | |
| justifyContent: 'center', | |
| gap: 6, | |
| transition: 'all 0.2s', | |
| marginTop: 4, | |
| }} | |
| onMouseEnter={e => { | |
| e.currentTarget.style.background = 'linear-gradient(to right, rgba(139, 92, 246, 0.25), rgba(59, 130, 246, 0.25))'; | |
| e.currentTarget.style.borderColor = 'rgba(139,92,246,0.5)'; | |
| }} | |
| onMouseLeave={e => { | |
| e.currentTarget.style.background = 'linear-gradient(to right, rgba(139, 92, 246, 0.15), rgba(59, 130, 246, 0.15))'; | |
| e.currentTarget.style.borderColor = 'rgba(139,92,246,0.3)'; | |
| }} | |
| > | |
| <Zap size={14} fill="currentColor" /> Uruchom Zaawansowany Matcher AI | |
| </button> | |
| {isModalOpen && ( | |
| <AdvancedMatcherModal | |
| projectId={projectId} | |
| onClose={() => setIsModalOpen(false)} | |
| onMatchesSaved={handleMatchesSaved} | |
| /> | |
| )} | |
| </div> | |
| ); | |
| } | |