| import React, { useState } from 'react'; |
| import { Shield, ShieldAlert, ShieldCheck, RefreshCw, Zap } from 'lucide-react'; |
|
|
| |
| const PRESETS = { |
| legitimate: { |
| F3888: '2026-06-08 10:00:00', |
| F3886: 'Savings', |
| F3887: '170', |
| F3889: 'G365D', |
| F3890: 'R', |
| F3891: 'salaried', |
| F3892: 'M', |
| F3893: 'RETAIL', |
| F3894: '30', |
| F3895: '600', |
| F3896: '600', |
| F3897: '0' |
| }, |
| suspicious: { |
| |
| F3888: '2026-06-08 23:45:00', |
| F3886: 'Savings', |
| F3887: '999', |
| F3889: 'X999Z', |
| F3890: 'S', |
| F3891: 'unemployed', |
| F3892: 'F', |
| F3893: 'INTERNET', |
| F3894: '1', |
| F3895: '50000', |
| F3896: '100000', |
| F3897: '1' |
| } |
| }; |
|
|
| export default function ManualQueryForm({ onSubmit, modelName, threshold }) { |
| const [formData, setFormData] = useState(PRESETS.legitimate); |
| const [loading, setLoading] = useState(false); |
| const [result, setResult] = useState(null); |
|
|
| const handleInputChange = (key, value) => { |
| setFormData(prev => ({ |
| ...prev, |
| [key]: value |
| })); |
| }; |
|
|
| const loadPreset = (type) => { |
| setFormData(PRESETS[type]); |
| setResult(null); |
| }; |
|
|
| const handleSubmit = async (e) => { |
| e.preventDefault(); |
| setLoading(true); |
| setResult(null); |
| try { |
| |
| const mappedData = {}; |
| Object.keys(formData).forEach(key => { |
| const val = formData[key]; |
| if (!isNaN(val) && val.trim() !== '') { |
| mappedData[key] = val.includes('.') ? parseFloat(val) : parseInt(val); |
| } else { |
| mappedData[key] = val; |
| } |
| }); |
| |
| const res = await onSubmit(mappedData); |
| setResult(res); |
| } catch (err) { |
| console.error(err); |
| alert('Prediction query failed. Ensure backend is running.'); |
| } finally { |
| setLoading(false); |
| } |
| }; |
|
|
| return ( |
| <div className="glass-card animate-fade-in" style={{ height: '100%' }}> |
| <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}> |
| <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}> |
| <Shield style={{ color: 'var(--primary)' }} /> |
| <h2 style={{ fontSize: '1.25rem', margin: 0 }}>Real-Time Inspector</h2> |
| </div> |
| |
| <div style={{ display: 'flex', gap: '8px' }}> |
| <button |
| type="button" |
| className="btn btn-secondary" |
| style={{ padding: '6px 12px', fontSize: '0.75rem', borderColor: 'var(--success)' }} |
| onClick={() => loadPreset('legitimate')} |
| > |
| Safe Preset |
| </button> |
| <button |
| type="button" |
| className="btn btn-secondary" |
| style={{ padding: '6px 12px', fontSize: '0.75rem', borderColor: 'var(--danger)' }} |
| onClick={() => loadPreset('suspicious')} |
| > |
| Suspicious Preset |
| </button> |
| </div> |
| </div> |
| |
| <form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}> |
| |
| <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}> |
| <div> |
| <label className="form-label">Date-Time (F3888)</label> |
| <input |
| type="text" |
| className="form-input" |
| value={formData.F3888} |
| onChange={(e) => handleInputChange('F3888', e.target.value)} |
| required |
| /> |
| </div> |
| <div> |
| <label className="form-label">Account Type (F3886)</label> |
| <select |
| className="form-input" |
| value={formData.F3886} |
| onChange={(e) => handleInputChange('F3886', e.target.value)} |
| > |
| <option value="Savings">Savings</option> |
| <option value="Current">Current</option> |
| <option value="Salary">Salary</option> |
| <option value="Corporate">Corporate</option> |
| </select> |
| </div> |
| <div> |
| <label className="form-label">Channel (F3893)</label> |
| <select |
| className="form-input" |
| value={formData.F3893} |
| onChange={(e) => handleInputChange('F3893', e.target.value)} |
| > |
| <option value="RETAIL">Retail / Branch</option> |
| <option value="INTERNET">Internet Banking</option> |
| <option value="MOBILE">Mobile App</option> |
| <option value="ATM">ATM Withdrawal</option> |
| </select> |
| </div> |
| <div> |
| <label className="form-label">Job Status (F3891)</label> |
| <select |
| className="form-input" |
| value={formData.F3891} |
| onChange={(e) => handleInputChange('F3891', e.target.value)} |
| > |
| <option value="salaried">Salaried Employee</option> |
| <option value="selfemployed">Self Employed</option> |
| <option value="student">Student</option> |
| <option value="unemployed">Unemployed</option> |
| </select> |
| </div> |
| <div> |
| <label className="form-label">Tx Interval Sec (F3894)</label> |
| <input |
| type="number" |
| className="form-input" |
| value={formData.F3894} |
| onChange={(e) => handleInputChange('F3894', e.target.value)} |
| required |
| /> |
| </div> |
| <div> |
| <label className="form-label">Tx Amount (F3895)</label> |
| <input |
| type="number" |
| className="form-input" |
| value={formData.F3895} |
| onChange={(e) => handleInputChange('F3895', e.target.value)} |
| required |
| /> |
| </div> |
| <div> |
| <label className="form-label">Daily Limit (F3896)</label> |
| <input |
| type="number" |
| className="form-input" |
| value={formData.F3896} |
| onChange={(e) => handleInputChange('F3896', e.target.value)} |
| required |
| /> |
| </div> |
| <div> |
| <label className="form-label">Account State (F3890)</label> |
| <input |
| type="text" |
| className="form-input" |
| value={formData.F3890} |
| onChange={(e) => handleInputChange('F3890', e.target.value)} |
| required |
| /> |
| </div> |
| </div> |
| |
| <button |
| type="submit" |
| className="btn btn-primary" |
| style={{ width: '100%', padding: '12px', marginTop: '8px' }} |
| disabled={loading} |
| > |
| {loading ? ( |
| <> |
| <RefreshCw size={18} style={{ animation: 'spin 1.5s linear infinite' }} /> |
| Running AI Risk Check... |
| </> |
| ) : ( |
| <> |
| <Zap size={18} /> |
| Evaluate Transaction |
| </> |
| )} |
| </button> |
| </form> |
|
|
| {} |
| {result && ( |
| <div className="animate-fade-in" style={{ |
| marginTop: '20px', |
| padding: '16px', |
| borderRadius: '10px', |
| background: result.is_suspicious ? 'rgba(239, 68, 68, 0.08)' : 'rgba(16, 185, 129, 0.08)', |
| border: `1px solid ${result.is_suspicious ? 'rgba(239, 68, 68, 0.2)' : 'rgba(16, 185, 129, 0.2)'}` |
| }}> |
| <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '10px' }}> |
| <span style={{ fontSize: '0.85rem', fontWeight: 600, color: 'var(--text-secondary)' }}>DECISION RESULT</span> |
| {result.is_suspicious ? ( |
| <span className="badge badge-danger" style={{ display: 'flex', gap: '4px' }}> |
| <ShieldAlert size={12} /> SUSPICIOUS |
| </span> |
| ) : ( |
| <span className="badge badge-success" style={{ display: 'flex', gap: '4px' }}> |
| <ShieldCheck size={12} /> LEGITIMATE |
| </span> |
| )} |
| </div> |
| |
| <div style={{ display: 'flex', alignItems: 'baseline', gap: '8px', marginBottom: '8px' }}> |
| <span style={{ fontSize: '2rem', fontWeight: 800, color: result.is_suspicious ? 'var(--danger)' : 'var(--success)' }}> |
| {Math.round(result.probability * 100)}% |
| </span> |
| <span style={{ fontSize: '0.875rem', color: 'var(--text-secondary)' }}>Mule / Fraud Risk Score</span> |
| </div> |
| |
| <div style={{ fontSize: '0.8rem', color: 'var(--text-secondary)', lineHeight: '1.4' }}> |
| {result.is_suspicious ? ( |
| <strong>⚠️ Threat Warning:</strong> |
| ) : ( |
| <strong>✅ Normal activity:</strong> |
| )}{' '} |
| The system evaluated this transaction as{' '} |
| <span style={{ color: result.is_suspicious ? 'var(--danger)' : 'var(--success)', fontWeight: 600 }}> |
| {result.risk_level} Risk |
| </span>{' '} |
| using the {modelName} model at threshold {threshold}. |
| </div> |
| </div> |
| )} |
| </div> |
| ); |
| } |
|
|