feat(gtos): adicionar botao abrir imagem com opcoes de baixar e copiar customizados
continuous-integration/webhook Deploy concluído (VPS4)

This commit is contained in:
VPS 4 Deploy Agent
2026-06-01 03:02:29 +02:00
parent c00c0a3646
commit 84346d455a
@@ -40,7 +40,7 @@ function StatusBadge({ sent, imageCount }) {
}
/* ── Modal de detalhes/edição de GTO ─────────────────────────── */
function GtoDetailModal({ isOpen, onClose, gto, onUpdated }) {
function GtoDetailModal({ isOpen, onClose, gto, onUpdated, onOpenImage }) {
const { showToast } = useToast();
const [detail, setDetail] = useState(null);
const [loading, setLoading] = useState(false);
@@ -142,13 +142,24 @@ function GtoDetailModal({ isOpen, onClose, gto, onUpdated }) {
<ThumbImg
src={`/api/images/${img.id}/thumbnail`}
className="preview-img"
style={{ width:'100%', height:90, objectFit:'cover', display:'block' }}
style={{ width:'100%', height:90, objectFit:'cover', display:'block', cursor:'pointer' }}
label=""
onClick={() => onOpenImage?.(img)}
/>
<div style={{ padding:'4px 6px', fontSize:'0.65rem', color:'var(--text-secondary)', whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' }}>
<div
style={{ padding:'4px 6px', fontSize:'0.65rem', color:'var(--text-secondary)', whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis', cursor:'pointer' }}
onClick={() => onOpenImage?.(img)}
>
{img.patient_name || img.filename}
</div>
<button
type="button"
title="Abrir imagem"
onClick={() => onOpenImage?.(img)}
style={{ position:'absolute', top:4, left:4, background:'rgba(59,130,246,0.85)', color:'#fff', border:'none', borderRadius:4, width:22, height:22, cursor:'pointer', fontSize:11, display:'flex', alignItems:'center', justifyContent:'center' }}
>👁</button>
<button
type="button"
title="Desvincular imagem"
onClick={() => unlinkImage(img.id)}
style={{ position:'absolute', top:4, right:4, background:'rgba(239,68,68,0.85)', color:'#fff', border:'none', borderRadius:4, width:22, height:22, cursor:'pointer', fontSize:11, display:'flex', alignItems:'center', justifyContent:'center' }}
@@ -248,6 +259,66 @@ export default function GtosPage() {
const [showSync, setShowSync] = useState(false);
const [syncTab, setSyncTab] = useState('devices');
const [showNewPatient, setShowNewPatient] = useState(false);
const [selectedImage, setSelectedImage] = useState(null);
const handleDownload = async (img) => {
try {
const url = img.filename?.startsWith('http')
? img.filename
: `/uploads/${img.filename}`;
const response = await fetch(url);
const blob = await response.blob();
// Sanitizar nome do paciente e observação
const pName = (img.patient_name || 'paciente').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '_');
const obs = (img.remark || '').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '_');
const fileName = obs ? `${pName}-${obs}.jpg` : `${pName}.jpg`;
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = fileName;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(a.href);
} catch (err) {
showToast('Erro ao baixar imagem', 'error');
}
};
const handleCopy = (img) => {
const url = img.filename?.startsWith('http')
? img.filename
: `/uploads/${img.filename}`;
const imageObj = new Image();
imageObj.crossOrigin = 'anonymous';
imageObj.onload = () => {
try {
const canvas = document.createElement('canvas');
canvas.width = imageObj.naturalWidth;
canvas.height = imageObj.naturalHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(imageObj, 0, 0);
canvas.toBlob(async (blob) => {
try {
await navigator.clipboard.write([
new ClipboardItem({ 'image/png': blob })
]);
showToast('Imagem copiada para a área de transferência!', 'success');
} catch (err) {
showToast('Erro ao copiar imagem para área de transferência.', 'error');
console.error(err);
}
}, 'image/png');
} catch (e) {
showToast('Erro ao processar imagem para cópia.', 'error');
console.error(e);
}
};
imageObj.onerror = () => showToast('Erro ao carregar imagem para cópia.', 'error');
imageObj.src = url;
};
const searchTimerRef = useRef(null);
@@ -560,12 +631,53 @@ export default function GtosPage() {
{/* Modals */}
<CreateGtoModal isOpen={showCreate} onClose={() => setShowCreate(false)} onCreated={reload} />
<GtoDetailModal isOpen={!!detailGto} gto={detailGto} onClose={() => setDetailGto(null)} onUpdated={reload} />
<GtoDetailModal isOpen={!!detailGto} gto={detailGto} onClose={() => setDetailGto(null)} onUpdated={reload} onOpenImage={(img) => setSelectedImage(img)} />
<CreatePatientModal isOpen={showNewPatient} onClose={() => setShowNewPatient(false)} />
<SettingsModal isOpen={showSettings} onClose={() => setShowSettings(false)} />
<UserManagementModal isOpen={showUsers} onClose={() => setShowUsers(false)} />
<PluginsModal isOpen={showPlugins} onClose={() => setShowPlugins(false)} />
<SyncModal isOpen={showSync} onClose={() => setShowSync(false)} defaultTab={syncTab} />
{selectedImage && (
<Modal
isOpen={!!selectedImage}
onClose={() => setSelectedImage(null)}
title={selectedImage.patient_name || 'Visualizar Imagem'}
large
>
<div style={{ display: 'flex', flexDirection: 'column', gap: 16, alignItems: 'center' }}>
<div style={{ width: '100%', background: '#0b1220', borderRadius: 8, padding: 12, display: 'flex', justifyContent: 'center' }}>
<img
src={selectedImage.filename?.startsWith('http') ? selectedImage.filename : `/uploads/${selectedImage.filename}`}
alt={selectedImage.patient_name}
style={{ maxHeight: '60vh', maxWidth: '100%', objectFit: 'contain' }}
/>
</div>
{selectedImage.remark && (
<div style={{ width: '100%', padding: '10px 14px', background: 'var(--bg-color)', borderRadius: 8, fontSize: '0.95rem' }}>
<strong>📝 Observação:</strong> {selectedImage.remark}
</div>
)}
<div style={{ display: 'flex', gap: 12, width: '100%' }}>
<button
type="button"
className="btn btn-primary"
onClick={() => handleDownload(selectedImage)}
style={{ flex: 1, height: 46, fontSize: '0.95rem', fontWeight: 600, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8 }}
>
💾 Baixar
</button>
<button
type="button"
className="btn btn-secondary"
onClick={() => handleCopy(selectedImage)}
style={{ flex: 1, height: 46, fontSize: '0.95rem', fontWeight: 600, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8 }}
>
📋 Copiar para Área de Transferência
</button>
</div>
</div>
</Modal>
)}
</>
);
}