feat(observation): separar observacao de imagem da observacao global do paciente (image_remark)
continuous-integration/webhook Deploy concluído (VPS4)
continuous-integration/webhook Deploy concluído (VPS4)
This commit is contained in:
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user