Files
doormilxpress_astryx/src/components/krow/proving/HazardIdentifier.jsx
2026-08-07 23:53:15 +05:30

82 lines
4.3 KiB
JavaScript

import React, { useState } from 'react';
import { Trash2, Plus } from 'lucide-react';
import { Image } from '@/components/ui/image';
const PRESET_TAGS = [
'Raw meat next to ready-to-eat food',
'Same board for raw + cooked',
'Soiled towel on food surface',
'Food in the temperature danger zone',
'Unwashed hands',
'Improper storage',
'Cross-contact allergen',
];
// Interactive hazard identification — tap a kitchen photo to mark hazards.
export default function HazardIdentifier({ course, onSubmit, submitting }) {
const imageUrl = course?.challenge?.image_url;
const [markers, setMarkers] = useState([]);
const [pending, setPending] = useState(null);
const [customLabel, setCustomLabel] = useState('');
const addMarker = (e) => {
if (submitting || pending) return;
const rect = e.currentTarget.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
const y = (e.clientY - rect.top) / rect.height;
setPending({ x, y });
};
const confirm = (label) => {
if (!pending) return;
setMarkers([...markers, { ...pending, label }]);
setPending(null);
setCustomLabel('');
};
const remove = (i) => setMarkers(markers.filter((_, j) => j !== i));
return (
<div>
<p className="text-[13px] text-[#64748B] mb-3">Tap anywhere on the kitchen photo to mark a hazard. Label each one, then submit for AI evaluation.</p>
<div className="relative rounded-2xl overflow-hidden border border-[#E5E7EB] mb-3 select-none cursor-crosshair" onClick={addMarker}>
{imageUrl ? (
<Image src={imageUrl} alt="Kitchen scene" className="w-full max-h-80 object-cover" fittingType="fill" />
) : (
<div className="w-full h-64 bg-[#F1F5F9] flex items-center justify-center text-[13px] text-[#94A3B8]">No scene image configured</div>
)}
{markers.map((m, i) => (
<div key={i} className="absolute -translate-x-1/2 -translate-y-1/2 group" style={{ left: `${m.x * 100}%`, top: `${m.y * 100}%` }} onClick={(e) => e.stopPropagation()}>
<span className="flex items-center justify-center w-6 h-6 rounded-full bg-red-500 text-white text-[11px] font-bold shadow ring-2 ring-white">{i + 1}</span>
<span className="absolute left-1/2 -translate-x-1/2 top-7 whitespace-nowrap text-[10px] bg-white/95 rounded px-1.5 py-0.5 shadow text-[#0F172A]">{m.label}</span>
<button onClick={() => remove(i)} className="absolute -top-1.5 -right-1.5 w-4 h-4 rounded-full bg-white text-red-500 shadow flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity"><Trash2 className="w-2.5 h-2.5" /></button>
</div>
))}
{pending && (
<div className="absolute -translate-x-1/2 -translate-y-1/2 pointer-events-none" style={{ left: `${pending.x * 100}%`, top: `${pending.y * 100}%` }}>
<span className="block w-6 h-6 rounded-full bg-[#0A39DF] ring-4 ring-white animate-pulse" />
</div>
)}
</div>
{pending && (
<div className="rounded-xl border border-[#0A39DF] bg-[#EFF6FF] p-3 mb-3">
<p className="text-[12px] font-medium text-[#1E40AF] mb-2">Label this hazard:</p>
<div className="flex flex-wrap gap-1.5 mb-2">
{PRESET_TAGS.map((t) => (
<button key={t} onClick={() => confirm(t)} className="text-[11px] px-2.5 py-1 rounded-full bg-white border border-[#E5E7EB] text-[#374151] hover:border-[#0A39DF]">{t}</button>
))}
</div>
<div className="flex gap-2">
<input value={customLabel} onChange={(e) => setCustomLabel(e.target.value)} placeholder="Or type your own…" className="flex-1 h-8 px-2.5 text-[12px] rounded-lg border border-[#E5E7EB] focus:border-[#0A39DF] focus:outline-none" />
<button onClick={() => customLabel.trim() && confirm(customLabel.trim())} className="h-8 px-3 rounded-lg bg-[#0A39DF] text-white text-[12px] font-medium flex items-center gap-1"><Plus className="w-3.5 h-3.5" /> Add</button>
</div>
</div>
)}
<button onClick={() => onSubmit(markers)} disabled={markers.length === 0 || submitting || !!pending} className="w-full h-10 rounded-xl bg-[#0F172A] text-white text-[13px] font-semibold disabled:opacity-40">
Submit {markers.length} hazard{markers.length !== 1 ? 's' : ''} for AI evaluation
</button>
</div>
);
}