⚓ Comprimento + calado são usados pra calcular raio de giro no fundeio.
@@ -1965,11 +2031,24 @@ function migrateBoatsSchema(){
length:null,beam:null,draft:null,
chainTotal:null,
year:null,
+ photoId:null,
+ engineHoursInitial:null,
+ registeredAt:null,
+ registrationNumber:'',
+ notes:'',
createdAt:Date.now(),
};
state.boats.push(b);
state.activeBoatId=b.id;
}
+ // Garante campos novos em barcos existentes (forward-compat)
+ state.boats.forEach(b=>{
+ if(!('photoId' in b))b.photoId=null;
+ if(!('engineHoursInitial' in b))b.engineHoursInitial=null;
+ if(!('registeredAt' in b))b.registeredAt=null;
+ if(!('registrationNumber' in b))b.registrationNumber='';
+ if(!('notes' in b))b.notes='';
+ });
if(!state.activeBoatId&&state.boats[0])state.activeBoatId=state.boats[0].id;
if(!state.units)state.units='metric';
}
@@ -1996,6 +2075,11 @@ function addBoat(data){
draft:lenFromInput(data.draft),
chainTotal:lenFromInput(data.chainTotal),
year:data.year?parseInt(data.year):null,
+ photoId:data.photoId||null,
+ engineHoursInitial:data.engineHoursInitial?parseFloat(data.engineHoursInitial):null,
+ registeredAt:data.registeredAt||null,
+ registrationNumber:data.registrationNumber||'',
+ notes:data.notes||'',
createdAt:Date.now(),
};
state.boats.push(b);
@@ -2015,11 +2099,19 @@ function updateBoat(id,data){
if('draft' in data)b.draft=lenFromInput(data.draft);
if('chainTotal' in data)b.chainTotal=lenFromInput(data.chainTotal);
if('year' in data)b.year=data.year?parseInt(data.year):null;
+ if('photoId' in data)b.photoId=data.photoId;
+ if('engineHoursInitial' in data)b.engineHoursInitial=data.engineHoursInitial?parseFloat(data.engineHoursInitial):null;
+ if('registeredAt' in data)b.registeredAt=data.registeredAt||null;
+ if('registrationNumber' in data)b.registrationNumber=data.registrationNumber||'';
+ if('notes' in data)b.notes=data.notes||'';
saveState();
}
function removeBoat(id){
if(state.boats.length<=1){toast('Mantenha pelo menos 1 embarcação');return}
+ const b=state.boats.find(x=>x.id===id);
+ // Limpa foto do IndexedDB se houver
+ if(b?.photoId){dbDelete(b.photoId).catch(()=>{})}
state.boats=state.boats.filter(b=>b.id!==id);
if(state.activeBoatId===id)state.activeBoatId=state.boats[0]?.id||null;
saveState();
@@ -2243,16 +2335,33 @@ function renderGPSBanner(){
function bindHeader(){
const nameEl=document.getElementById('boat-name-display');
const metaEl=document.getElementById('boat-model-display');
+ const avatarEl=document.getElementById('boat-header-avatar');
if(!nameEl||!metaEl)return;
const b=activeBoat();
- if(!b){nameEl.textContent='Sem embarcação';metaEl.textContent='Toque para adicionar';return}
+ if(!b){
+ nameEl.textContent='Sem embarcação';
+ metaEl.textContent='Toque para adicionar';
+ if(avatarEl)avatarEl.innerHTML='⛵';
+ return;
+ }
nameEl.textContent=b.name||'Embarcação';
const t=BOAT_TYPES[b.type]||BOAT_TYPES.sailing;
const parts=[`${t.icon} ${t.label}`];
if(b.model)parts.push(b.model);
if(b.length)parts.push(fmtLen(b.length,1));
metaEl.textContent=parts.join(' · ');
- // sync legacy state.boat para compat com qualquer código antigo que ainda use
+ // Avatar: foto se houver, senão ícone do tipo
+ if(avatarEl){
+ avatarEl.innerHTML=t.icon;
+ if(b.photoId){
+ dbGet(b.photoId).then(item=>{
+ if(item&&activeBoat()?.id===b.id){
+ avatarEl.innerHTML=`
`;
+ }
+ }).catch(()=>{});
+ }
+ }
+ // sync legacy state.boat para compat
state.boat={name:b.name,model:b.model};
}
@@ -2277,7 +2386,7 @@ function renderFleetList(){
if(b.model)meta.push(b.model);
if(b.length)meta.push(fmtLen(b.length,1));
return `
-
${t.icon}
+
${t.icon}
${escapeHtml(b.name)}
${meta.map(escapeHtml).join(' · ')}
@@ -2286,6 +2395,20 @@ function renderFleetList(){
`;
}).join('');
+ // Carrega fotos async — não bloqueia a render
+ state.boats.forEach(b=>{if(b.photoId)loadBoatAvatarInto(`.fleet-avatar[data-boat-id="${b.id}"]`,b.photoId)});
+}
+
+async function loadBoatAvatarInto(selector,photoId){
+ if(!photoId)return;
+ try{
+ const item=await dbGet(photoId);
+ if(!item)return;
+ const url=getMediaUrl(item);
+ document.querySelectorAll(selector).forEach(el=>{
+ el.innerHTML=`

`;
+ });
+ }catch(e){}
}
function setActiveBoatAndClose(id){
@@ -2311,6 +2434,13 @@ function openBoatEditor(id){
const chainEl=document.getElementById('boat-edit-chain');
chainEl.value=b.chainTotal==null?'':lenToDisplay(b.chainTotal).toFixed(0);
document.getElementById('boat-edit-year').value=b.year||'';
+ // Novos campos
+ document.getElementById('boat-edit-engine-hours-initial').value=b.engineHoursInitial||'';
+ document.getElementById('boat-edit-registered-at').value=b.registeredAt||(isNew?new Date().toISOString().slice(0,10):'');
+ document.getElementById('boat-edit-registration').value=b.registrationNumber||'';
+ document.getElementById('boat-edit-notes').value=b.notes||'';
+ document.getElementById('boat-edit-photo-id').value=b.photoId||'';
+ renderBoatPhotoPreview(b.photoId);
// Atualizar labels de unidade
const unitLabel=`(${lengthUnit()})`;
['boat-len-unit','boat-beam-unit','boat-draft-unit','boat-chain-unit'].forEach(id=>{
@@ -2323,9 +2453,81 @@ function openBoatEditor(id){
setTimeout(()=>document.getElementById('boat-edit-name').focus(),100);
}
-function saveBoatFromForm(ev){
+async function renderBoatPhotoPreview(photoId){
+ const wrap=document.getElementById('boat-photo-preview');
+ const clearBtn=document.getElementById('boat-photo-clear');
+ if(!wrap)return;
+ if(!photoId){
+ const t=BOAT_TYPES[document.getElementById('boat-edit-type').value]||BOAT_TYPES.sailing;
+ wrap.innerHTML=`
${t.icon}`;
+ if(clearBtn)clearBtn.style.display='none';
+ return;
+ }
+ try{
+ const item=await dbGet(photoId);
+ if(!item){wrap.innerHTML='
⛵';return}
+ const url=getMediaUrl(item);
+ wrap.innerHTML=`

`;
+ if(clearBtn)clearBtn.style.display='block';
+ }catch(e){
+ wrap.innerHTML='
⛵';
+ }
+}
+
+async function handleBoatPhoto(ev){
+ const file=ev.target.files?.[0];
+ ev.target.value='';
+ if(!file)return;
+ if(!file.type.startsWith('image/')){toast('Selecione uma imagem');return}
+ // Comprime/redimensiona pra max 1280px (economia de storage e sync)
+ try{
+ const blob=await resizeImage(file,1280,0.85);
+ const oldId=document.getElementById('boat-edit-photo-id').value;
+ if(oldId){await dbDelete(oldId).catch(()=>{})}
+ const newId='boat-photo-'+uid();
+ await dbPut({id:newId,kind:'photo',blob,mime:blob.type||'image/jpeg',parentId:document.getElementById('boat-edit-id').value||'pending',parentType:'boat',createdAt:Date.now()});
+ document.getElementById('boat-edit-photo-id').value=newId;
+ await renderBoatPhotoPreview(newId);
+ toast('Foto carregada');
+ }catch(e){
+ console.warn('photo upload failed',e);
+ toast('Erro ao carregar foto');
+ }
+}
+
+async function clearBoatPhoto(){
+ const oldId=document.getElementById('boat-edit-photo-id').value;
+ if(oldId){await dbDelete(oldId).catch(()=>{})}
+ document.getElementById('boat-edit-photo-id').value='';
+ renderBoatPhotoPreview(null);
+}
+
+// Resize/compress image via canvas (mantém proporção, max maxDim, jpeg quality)
+function resizeImage(file,maxDim=1280,quality=0.85){
+ return new Promise((resolve,reject)=>{
+ const img=new Image();
+ img.onload=()=>{
+ try{
+ let{width,height}=img;
+ if(width>height){if(width>maxDim){height=Math.round(height*maxDim/width);width=maxDim}}
+ else{if(height>maxDim){width=Math.round(width*maxDim/height);height=maxDim}}
+ const canvas=document.createElement('canvas');
+ canvas.width=width;canvas.height=height;
+ const ctx=canvas.getContext('2d');
+ ctx.drawImage(img,0,0,width,height);
+ canvas.toBlob(b=>b?resolve(b):reject(new Error('toBlob null')),'image/jpeg',quality);
+ }catch(e){reject(e)}
+ finally{URL.revokeObjectURL(img.src)}
+ };
+ img.onerror=()=>{URL.revokeObjectURL(img.src);reject(new Error('image load error'))};
+ img.src=URL.createObjectURL(file);
+ });
+}
+
+async function saveBoatFromForm(ev){
ev.preventDefault();
const id=document.getElementById('boat-edit-id').value;
+ const photoId=document.getElementById('boat-edit-photo-id').value||null;
const data={
name:document.getElementById('boat-edit-name').value.trim(),
type:document.getElementById('boat-edit-type').value,
@@ -2335,10 +2537,26 @@ function saveBoatFromForm(ev){
draft:document.getElementById('boat-edit-draft').value,
chainTotal:document.getElementById('boat-edit-chain').value,
year:document.getElementById('boat-edit-year').value,
+ photoId,
+ engineHoursInitial:document.getElementById('boat-edit-engine-hours-initial').value,
+ registeredAt:document.getElementById('boat-edit-registered-at').value,
+ registrationNumber:document.getElementById('boat-edit-registration').value.trim(),
+ notes:document.getElementById('boat-edit-notes').value.trim(),
};
if(!data.name){toast('Informe o nome da embarcação');return}
+ let boatId=id;
if(id){updateBoat(id,data);toast('Embarcação atualizada')}
- else{const b=addBoat(data);state.activeBoatId=b.id;saveState();toast('Embarcação adicionada')}
+ else{const b=addBoat(data);state.activeBoatId=b.id;boatId=b.id;saveState();toast('Embarcação adicionada')}
+ // Re-vincular parentId da foto (nova embarcação tinha 'pending' como parentId)
+ if(photoId){
+ try{
+ const item=await dbGet(photoId);
+ if(item&&item.parentId!==boatId){
+ item.parentId=boatId;
+ await dbPut(item);
+ }
+ }catch(e){}
+ }
closeModal('boat-editor-modal');
bindHeader();
renderAll();
diff --git a/mobile/android/app/build.gradle b/mobile/android/app/build.gradle
index 83b7add..d22f079 100644
--- a/mobile/android/app/build.gradle
+++ b/mobile/android/app/build.gradle
@@ -7,8 +7,8 @@ android {
applicationId "br.com.pontualtech.shivao"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 4
- versionName "1.4.0"
+ versionCode 5
+ versionName "1.4.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
diff --git a/mobile/package.json b/mobile/package.json
index a5af7b0..03183e9 100644
--- a/mobile/package.json
+++ b/mobile/package.json
@@ -1,6 +1,6 @@
{
"name": "shivao-mobile",
- "version": "1.4.0",
+ "version": "1.4.1",
"description": "Shivao app nativo (Capacitor wrapper Android/iOS)",
"main": "index.js",
"type": "module",
diff --git a/server/public/index.html b/server/public/index.html
index 0aff4db..1da2e13 100644
--- a/server/public/index.html
+++ b/server/public/index.html
@@ -528,6 +528,34 @@ header{
.fleet-units-toggle button.active{background:var(--brass);color:var(--bg-paper)}
.fleet-units-toggle button:hover:not(.active){background:var(--bg-aged)}
+/* === Boat Photo === */
+.boat-photo-row{display:flex;gap:14px;align-items:flex-start}
+.boat-photo-preview{
+ width:96px;height:96px;flex-shrink:0;
+ background:var(--bg-canvas);border:1px solid var(--rule);
+ display:flex;align-items:center;justify-content:center;
+ overflow:hidden;position:relative;
+}
+.boat-photo-preview img{width:100%;height:100%;object-fit:cover;display:block}
+.boat-photo-placeholder{font-size:36px;opacity:.4}
+.boat-photo-actions{flex:1;display:flex;flex-direction:column;justify-content:center}
+
+/* Avatares pequenos (lista frota + header) */
+.fleet-avatar{
+ width:44px;height:44px;flex-shrink:0;
+ background:var(--bg-aged);border:1px solid var(--rule);
+ display:flex;align-items:center;justify-content:center;
+ overflow:hidden;font-size:22px;
+}
+.fleet-avatar img{width:100%;height:100%;object-fit:cover}
+.boat-header-avatar{
+ width:34px;height:34px;border-radius:50%;flex-shrink:0;
+ background:rgba(250,242,221,.12);border:1.5px solid rgba(250,242,221,.3);
+ overflow:hidden;display:flex;align-items:center;justify-content:center;
+ font-size:16px;color:rgba(250,242,221,.6);
+}
+.boat-header-avatar img{width:100%;height:100%;object-fit:cover}
+
/* === Anchor Calculator === */
.anchor-calc{
background:var(--bg-canvas);border:1px solid var(--rule);
@@ -1338,6 +1366,7 @@ header{
N
+
Diário de Bordo · Logbook
-
-
+
+
+
+
+
Horas do motor no dia que começou a registrar.
+
+
+
+
+
+
+
⚓ Comprimento + calado são usados pra calcular raio de giro no fundeio.
@@ -1965,11 +2031,24 @@ function migrateBoatsSchema(){
length:null,beam:null,draft:null,
chainTotal:null,
year:null,
+ photoId:null,
+ engineHoursInitial:null,
+ registeredAt:null,
+ registrationNumber:'',
+ notes:'',
createdAt:Date.now(),
};
state.boats.push(b);
state.activeBoatId=b.id;
}
+ // Garante campos novos em barcos existentes (forward-compat)
+ state.boats.forEach(b=>{
+ if(!('photoId' in b))b.photoId=null;
+ if(!('engineHoursInitial' in b))b.engineHoursInitial=null;
+ if(!('registeredAt' in b))b.registeredAt=null;
+ if(!('registrationNumber' in b))b.registrationNumber='';
+ if(!('notes' in b))b.notes='';
+ });
if(!state.activeBoatId&&state.boats[0])state.activeBoatId=state.boats[0].id;
if(!state.units)state.units='metric';
}
@@ -1996,6 +2075,11 @@ function addBoat(data){
draft:lenFromInput(data.draft),
chainTotal:lenFromInput(data.chainTotal),
year:data.year?parseInt(data.year):null,
+ photoId:data.photoId||null,
+ engineHoursInitial:data.engineHoursInitial?parseFloat(data.engineHoursInitial):null,
+ registeredAt:data.registeredAt||null,
+ registrationNumber:data.registrationNumber||'',
+ notes:data.notes||'',
createdAt:Date.now(),
};
state.boats.push(b);
@@ -2015,11 +2099,19 @@ function updateBoat(id,data){
if('draft' in data)b.draft=lenFromInput(data.draft);
if('chainTotal' in data)b.chainTotal=lenFromInput(data.chainTotal);
if('year' in data)b.year=data.year?parseInt(data.year):null;
+ if('photoId' in data)b.photoId=data.photoId;
+ if('engineHoursInitial' in data)b.engineHoursInitial=data.engineHoursInitial?parseFloat(data.engineHoursInitial):null;
+ if('registeredAt' in data)b.registeredAt=data.registeredAt||null;
+ if('registrationNumber' in data)b.registrationNumber=data.registrationNumber||'';
+ if('notes' in data)b.notes=data.notes||'';
saveState();
}
function removeBoat(id){
if(state.boats.length<=1){toast('Mantenha pelo menos 1 embarcação');return}
+ const b=state.boats.find(x=>x.id===id);
+ // Limpa foto do IndexedDB se houver
+ if(b?.photoId){dbDelete(b.photoId).catch(()=>{})}
state.boats=state.boats.filter(b=>b.id!==id);
if(state.activeBoatId===id)state.activeBoatId=state.boats[0]?.id||null;
saveState();
@@ -2243,16 +2335,33 @@ function renderGPSBanner(){
function bindHeader(){
const nameEl=document.getElementById('boat-name-display');
const metaEl=document.getElementById('boat-model-display');
+ const avatarEl=document.getElementById('boat-header-avatar');
if(!nameEl||!metaEl)return;
const b=activeBoat();
- if(!b){nameEl.textContent='Sem embarcação';metaEl.textContent='Toque para adicionar';return}
+ if(!b){
+ nameEl.textContent='Sem embarcação';
+ metaEl.textContent='Toque para adicionar';
+ if(avatarEl)avatarEl.innerHTML='⛵';
+ return;
+ }
nameEl.textContent=b.name||'Embarcação';
const t=BOAT_TYPES[b.type]||BOAT_TYPES.sailing;
const parts=[`${t.icon} ${t.label}`];
if(b.model)parts.push(b.model);
if(b.length)parts.push(fmtLen(b.length,1));
metaEl.textContent=parts.join(' · ');
- // sync legacy state.boat para compat com qualquer código antigo que ainda use
+ // Avatar: foto se houver, senão ícone do tipo
+ if(avatarEl){
+ avatarEl.innerHTML=t.icon;
+ if(b.photoId){
+ dbGet(b.photoId).then(item=>{
+ if(item&&activeBoat()?.id===b.id){
+ avatarEl.innerHTML=`
})
`;
+ }
+ }).catch(()=>{});
+ }
+ }
+ // sync legacy state.boat para compat
state.boat={name:b.name,model:b.model};
}
@@ -2277,7 +2386,7 @@ function renderFleetList(){
if(b.model)meta.push(b.model);
if(b.length)meta.push(fmtLen(b.length,1));
return `
-
${t.icon}
+
${t.icon}
${escapeHtml(b.name)}
${meta.map(escapeHtml).join(' · ')}
@@ -2286,6 +2395,20 @@ function renderFleetList(){
`;
}).join('');
+ // Carrega fotos async — não bloqueia a render
+ state.boats.forEach(b=>{if(b.photoId)loadBoatAvatarInto(`.fleet-avatar[data-boat-id="${b.id}"]`,b.photoId)});
+}
+
+async function loadBoatAvatarInto(selector,photoId){
+ if(!photoId)return;
+ try{
+ const item=await dbGet(photoId);
+ if(!item)return;
+ const url=getMediaUrl(item);
+ document.querySelectorAll(selector).forEach(el=>{
+ el.innerHTML=`

`;
+ });
+ }catch(e){}
}
function setActiveBoatAndClose(id){
@@ -2311,6 +2434,13 @@ function openBoatEditor(id){
const chainEl=document.getElementById('boat-edit-chain');
chainEl.value=b.chainTotal==null?'':lenToDisplay(b.chainTotal).toFixed(0);
document.getElementById('boat-edit-year').value=b.year||'';
+ // Novos campos
+ document.getElementById('boat-edit-engine-hours-initial').value=b.engineHoursInitial||'';
+ document.getElementById('boat-edit-registered-at').value=b.registeredAt||(isNew?new Date().toISOString().slice(0,10):'');
+ document.getElementById('boat-edit-registration').value=b.registrationNumber||'';
+ document.getElementById('boat-edit-notes').value=b.notes||'';
+ document.getElementById('boat-edit-photo-id').value=b.photoId||'';
+ renderBoatPhotoPreview(b.photoId);
// Atualizar labels de unidade
const unitLabel=`(${lengthUnit()})`;
['boat-len-unit','boat-beam-unit','boat-draft-unit','boat-chain-unit'].forEach(id=>{
@@ -2323,9 +2453,81 @@ function openBoatEditor(id){
setTimeout(()=>document.getElementById('boat-edit-name').focus(),100);
}
-function saveBoatFromForm(ev){
+async function renderBoatPhotoPreview(photoId){
+ const wrap=document.getElementById('boat-photo-preview');
+ const clearBtn=document.getElementById('boat-photo-clear');
+ if(!wrap)return;
+ if(!photoId){
+ const t=BOAT_TYPES[document.getElementById('boat-edit-type').value]||BOAT_TYPES.sailing;
+ wrap.innerHTML=`
${t.icon}`;
+ if(clearBtn)clearBtn.style.display='none';
+ return;
+ }
+ try{
+ const item=await dbGet(photoId);
+ if(!item){wrap.innerHTML='
⛵';return}
+ const url=getMediaUrl(item);
+ wrap.innerHTML=`

`;
+ if(clearBtn)clearBtn.style.display='block';
+ }catch(e){
+ wrap.innerHTML='
⛵';
+ }
+}
+
+async function handleBoatPhoto(ev){
+ const file=ev.target.files?.[0];
+ ev.target.value='';
+ if(!file)return;
+ if(!file.type.startsWith('image/')){toast('Selecione uma imagem');return}
+ // Comprime/redimensiona pra max 1280px (economia de storage e sync)
+ try{
+ const blob=await resizeImage(file,1280,0.85);
+ const oldId=document.getElementById('boat-edit-photo-id').value;
+ if(oldId){await dbDelete(oldId).catch(()=>{})}
+ const newId='boat-photo-'+uid();
+ await dbPut({id:newId,kind:'photo',blob,mime:blob.type||'image/jpeg',parentId:document.getElementById('boat-edit-id').value||'pending',parentType:'boat',createdAt:Date.now()});
+ document.getElementById('boat-edit-photo-id').value=newId;
+ await renderBoatPhotoPreview(newId);
+ toast('Foto carregada');
+ }catch(e){
+ console.warn('photo upload failed',e);
+ toast('Erro ao carregar foto');
+ }
+}
+
+async function clearBoatPhoto(){
+ const oldId=document.getElementById('boat-edit-photo-id').value;
+ if(oldId){await dbDelete(oldId).catch(()=>{})}
+ document.getElementById('boat-edit-photo-id').value='';
+ renderBoatPhotoPreview(null);
+}
+
+// Resize/compress image via canvas (mantém proporção, max maxDim, jpeg quality)
+function resizeImage(file,maxDim=1280,quality=0.85){
+ return new Promise((resolve,reject)=>{
+ const img=new Image();
+ img.onload=()=>{
+ try{
+ let{width,height}=img;
+ if(width>height){if(width>maxDim){height=Math.round(height*maxDim/width);width=maxDim}}
+ else{if(height>maxDim){width=Math.round(width*maxDim/height);height=maxDim}}
+ const canvas=document.createElement('canvas');
+ canvas.width=width;canvas.height=height;
+ const ctx=canvas.getContext('2d');
+ ctx.drawImage(img,0,0,width,height);
+ canvas.toBlob(b=>b?resolve(b):reject(new Error('toBlob null')),'image/jpeg',quality);
+ }catch(e){reject(e)}
+ finally{URL.revokeObjectURL(img.src)}
+ };
+ img.onerror=()=>{URL.revokeObjectURL(img.src);reject(new Error('image load error'))};
+ img.src=URL.createObjectURL(file);
+ });
+}
+
+async function saveBoatFromForm(ev){
ev.preventDefault();
const id=document.getElementById('boat-edit-id').value;
+ const photoId=document.getElementById('boat-edit-photo-id').value||null;
const data={
name:document.getElementById('boat-edit-name').value.trim(),
type:document.getElementById('boat-edit-type').value,
@@ -2335,10 +2537,26 @@ function saveBoatFromForm(ev){
draft:document.getElementById('boat-edit-draft').value,
chainTotal:document.getElementById('boat-edit-chain').value,
year:document.getElementById('boat-edit-year').value,
+ photoId,
+ engineHoursInitial:document.getElementById('boat-edit-engine-hours-initial').value,
+ registeredAt:document.getElementById('boat-edit-registered-at').value,
+ registrationNumber:document.getElementById('boat-edit-registration').value.trim(),
+ notes:document.getElementById('boat-edit-notes').value.trim(),
};
if(!data.name){toast('Informe o nome da embarcação');return}
+ let boatId=id;
if(id){updateBoat(id,data);toast('Embarcação atualizada')}
- else{const b=addBoat(data);state.activeBoatId=b.id;saveState();toast('Embarcação adicionada')}
+ else{const b=addBoat(data);state.activeBoatId=b.id;boatId=b.id;saveState();toast('Embarcação adicionada')}
+ // Re-vincular parentId da foto (nova embarcação tinha 'pending' como parentId)
+ if(photoId){
+ try{
+ const item=await dbGet(photoId);
+ if(item&&item.parentId!==boatId){
+ item.parentId=boatId;
+ await dbPut(item);
+ }
+ }catch(e){}
+ }
closeModal('boat-editor-modal');
bindHeader();
renderAll();
diff --git a/server/src/index.js b/server/src/index.js
index 92f8bae..b3a5d1f 100644
--- a/server/src/index.js
+++ b/server/src/index.js
@@ -264,7 +264,7 @@ app.get('/.well-known/assetlinks.json', (req, res) => {
});
// Atalho: /apk redireciona pra última APK release no Forgejo
-const LATEST_APK_URL = 'https://git.pontualtech.work/karlao/shivao-projeto/releases/download/v1.4.0/Shivao-v1.4.0.apk';
+const LATEST_APK_URL = 'https://git.pontualtech.work/karlao/shivao-projeto/releases/download/v1.4.1/Shivao-v1.4.1.apk';
app.get('/apk', (req, res) => res.redirect(302, LATEST_APK_URL));
// Página A4 imprimível com QR Code + instruções (cola no barco/marina)