feat(observation): separar observacao de imagem da observacao global do paciente (image_remark)
continuous-integration/webhook Deploy concluído (VPS4)

This commit is contained in:
VPS 4 Deploy Agent
2026-06-01 03:48:34 +02:00
parent 241a194f2a
commit d86def2f12
4 changed files with 28 additions and 24 deletions
+1
View File
@@ -94,6 +94,7 @@ async function createTables() {
// Garantir que a coluna exista caso a tabela já tenha sido criada antes // Garantir que a coluna exista caso a tabela já tenha sido criada antes
await client.query('ALTER TABLE images ADD COLUMN IF NOT EXISTS thumb_filename TEXT;'); await client.query('ALTER TABLE images ADD COLUMN IF NOT EXISTS thumb_filename TEXT;');
await client.query('ALTER TABLE images ADD COLUMN IF NOT EXISTS clinic_name VARCHAR(100);'); await client.query('ALTER TABLE images ADD COLUMN IF NOT EXISTS clinic_name VARCHAR(100);');
await client.query('ALTER TABLE images ADD COLUMN IF NOT EXISTS image_remark TEXT;');
console.log('✅ Tabela images criada/verificada no PostgreSQL'); console.log('✅ Tabela images criada/verificada no PostgreSQL');
@@ -54,7 +54,7 @@ function GtoDetailModal({ isOpen, onClose, gto, onUpdated }) {
useEffect(() => { useEffect(() => {
if (selectedImage) { if (selectedImage) {
setTempRemark(selectedImage.remark || ''); setTempRemark(selectedImage.image_remark || '');
setIsEditingRemark(false); setIsEditingRemark(false);
} }
}, [selectedImage]); }, [selectedImage]);
@@ -77,7 +77,7 @@ function GtoDetailModal({ isOpen, onClose, gto, onUpdated }) {
const ext = getExtension(img.filename); const ext = getExtension(img.filename);
const pName = (img.patient_name || 'paciente').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '_'); const pName = (img.patient_name || 'paciente').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '_');
const obs = (img.remark || '').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '_'); const obs = (img.image_remark || '').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '_');
const fileName = obs ? `${pName}-${obs}.${ext}` : `${pName}.${ext}`; const fileName = obs ? `${pName}-${obs}.${ext}` : `${pName}.${ext}`;
@@ -139,7 +139,7 @@ function GtoDetailModal({ isOpen, onClose, gto, onUpdated }) {
try { try {
const ext = getExtension(img.filename); const ext = getExtension(img.filename);
const pName = (img.patient_name || 'paciente').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '_'); const pName = (img.patient_name || 'paciente').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '_');
const obs = (img.remark || '').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '_'); const obs = (img.image_remark || '').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '_');
const fileName = obs ? `${pName}-${obs}.${ext}` : `${pName}.${ext}`; const fileName = obs ? `${pName}-${obs}.${ext}` : `${pName}.${ext}`;
@@ -153,9 +153,9 @@ function GtoDetailModal({ isOpen, onClose, gto, onUpdated }) {
const saveImageRemark = async () => { const saveImageRemark = async () => {
setSavingRemark(true); setSavingRemark(true);
try { try {
await api.put(`/images/${selectedImage.id}/remark`, { remark: tempRemark }); await api.put(`/images/${selectedImage.id}/remark`, { image_remark: tempRemark });
showToast('Observação atualizada com sucesso!', 'success'); showToast('Observação atualizada com sucesso!', 'success');
setSelectedImage(prev => ({ ...prev, remark: tempRemark })); setSelectedImage(prev => ({ ...prev, image_remark: tempRemark }));
setIsEditingRemark(false); setIsEditingRemark(false);
loadDetail(); loadDetail();
onUpdated?.(); onUpdated?.();
@@ -344,14 +344,14 @@ function GtoDetailModal({ isOpen, onClose, gto, onUpdated }) {
</div> </div>
) : ( ) : (
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12 }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12 }}>
<div style={{ fontSize: '0.95rem', color: selectedImage.remark ? 'var(--text-primary)' : 'var(--text-muted)', fontStyle: selectedImage.remark ? 'normal' : 'italic' }}> <div style={{ fontSize: '0.95rem', color: selectedImage.image_remark ? 'var(--text-primary)' : 'var(--text-muted)', fontStyle: selectedImage.image_remark ? 'normal' : 'italic' }}>
{selectedImage.remark || 'Nenhuma observação adicionada.'} {selectedImage.image_remark || 'Nenhuma observação adicionada.'}
</div> </div>
<button <button
type="button" type="button"
className="btn btn-secondary btn-small" className="btn btn-secondary btn-small"
onClick={() => { onClick={() => {
setTempRemark(selectedImage.remark || ''); setTempRemark(selectedImage.image_remark || '');
setIsEditingRemark(true); setIsEditingRemark(true);
}} }}
style={{ padding: '4px 10px', fontSize: '0.8rem', flexShrink: 0 }} style={{ padding: '4px 10px', fontSize: '0.8rem', flexShrink: 0 }}
@@ -82,11 +82,11 @@ export default function ImageDetailsPage() {
setFineTuneAngle(0); setFineTuneAngle(0);
setBaseRotation(0); setBaseRotation(0);
setBaseFlipH(false); setBaseFlipH(false);
setBaseFlipV(false); setBaseFlipV(image.flip_vertical === 1);
setChoosePage(0); setChoosePage(0);
setOrientationSubpage(0); setOrientationSubpage(0);
setStep('choose'); setStep('choose');
setRemark(image.remark || ''); // carrega remark existente se houver setRemark(image.image_remark || ''); // carrega remark existente se houver
setShowInfo(false); setShowInfo(false);
setActiveView('transform'); setActiveView('transform');
@@ -153,7 +153,7 @@ export default function ImageDetailsPage() {
const totalRotation = ((baseRotation + fineTuneAngle) % 360 + 360) % 360; const totalRotation = ((baseRotation + fineTuneAngle) % 360 + 360) % 360;
const saveTransform = async () => { const saveTransform = async () => {
if (totalRotation === 0 && !baseFlipH && !baseFlipV && remark === (image.remark || '')) { if (totalRotation === 0 && !baseFlipH && !baseFlipV && remark === (image.image_remark || '')) {
showToast('Nenhuma edição aplicada.', 'error'); showToast('Nenhuma edição aplicada.', 'error');
return; return;
} }
@@ -163,7 +163,7 @@ export default function ImageDetailsPage() {
rotation: totalRotation, rotation: totalRotation,
flipH: baseFlipH, flipH: baseFlipH,
flipV: baseFlipV, flipV: baseFlipV,
remark image_remark: remark
}); });
showToast('Imagem e orientação salvas!', 'success'); showToast('Imagem e orientação salvas!', 'success');
// Atualiza a rota com o novo ID gerado em background (sem adicionar ao histórico de navegação) // Atualiza a rota com o novo ID gerado em background (sem adicionar ao histórico de navegação)
@@ -180,7 +180,7 @@ export default function ImageDetailsPage() {
setResetting(true); setResetting(true);
try { try {
await api.post(`/images/${image.id}/transform`, { await api.post(`/images/${image.id}/transform`, {
rotation: 0, flipH: false, flipV: false, remark: '' rotation: 0, flipH: false, flipV: false, image_remark: ''
}); });
showToast('Imagem restaurada para o original!', 'success'); showToast('Imagem restaurada para o original!', 'success');
navigate('/' + encodeURIComponent(patientName)); navigate('/' + encodeURIComponent(patientName));
@@ -233,8 +233,10 @@ export default function ImageDetailsPage() {
canvas.toBlob((blob) => { canvas.toBlob((blob) => {
const a = document.createElement('a'); const a = document.createElement('a');
a.href = URL.createObjectURL(blob); a.href = URL.createObjectURL(blob);
const safe = (image.patient_name || 'rx').replace(/[^\w\-]+/g, '_'); const pName = (image.patient_name || 'paciente').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '_');
a.download = `${safe}_${applyTransform ? deg + 'deg' : 'original'}.png`; const obs = (image.image_remark || '').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '_');
const suffix = applyTransform ? `${deg}deg` : 'original';
a.download = obs ? `${pName}-${obs}_${suffix}.png` : `${pName}_${suffix}.png`;
document.body.appendChild(a); a.click(); a.remove(); document.body.appendChild(a); a.click(); a.remove();
setTimeout(() => URL.revokeObjectURL(a.href), 4000); setTimeout(() => URL.revokeObjectURL(a.href), 4000);
}, 'image/png'); }, 'image/png');
@@ -391,7 +393,7 @@ export default function ImageDetailsPage() {
{showInfo && ( {showInfo && (
<div className="extra-info-panel" style={{ marginBottom: 20 }}> <div className="extra-info-panel" style={{ marginBottom: 20 }}>
<div style={{ marginBottom: 5 }}><strong>👨 Dentista:</strong> {image.doctor || 'Não informado'}</div> <div style={{ marginBottom: 5 }}><strong>👨 Dentista:</strong> {image.doctor || 'Não informado'}</div>
<div><strong>📝 Obs:</strong> {image.remark || 'Nenhuma observação'}</div> <div><strong>📝 Obs:</strong> {image.image_remark || 'Nenhuma observação'}</div>
</div> </div>
)} )}
+9 -8
View File
@@ -366,7 +366,7 @@ router.get('/:id/transformations', async (req, res) => {
// ================================================================ // ================================================================
router.post('/:id/transform', async (req, res) => { router.post('/:id/transform', async (req, res) => {
try { try {
const { rotation, flipH, flipV, remark } = req.body; const { rotation, flipH, flipV, image_remark } = req.body;
const originalImage = await db.get('SELECT * FROM images WHERE id = $1', [req.params.id]); const originalImage = await db.get('SELECT * FROM images WHERE id = $1', [req.params.id]);
if (!originalImage) { if (!originalImage) {
@@ -396,14 +396,14 @@ router.post('/:id/transform', async (req, res) => {
// Desabilitar a imagem active atual para que não fique duplicada na interface // Desabilitar a imagem active atual para que não fique duplicada na interface
await db.run('UPDATE images SET enabled = 0 WHERE id = $1', [req.params.id]); await db.run('UPDATE images SET enabled = 0 WHERE id = $1', [req.params.id]);
const newRemark = remark !== undefined ? remark : originalImage.remark; const newImageRemark = image_remark !== undefined ? image_remark : originalImage.image_remark;
// Inserir a nova imagem como a versão ativa // Inserir a nova imagem como a versão ativa
const result = await db.run( const result = await db.run(
`INSERT INTO images ( `INSERT INTO images (
image_guid, patient_name, clinic_name, client_name, original_filename, filename, image_guid, patient_name, clinic_name, client_name, original_filename, filename,
enabled, rotation, flip_horizontal, flip_vertical, original_image_id, created_at, remark, doctor enabled, rotation, flip_horizontal, flip_vertical, original_image_id, created_at, remark, image_remark, doctor
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING id`, ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) RETURNING id`,
[ [
`${realOriginal.image_guid}_${timestamp}`, `${realOriginal.image_guid}_${timestamp}`,
realOriginal.patient_name, realOriginal.patient_name,
@@ -417,7 +417,8 @@ router.post('/:id/transform', async (req, res) => {
flipV ? 1 : 0, flipV ? 1 : 0,
realOriginalId, realOriginalId,
originalImage.created_at, // Manter a data de criação original originalImage.created_at, // Manter a data de criação original
newRemark, originalImage.remark, // Manter a observação do paciente intacta
newImageRemark, // A observação específica da imagem
realOriginal.doctor realOriginal.doctor
] ]
); );
@@ -438,10 +439,10 @@ router.post('/:id/transform', async (req, res) => {
// ================================================================ // ================================================================
router.put('/:id/remark', async (req, res) => { router.put('/:id/remark', async (req, res) => {
try { try {
const { remark } = req.body; const { image_remark } = req.body;
await db.run( await db.run(
'UPDATE images SET remark = $1 WHERE id = $2', 'UPDATE images SET image_remark = $1 WHERE id = $2',
[remark, req.params.id] [image_remark, req.params.id]
); );
res.json({ success: true, message: 'Observação da imagem atualizada!' }); res.json({ success: true, message: 'Observação da imagem atualizada!' });
} catch (error) { } catch (error) {