feat(protese): marketplace transacional fatia 4 — avaliação, satisfação na nota e selo verificado
- A clínica avalia o laboratório (1-5★ + comentário) após a entrega (protese_avaliacao, 1 por OS, reavaliável). POST /protese/os/:id/avaliar; avaliação volta no GET detalhe. - Nota ScoreOdonto passa a combinar 0.6·operacional + 0.4·satisfação (estrelas) quando há avaliações. Selo "Verificado" (≥5 entregas). Exposto no marketplace (★nota · nº avaliações · Verificado) e nos indicadores do lab (KPI Satisfação). Helper notasScorePorProtetico unificado (dropdown + marketplace). - UI: seção "Avaliar o laboratório" na OS entregue (lado clínica); selo/avaliações no card do marketplace. - Correção de passagem: FileText/Star importados em ProteseView (FileText faltava desde a fatia 3). Validado em DEV: 6 entregas + 5★/3★ → nota 9.2, satisfação 4★, verificado (marketplace e indicadores). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { FlaskConical, Plus, X, Search, Clock, AlertTriangle, ImagePlus, ChevronRight, Trash2, RefreshCw, ShieldCheck, Tags, Pencil, Check, Package, Wallet, Camera, CheckCircle2, Circle, History, Printer, MapPin, Building2, Share2 } from 'lucide-react';
|
||||
import { FlaskConical, Plus, X, Search, Clock, AlertTriangle, ImagePlus, ChevronRight, Trash2, RefreshCw, ShieldCheck, Tags, Pencil, Check, Package, Wallet, Camera, CheckCircle2, Circle, History, Printer, MapPin, Building2, Share2, FileText, Star } from 'lucide-react';
|
||||
import { HybridBackend } from '../services/backend.ts';
|
||||
import { useToast } from '../contexts/ToastContext.tsx';
|
||||
import { useConfirm } from '../contexts/ConfirmContext.tsx';
|
||||
@@ -372,6 +372,7 @@ const OSDetailModal: React.FC<{ id: string; modo: 'bancada' | 'clinica'; onClose
|
||||
{naGarantia && modo === 'clinica' && (
|
||||
<button disabled={busy} onClick={acionarGarantia} className="w-full py-2.5 rounded-xl bg-emerald-600 text-white font-black text-sm uppercase disabled:opacity-50 flex items-center justify-center gap-2"><ShieldCheck size={16} /> Acionar garantia (refazer sem custo)</button>
|
||||
)}
|
||||
{os.status === 'entregue' && modo === 'clinica' && <AvaliacaoSecao os={os} onChanged={carregar} />}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -599,6 +600,32 @@ const CompartilharLinkSecao: React.FC<{ os: any }> = ({ os }) => {
|
||||
);
|
||||
};
|
||||
|
||||
// ── Seção: Avaliar o laboratório (após a entrega, lado clínica) ─────────────
|
||||
const AvaliacaoSecao: React.FC<{ os: any; onChanged: () => void }> = ({ os, onChanged }) => {
|
||||
const toast = useToast();
|
||||
const [estrelas, setEstrelas] = useState<number>(os.avaliacao?.estrelas || 0);
|
||||
const [coment, setComent] = useState<string>(os.avaliacao?.comentario || '');
|
||||
const [busy, setBusy] = useState(false);
|
||||
const salvar = async () => {
|
||||
if (!estrelas) { toast.error('DÊ UMA NOTA DE 1 A 5.'); return; }
|
||||
setBusy(true);
|
||||
try { await HybridBackend.avaliarProteseOS(os.id, { estrelas, comentario: coment.trim() || undefined }); toast.success('AVALIAÇÃO ENVIADA.'); onChanged(); }
|
||||
catch (e: any) { toast.error((e.message || 'ERRO').toUpperCase()); } finally { setBusy(false); }
|
||||
};
|
||||
return (
|
||||
<div className="pt-3 border-t border-gray-100">
|
||||
<p className="text-[10px] font-black text-gray-400 uppercase mb-2 flex items-center gap-1.5"><Star size={12} /> Avaliar o laboratório</p>
|
||||
<div className="flex gap-1 mb-2">
|
||||
{[1, 2, 3, 4, 5].map(n => (
|
||||
<button key={n} onClick={() => setEstrelas(n)} className={n <= estrelas ? 'text-amber-400' : 'text-gray-200'}><Star size={26} fill={n <= estrelas ? 'currentColor' : 'none'} /></button>
|
||||
))}
|
||||
</div>
|
||||
<textarea value={coment} onChange={e => setComent(e.target.value)} rows={2} placeholder="Comentário (opcional)" className="w-full p-2 bg-gray-50 border border-gray-100 rounded-lg text-xs outline-none focus:border-amber-400 resize-none" />
|
||||
<button disabled={busy} onClick={salvar} className="mt-2 px-3 py-2 rounded-lg bg-amber-500 text-white text-[11px] font-black uppercase disabled:opacity-50">{os.avaliacao ? 'Atualizar avaliação' : 'Enviar avaliação'}</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// ── Seção: Monitoramento de custódia (onde o trabalho está) ─────────────────
|
||||
const MonitoramentoSecao: React.FC<{ os: any; podeEditar: boolean; busy: boolean; onMover: (p: 'clinica' | 'laboratorio') => void }> = ({ os, podeEditar, busy, onMover }) => {
|
||||
const noLab = (os.local_atual || 'clinica') === 'laboratorio';
|
||||
|
||||
Reference in New Issue
Block a user