Spaces:
Sleeping
Sleeping
| import React, { useState } from 'react'; | |
| import { AlertTriangle, CheckCircle } from 'lucide-react'; | |
| import toast from 'react-hot-toast'; | |
| import { resolveGsdHitl } from '../../api/client'; | |
| interface GsdHitlBannerProps { | |
| projectId: string; | |
| hitl: { | |
| id: string; | |
| title: string; | |
| question: string; | |
| options?: string[]; | |
| risk_level?: string; | |
| }; | |
| onResolved: () => void; | |
| } | |
| export const GsdHitlBanner: React.FC<GsdHitlBannerProps> = ({ projectId, hitl, onResolved }) => { | |
| const [isSubmitting, setIsSubmitting] = useState(false); | |
| const options = hitl.options?.length ? hitl.options : ['Tak, kontynuuj', 'Nie, wymaga poprawy']; | |
| const handleDecision = async (decision: string) => { | |
| try { | |
| setIsSubmitting(true); | |
| toast.loading('Zapisuję odpowiedź...', { id: 'gsd-hitl' }); | |
| const res = await resolveGsdHitl(projectId, hitl.id || '', decision); | |
| if (res?.status === 'error') { | |
| throw new Error(res.message || 'Błąd zapisu HITL'); | |
| } | |
| const msg = | |
| res?.approved === false | |
| ? res.message || 'Zapisano prośbę o korektę.' | |
| : res?.message || 'Odpowiedź zapisana — kontynuujemy przygotowanie wniosku.'; | |
| toast.success(msg, { id: 'gsd-hitl' }); | |
| onResolved(); | |
| // Force project + sections refresh in parent trees | |
| try { | |
| window.dispatchEvent(new Event('refresh-sections')); | |
| } catch { | |
| /* ignore */ | |
| } | |
| } catch (err: any) { | |
| const detail = err?.response?.data?.detail || err?.message; | |
| toast.error(typeof detail === 'string' ? detail : 'Nie udało się zapisać odpowiedzi.', { id: 'gsd-hitl' }); | |
| } finally { | |
| setIsSubmitting(false); | |
| } | |
| }; | |
| return ( | |
| <div style={{ | |
| margin: '0 0 1rem 0', | |
| padding: '1rem 1.25rem', | |
| borderRadius: '12px', | |
| background: 'rgba(245, 158, 11, 0.08)', | |
| border: '1px solid rgba(245, 158, 11, 0.35)', | |
| display: 'flex', | |
| flexDirection: 'column', | |
| gap: '0.75rem', | |
| }}> | |
| <div style={{ display: 'flex', alignItems: 'flex-start', gap: '0.75rem' }}> | |
| <AlertTriangle size={22} color="#f59e0b" style={{ flexShrink: 0, marginTop: 2 }} /> | |
| <div> | |
| <div style={{ fontWeight: 700, color: '#fde68a', marginBottom: '0.35rem' }}> | |
| Wymagane potwierdzenie: {hitl.title} | |
| </div> | |
| <div style={{ color: 'var(--text-secondary)', fontSize: '0.9rem', whiteSpace: 'pre-wrap' }}> | |
| {hitl.question} | |
| </div> | |
| {hitl.risk_level && ( | |
| <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)', marginTop: '0.35rem' }}> | |
| Poziom ryzyka: {hitl.risk_level} | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem', paddingLeft: '2rem' }}> | |
| {options.map((opt) => ( | |
| <button | |
| key={opt} | |
| type="button" | |
| disabled={isSubmitting} | |
| onClick={() => handleDecision(opt)} | |
| className="btn" | |
| style={{ | |
| background: opt.toLowerCase().startsWith('tak') ? 'var(--accent-green)' : 'rgba(255,255,255,0.08)', | |
| color: opt.toLowerCase().startsWith('tak') ? '#000' : '#fff', | |
| fontSize: '0.85rem', | |
| opacity: isSubmitting ? 0.6 : 1, | |
| }} | |
| > | |
| <CheckCircle size={14} style={{ marginRight: 4 }} /> | |
| {opt} | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| ); | |
| }; | |