Spaces:
Sleeping
Sleeping
File size: 4,136 Bytes
ce8f04a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | 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>
);
};
|