|
|
|
@ -569,7 +569,7 @@ const RACES_DATA={
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let S={enclos:[],activeId:null,nextEnclosId:1,alarmSound:'arpege',notifsEnabled:true,ntfyTopic:''};
|
|
|
|
let S={enclos:[],activeId:null,nextEnclosId:1,alarmSound:'arpege',notifsEnabled:true,ntfyTopic:'',archivedStats:[]};
|
|
|
|
const NTFY_BASE='https://ntfy.mickael-pol.fr';
|
|
|
|
const NTFY_BASE='https://ntfy.mickael-pol.fr';
|
|
|
|
const NTFY_REDIRECT='https://ntfy-redirect.mickael-pol.fr';
|
|
|
|
const NTFY_REDIRECT='https://ntfy-redirect.mickael-pol.fr';
|
|
|
|
const lastTickIdx={};
|
|
|
|
const lastTickIdx={};
|
|
|
|
@ -856,11 +856,22 @@ function addEnclos(){
|
|
|
|
S.enclos.push(e);S.activeId=e.id;
|
|
|
|
S.enclos.push(e);S.activeId=e.id;
|
|
|
|
save();render();addDD(e.id);
|
|
|
|
save();render();addDD(e.id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function removeEnclos(id){
|
|
|
|
async function removeEnclos(id){
|
|
|
|
if(S.enclos.length<=1)return;
|
|
|
|
if(S.enclos.length<=1)return;
|
|
|
|
|
|
|
|
const enc=S.enclos.find(e=>e.id===id);
|
|
|
|
|
|
|
|
if(!enc)return;
|
|
|
|
|
|
|
|
const ok=await window.electronAPI.showConfirm('Supprimer l\'enclos',`Supprimer "${enc.name}" ?`,'Les statistiques globales seront conservees.');
|
|
|
|
|
|
|
|
if(!ok)return;
|
|
|
|
|
|
|
|
// Archiver les stats (babyHistory) pour les conserver dans les statistiques globales
|
|
|
|
|
|
|
|
const hist=enc.babyHistory||enc.babies||[];
|
|
|
|
|
|
|
|
if(hist.length){
|
|
|
|
|
|
|
|
if(!S.archivedStats)S.archivedStats=[];
|
|
|
|
|
|
|
|
S.archivedStats.push(...hist.map(b=>({...b,fromEnclos:enc.name})));
|
|
|
|
|
|
|
|
}
|
|
|
|
S.enclos=S.enclos.filter(e=>e.id!==id);
|
|
|
|
S.enclos=S.enclos.filter(e=>e.id!==id);
|
|
|
|
if(S.activeId===id||S.activeId==='dashboard')S.activeId=S.enclos[0].id;
|
|
|
|
if(S.activeId===id||S.activeId==='dashboard')S.activeId=S.enclos[0].id;
|
|
|
|
save();render();
|
|
|
|
save();
|
|
|
|
|
|
|
|
render();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function selectEnclos(id){S.activeId=id;save();renderTabs();renderContent();}
|
|
|
|
function selectEnclos(id){S.activeId=id;save();renderTabs();renderContent();}
|
|
|
|
|
|
|
|
|
|
|
|
@ -915,20 +926,23 @@ function removeDD(encId,ddId){
|
|
|
|
enc.dragodindes=enc.dragodindes.filter(d=>d.id!==ddId);
|
|
|
|
enc.dragodindes=enc.dragodindes.filter(d=>d.id!==ddId);
|
|
|
|
save();renderDDs(enc);
|
|
|
|
save();renderDDs(enc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function viderEnclos(encId){
|
|
|
|
async function viderEnclos(encId){
|
|
|
|
const enc=S.enclos.find(e=>e.id===encId);
|
|
|
|
const enc=S.enclos.find(e=>e.id===encId);
|
|
|
|
if(!enc)return;
|
|
|
|
if(!enc)return;
|
|
|
|
if(!confirm(`Vider "${enc.name}" ?\n\nToutes les dragodindes seront supprimées.\nLes bébés et statistiques sont conservés.`))return;
|
|
|
|
const ok=await window.electronAPI.showConfirm('Vider l\'enclos',`Vider "${enc.name}" ?`,'Toutes les dragodindes seront supprimees.\nLes bebes et statistiques sont conserves.');
|
|
|
|
|
|
|
|
if(!ok)return;
|
|
|
|
enc.dragodindes=[];
|
|
|
|
enc.dragodindes=[];
|
|
|
|
enc.nextDdId=1;
|
|
|
|
enc.nextDdId=1;
|
|
|
|
enc.timer={running:false,startTime:null,pausedAt:null,pausedMs:0,snapGauges:{},snapStats:{}};
|
|
|
|
enc.timer={running:false,startTime:null,pausedAt:null,pausedMs:0,snapGauges:{},snapStats:{}};
|
|
|
|
enc.alerted={};
|
|
|
|
enc.alerted={};
|
|
|
|
enc.activeGauges=[];
|
|
|
|
enc.activeGauges=[];
|
|
|
|
Object.keys(enc.gaugeLevels).forEach(k=>{enc.gaugeLevels[k]=0;});
|
|
|
|
Object.keys(enc.gaugeLevels).forEach(k=>{enc.gaugeLevels[k]=0;});
|
|
|
|
// babies session vide mais babyHistory intact
|
|
|
|
|
|
|
|
enc.babies=[];
|
|
|
|
enc.babies=[];
|
|
|
|
lockInputs(encId,false);
|
|
|
|
// Ajouter une DD par défaut
|
|
|
|
save();renderContent();
|
|
|
|
const newId=enc.nextDdId++;
|
|
|
|
|
|
|
|
enc.dragodindes.push({id:newId,name:`Dragodinde ${newId}`,stats:{serenite:0,endurance:0,maturite:0,amour:0,xp:1},targets:{...DEFAULT_TARGETS}});
|
|
|
|
|
|
|
|
save();
|
|
|
|
|
|
|
|
render();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function updateName(encId,ddId,v){
|
|
|
|
function updateName(encId,ddId,v){
|
|
|
|
const enc=S.enclos.find(e=>e.id===encId);
|
|
|
|
const enc=S.enclos.find(e=>e.id===encId);
|
|
|
|
@ -956,18 +970,22 @@ function updateStatDirect(encId,ddId,stat,v){
|
|
|
|
save();updateLive();
|
|
|
|
save();updateLive();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function nouvelleFournee(encId){
|
|
|
|
async function nouvelleFournee(encId){
|
|
|
|
const enc=S.enclos.find(e=>e.id===encId);
|
|
|
|
const enc=S.enclos.find(e=>e.id===encId);
|
|
|
|
if(!enc)return;
|
|
|
|
if(!enc)return;
|
|
|
|
if(!confirm(`Nouvelle fournée pour ${enc.name} ?\n\nLes dragodindes actuelles seront supprimées.\nLes bébés et l'historique sont conservés.`))return;
|
|
|
|
const ok=await window.electronAPI.showConfirm('Nouvelle fournee',`Nouvelle fournee pour ${enc.name} ?`,'Les dragodindes actuelles seront supprimees.\nLes bebes et l\'historique sont conserves.');
|
|
|
|
|
|
|
|
if(!ok)return;
|
|
|
|
enc.dragodindes=[];
|
|
|
|
enc.dragodindes=[];
|
|
|
|
enc.nextDdId=1;
|
|
|
|
enc.nextDdId=1;
|
|
|
|
enc.timer={running:false,startTime:null,pausedAt:null,pausedMs:0,snapGauges:{},snapStats:{}};
|
|
|
|
enc.timer={running:false,startTime:null,pausedAt:null,pausedMs:0,snapGauges:{},snapStats:{}};
|
|
|
|
enc.alerted={};
|
|
|
|
enc.alerted={};
|
|
|
|
enc.activeGauges=[];
|
|
|
|
enc.activeGauges=[];
|
|
|
|
Object.keys(enc.gaugeLevels).forEach(k=>{enc.gaugeLevels[k]=0;});
|
|
|
|
Object.keys(enc.gaugeLevels).forEach(k=>{enc.gaugeLevels[k]=0;});
|
|
|
|
enc.babies=[];
|
|
|
|
// Ajouter une DD par défaut
|
|
|
|
save();renderContent();
|
|
|
|
const newId=enc.nextDdId++;
|
|
|
|
|
|
|
|
enc.dragodindes.push({id:newId,name:`Dragodinde ${newId}`,stats:{serenite:0,endurance:0,maturite:0,amour:0,xp:1},targets:{...DEFAULT_TARGETS}});
|
|
|
|
|
|
|
|
save();
|
|
|
|
|
|
|
|
render();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function updateStat(encId,ddId,gid,v){
|
|
|
|
function updateStat(encId,ddId,gid,v){
|
|
|
|
const enc=S.enclos.find(e=>e.id===encId);
|
|
|
|
const enc=S.enclos.find(e=>e.id===encId);
|
|
|
|
@ -1070,6 +1088,7 @@ async function load(){
|
|
|
|
S.alarmSound=d.alarmSound||'arpege';
|
|
|
|
S.alarmSound=d.alarmSound||'arpege';
|
|
|
|
S.notifsEnabled=d.notifsEnabled!==undefined?d.notifsEnabled:true;
|
|
|
|
S.notifsEnabled=d.notifsEnabled!==undefined?d.notifsEnabled:true;
|
|
|
|
S.ntfyTopic=d.ntfyTopic||'';
|
|
|
|
S.ntfyTopic=d.ntfyTopic||'';
|
|
|
|
|
|
|
|
S.archivedStats=d.archivedStats||[];
|
|
|
|
// Migration: ancien format ntfyUrl → ntfyTopic
|
|
|
|
// Migration: ancien format ntfyUrl → ntfyTopic
|
|
|
|
if(!S.ntfyTopic&&d.ntfyUrl){const m=d.ntfyUrl.match(/\/([^\/]+)$/);if(m)S.ntfyTopic=m[1];}
|
|
|
|
if(!S.ntfyTopic&&d.ntfyUrl){const m=d.ntfyUrl.match(/\/([^\/]+)$/);if(m)S.ntfyTopic=m[1];}
|
|
|
|
S.enclos.forEach(enc=>{
|
|
|
|
S.enclos.forEach(enc=>{
|
|
|
|
@ -1315,6 +1334,8 @@ function renderGaugesCfg(enc){
|
|
|
|
<input type="number" class="gauge-inp" min="0" max="100000" step="1000"
|
|
|
|
<input type="number" class="gauge-inp" min="0" max="100000" step="1000"
|
|
|
|
value="${lvl}" style="border-color:${color}44"
|
|
|
|
value="${lvl}" style="border-color:${color}44"
|
|
|
|
oninput="updateGaugeLevel(${enc.id},'${gid}',this.value)"
|
|
|
|
oninput="updateGaugeLevel(${enc.id},'${gid}',this.value)"
|
|
|
|
|
|
|
|
onfocus="this.dataset.prev=this.value;this.value=''"
|
|
|
|
|
|
|
|
onblur="if(this.value==='')this.value=this.dataset.prev"
|
|
|
|
${enc.timer.running?'disabled':''}>
|
|
|
|
${enc.timer.running?'disabled':''}>
|
|
|
|
<span style="color:var(--muted);font-size:11px">/ 100 000</span>
|
|
|
|
<span style="color:var(--muted);font-size:11px">/ 100 000</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
@ -1390,6 +1411,8 @@ function renderDDs(enc){
|
|
|
|
<input type="number" id="pstat-${enc.id}-${dd.id}-${p.stat}"
|
|
|
|
<input type="number" id="pstat-${enc.id}-${dd.id}-${p.stat}"
|
|
|
|
value="${val}" min="${p.min}" max="${p.max}" step="${p.stat==='xp'?1:10}"
|
|
|
|
value="${val}" min="${p.min}" max="${p.max}" step="${p.stat==='xp'?1:10}"
|
|
|
|
oninput="updateStatDirect(${enc.id},${dd.id},'${p.stat}',this.value)"
|
|
|
|
oninput="updateStatDirect(${enc.id},${dd.id},'${p.stat}',this.value)"
|
|
|
|
|
|
|
|
onfocus="this.dataset.prev=this.value;this.value=''"
|
|
|
|
|
|
|
|
onblur="if(this.value==='')this.value=this.dataset.prev"
|
|
|
|
title="${p.stat}">
|
|
|
|
title="${p.stat}">
|
|
|
|
</div>`;
|
|
|
|
</div>`;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
@ -1987,17 +2010,41 @@ function changeCount(delta){
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
// STATS TAB
|
|
|
|
// STATS TAB
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
|
|
|
|
async function resetStats(){
|
|
|
|
|
|
|
|
const ok=await window.electronAPI.showConfirm(
|
|
|
|
|
|
|
|
'Reinitialiser les statistiques',
|
|
|
|
|
|
|
|
'Reinitialiser toutes les statistiques ?',
|
|
|
|
|
|
|
|
'Cette action est irreversible.\nTous les bebes enregistres, l\'historique de chaque enclos et les statistiques archivees seront definitivement supprimes.'
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
if(!ok)return;
|
|
|
|
|
|
|
|
S.archivedStats=[];
|
|
|
|
|
|
|
|
S.enclos.forEach(enc=>{
|
|
|
|
|
|
|
|
enc.babies=[];
|
|
|
|
|
|
|
|
enc.babyHistory=[];
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
save();
|
|
|
|
|
|
|
|
render();
|
|
|
|
|
|
|
|
}
|
|
|
|
function renderStats(){
|
|
|
|
function renderStats(){
|
|
|
|
const c=document.getElementById('enclos-content');
|
|
|
|
const c=document.getElementById('enclos-content');
|
|
|
|
c.removeAttribute('data-enc');
|
|
|
|
c.removeAttribute('data-enc');
|
|
|
|
|
|
|
|
|
|
|
|
// Agréger toutes les données
|
|
|
|
// Agréger toutes les données (enclos actifs + stats archivées des enclos supprimés)
|
|
|
|
let totalDD=0,totalBabies=0,totalSessions=0;
|
|
|
|
let totalDD=0,totalBabies=0,totalSessions=0;
|
|
|
|
const raceMap={};
|
|
|
|
const raceMap={};
|
|
|
|
const enclosStats=[];
|
|
|
|
const enclosStats=[];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Stats archivées (enclos supprimés)
|
|
|
|
|
|
|
|
const archived=S.archivedStats||[];
|
|
|
|
|
|
|
|
archived.forEach(b=>{
|
|
|
|
|
|
|
|
if(!raceMap[b.race])raceMap[b.race]={count:0,gen:b.gen};
|
|
|
|
|
|
|
|
raceMap[b.race].count+=b.count;
|
|
|
|
|
|
|
|
totalBabies+=b.count;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
const archivedMax=archived.reduce((s,b)=>s+Math.min(5,Math.floor((b.ddCount||1)/2)),0);
|
|
|
|
|
|
|
|
|
|
|
|
S.enclos.forEach(enc=>{
|
|
|
|
S.enclos.forEach(enc=>{
|
|
|
|
const babies=(enc.babyHistory||enc.babies||[]); // utilise l'historique permanent
|
|
|
|
const babies=(enc.babyHistory||enc.babies||[]);
|
|
|
|
const nbBabies=babies.reduce((s,b)=>s+b.count,0);
|
|
|
|
const nbBabies=babies.reduce((s,b)=>s+b.count,0);
|
|
|
|
const nbDD=enc.dragodindes.length;
|
|
|
|
const nbDD=enc.dragodindes.length;
|
|
|
|
totalDD+=nbDD;
|
|
|
|
totalDD+=nbDD;
|
|
|
|
@ -2011,8 +2058,6 @@ function renderStats(){
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if(nbBabies>0){
|
|
|
|
if(nbBabies>0){
|
|
|
|
// Calcul taux de réussite basé sur le ddCount stocké lors de l'ajout
|
|
|
|
|
|
|
|
// → résiste à la suppression/nouvelle fournée de DD
|
|
|
|
|
|
|
|
const maxPossible=babies.reduce((s,b)=>{
|
|
|
|
const maxPossible=babies.reduce((s,b)=>{
|
|
|
|
const m=Math.min(5,Math.floor((b.ddCount||1)/2));
|
|
|
|
const m=Math.min(5,Math.floor((b.ddCount||1)/2));
|
|
|
|
return s+m;
|
|
|
|
return s+m;
|
|
|
|
@ -2022,8 +2067,8 @@ function renderStats(){
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Taux global basé sur les ddCount stockés
|
|
|
|
// Taux global (enclos actifs + archivés)
|
|
|
|
const totalMax=S.enclos.reduce((s,e)=>{
|
|
|
|
const totalMax=archivedMax+S.enclos.reduce((s,e)=>{
|
|
|
|
const hist=(e.babyHistory||e.babies||[]);
|
|
|
|
const hist=(e.babyHistory||e.babies||[]);
|
|
|
|
return s+hist.reduce((ss,b)=>ss+Math.min(5,Math.floor((b.ddCount||1)/2)),0);
|
|
|
|
return s+hist.reduce((ss,b)=>ss+Math.min(5,Math.floor((b.ddCount||1)/2)),0);
|
|
|
|
},0);
|
|
|
|
},0);
|
|
|
|
@ -2034,8 +2079,16 @@ function renderStats(){
|
|
|
|
const maxRaceCount=racesSorted[0]?.[1].count||1;
|
|
|
|
const maxRaceCount=racesSorted[0]?.[1].count||1;
|
|
|
|
|
|
|
|
|
|
|
|
c.innerHTML=`
|
|
|
|
c.innerHTML=`
|
|
|
|
<div style="font-family:'Cinzel',serif;font-size:0.8rem;letter-spacing:2.5px;text-transform:uppercase;color:var(--muted);margin-bottom:6px">
|
|
|
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:6px">
|
|
|
|
Statistiques globales
|
|
|
|
<div style="font-family:'Cinzel',serif;font-size:0.8rem;letter-spacing:2.5px;text-transform:uppercase;color:var(--muted)">
|
|
|
|
|
|
|
|
Statistiques globales
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<button onclick="resetStats()" style="padding:6px 14px;border-radius:8px;border:1px solid var(--border);background:transparent;color:var(--muted);cursor:pointer;font:700 0.82rem 'Nunito',sans-serif;transition:.15s"
|
|
|
|
|
|
|
|
onmouseover="this.style.borderColor='var(--amour)';this.style.color='var(--amour)'"
|
|
|
|
|
|
|
|
onmouseout="this.style.borderColor='var(--border)';this.style.color='var(--muted)'"
|
|
|
|
|
|
|
|
title="Remettre toutes les statistiques a zero">
|
|
|
|
|
|
|
|
🗑 Reinitialiser les statistiques
|
|
|
|
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="stats-wrap">
|
|
|
|
<div class="stats-wrap">
|
|
|
|
<div class="stats-section">
|
|
|
|
<div class="stats-section">
|
|
|
|
|