v1.1.5 - ajout de nouvelles features.
This commit is contained in:
parent
d9f1d7cbb7
commit
0afc53fc1a
11
README.md
11
README.md
@ -70,6 +70,17 @@ dd-timer/
|
|||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
### v1.1.5
|
||||||
|
- ✨ **Onglet Accouplement** — selection des 2 parents, deduction automatique du bebe, saisie du nombre de couples et bebes obtenus pour alimenter les statistiques globales
|
||||||
|
- ✨ **Sidebar navigation** — menu hamburger avec panneau lateral overlay (Dashboard, Enclos, Accouplement, Reappro, Inventaire, Workflows, Parametres)
|
||||||
|
- ✨ **Onglet Parametres** — son d'alarme, notifications PC et mobile regroupes dans une vue dediee accessible via la sidebar
|
||||||
|
- ✨ **Reappro ♂/♀** — chaque etape de l'arbre affiche les genres necessaires (♂/♀) avec toggle d'inversion par croisement
|
||||||
|
- 🎨 Statistiques globales integrees au Dashboard (bebes, couples, taux de reussite, races obtenues)
|
||||||
|
- 🎨 Onglets horizontaux simplifies — seuls Dashboard et Enclos restent, le reste est dans la sidebar
|
||||||
|
- 🔧 Retrait du systeme de bebes des enclos — les accouplements se font desormais via l'onglet dedie
|
||||||
|
- 🔧 Migration automatique des donnees bebes existantes vers les statistiques archivees
|
||||||
|
- 🗑 Suppression de l'onglet Statistiques (donnees conservees dans le Dashboard)
|
||||||
|
|
||||||
### v1.1.4
|
### v1.1.4
|
||||||
- ✨ **Cible serenite par DD** — champ cible avec ETA en temps reel (calcul automatique baffeur/caresseur necessaire)
|
- ✨ **Cible serenite par DD** — champ cible avec ETA en temps reel (calcul automatique baffeur/caresseur necessaire)
|
||||||
- ✨ **Cible niveau par DD** — champ cible avec ETA en temps reel (modele XP lineaire base sur le tier de la mangeoire)
|
- ✨ **Cible niveau par DD** — champ cible avec ETA en temps reel (modele XP lineaire base sur le tier de la mangeoire)
|
||||||
|
|||||||
47
docs/plans/2026-03-27-v1.2.0-design.md
Normal file
47
docs/plans/2026-03-27-v1.2.0-design.md
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# Design — v1.2.0 (Accouplement, Sidebar, Réappro ♂/♀)
|
||||||
|
|
||||||
|
## 1. Sidebar navigation
|
||||||
|
|
||||||
|
Bouton hamburger (☰) fixe en haut à gauche. Panneau overlay glissant depuis la gauche par-dessus le contenu (fond semi-transparent). Se ferme au clic sur un item ou sur le fond.
|
||||||
|
|
||||||
|
Contenu :
|
||||||
|
- Dashboard
|
||||||
|
- Enclos 1 à 6 (dynamique, nom personnalisé)
|
||||||
|
- Accouplement (nouveau)
|
||||||
|
- Réappro
|
||||||
|
- Inventaire
|
||||||
|
- Workflows
|
||||||
|
|
||||||
|
La barre d'onglets horizontale actuelle reste en place.
|
||||||
|
|
||||||
|
## 2. Dashboard enrichi
|
||||||
|
|
||||||
|
Ajouts :
|
||||||
|
- **Section "Statistiques globales"** : tout ce qui est dans l'onglet Stats actuel (KPIs, bébés par race, taux de réussite, stats par enclos). L'onglet Stats est supprimé de SPECIAL_TABS.
|
||||||
|
- **Section "Paramètres"** : card dédiée en bas avec sélecteur de son, toggle notifications Windows, bouton ntfy mobile. Retiré du header dashboard.
|
||||||
|
|
||||||
|
## 3. Onglet Accouplement
|
||||||
|
|
||||||
|
Workflow :
|
||||||
|
1. Sélection Parent 1 : grille de cards filtrables par gen (image + badge gen + nom)
|
||||||
|
2. Sélection Parent 2 : filtré aux races compatibles avec Parent 1
|
||||||
|
3. Résultat : card du bébé déduit (image + gen + nom)
|
||||||
|
4. Saisie : nombre de couples + nombre de bébés obtenus
|
||||||
|
5. Bouton "Enregistrer" → alimente les stats globales
|
||||||
|
|
||||||
|
Données : `S.accouplements` — tableau de `{ parentA, parentB, baby, gen, couples, babiesObtained, date }`
|
||||||
|
|
||||||
|
Stats globales agrègent ces données comme elles agrègent actuellement `enc.babyHistory`.
|
||||||
|
|
||||||
|
## 4. Retrait des bébés des enclos
|
||||||
|
|
||||||
|
- Suppression bouton "Ajouter bébé", sous-onglets "Élevage / Historique bébés"
|
||||||
|
- Suppression de `enc.babies` et `enc.babyHistory` dans les nouveaux enclos
|
||||||
|
- Migration : les `enc.babyHistory` existants → `S.archivedStats` au premier lancement
|
||||||
|
- `isBabyUnlocked()` supprimé
|
||||||
|
|
||||||
|
## 5. Réappro ♂/♀
|
||||||
|
|
||||||
|
Chaque étape de l'arbre indique ♂ et ♀ nécessaires. Convention : Parent A = ♂, Parent B = ♀ dans BREEDING_RECIPES. Toggle pour inverser par étape.
|
||||||
|
|
||||||
|
Affichage : `×4 ♂` ou `×4 ♀` sur chaque mini-card parent. Résumé gen 1 : "4♂ Rousse + 4♀ Dorée".
|
||||||
640
docs/plans/2026-03-27-v1.2.0-implementation.md
Normal file
640
docs/plans/2026-03-27-v1.2.0-implementation.md
Normal file
@ -0,0 +1,640 @@
|
|||||||
|
# v1.2.0 Implementation Plan — Accouplement, Sidebar, Réappro ♂/♀
|
||||||
|
|
||||||
|
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||||
|
|
||||||
|
**Goal:** Ajouter un onglet Accouplement dédié, une sidebar de navigation overlay, intégrer les stats dans le dashboard, et ajouter les contraintes ♂/♀ dans la réappro.
|
||||||
|
|
||||||
|
**Architecture:** Tout le code est dans `src/index.html` (monolithique). On modifie le CSS, le HTML, et le JS inline. Pas de framework. Rendu impératif via innerHTML. État central dans `S`, persisté via `save()`.
|
||||||
|
|
||||||
|
**Tech Stack:** Vanilla JS, CSS custom properties, Electron IPC.
|
||||||
|
|
||||||
|
**Fichier principal:** `/mnt/c/Users/micka/Desktop/dd-timer/src/index.html`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1 : Retrait des bébés des enclos + migration
|
||||||
|
|
||||||
|
**But :** Supprimer le système de bébés des enclos et migrer les données existantes vers `S.archivedStats`.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/index.html`
|
||||||
|
|
||||||
|
**Step 1 : Migration des données au chargement**
|
||||||
|
|
||||||
|
Dans la fonction `load()` ou juste après le chargement de `S`, ajouter une migration one-shot :
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Migration v1.2.0 : archiver les babyHistory des enclos
|
||||||
|
if(!S._migratedBabies120){
|
||||||
|
S.enclos.forEach(enc=>{
|
||||||
|
if(enc.babyHistory&&enc.babyHistory.length){
|
||||||
|
if(!S.archivedStats)S.archivedStats={babies:[],totalMax:0};
|
||||||
|
if(!S.archivedStats.babies)S.archivedStats.babies=[];
|
||||||
|
enc.babyHistory.forEach(b=>{
|
||||||
|
S.archivedStats.babies.push({...b,enclosName:enc.name||('Enclos '+(S.enclos.indexOf(enc)+1))});
|
||||||
|
S.archivedStats.totalMax=(S.archivedStats.totalMax||0)+Math.min(5,Math.floor((b.ddCount||2)/2));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
S._migratedBabies120=true;
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2 : Supprimer le code des bébés dans les enclos**
|
||||||
|
|
||||||
|
Supprimer ou commenter les fonctions et le code suivant :
|
||||||
|
- `isBabyUnlocked()` (ligne ~2095)
|
||||||
|
- `updateBabyBtnState()` (ligne ~2101)
|
||||||
|
- `maxBabiesForEnclos()` (ligne ~2115)
|
||||||
|
- `currentBabiesCount()` (ligne ~2118)
|
||||||
|
- `openBabyModal()` (ligne ~2127)
|
||||||
|
- `closeBabyModal()` (ligne ~2134)
|
||||||
|
- `modalBack()`, `modalNext()`, `renderModalStep()` (lignes ~2138-2250)
|
||||||
|
- `selectModalGen()`, `selectModalRaceByIdx()`, `selectModalRace()`, `changeCount()` (lignes ~2250-2285)
|
||||||
|
- `renderBabies()` (ligne ~2015)
|
||||||
|
- `renderBabyHistory()` (ligne ~2049)
|
||||||
|
- `switchSubTab()` (ligne ~2001)
|
||||||
|
- `removeBaby()` (ligne ~2079)
|
||||||
|
- `clearSessionBabies()` (ligne ~2087)
|
||||||
|
- Le HTML du modal bébé (lignes ~352-369)
|
||||||
|
|
||||||
|
**Step 3 : Nettoyer renderContent pour les enclos**
|
||||||
|
|
||||||
|
Dans `renderContent()` (ligne ~1465), retirer les sous-onglets "elevage"/"history" et le bouton bébé du rendu des enclos.
|
||||||
|
|
||||||
|
**Step 4 : Commit**
|
||||||
|
|
||||||
|
```
|
||||||
|
feat: retire bébés des enclos, migre données vers archivedStats
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 2 : Onglet Accouplement — données et constante
|
||||||
|
|
||||||
|
**But :** Créer la structure de données et le lookup inversé pour les accouplements.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/index.html`
|
||||||
|
|
||||||
|
**Step 1 : Ajouter 'accouplement' dans SPECIAL_TABS**
|
||||||
|
|
||||||
|
Ligne ~698 :
|
||||||
|
```javascript
|
||||||
|
const SPECIAL_TABS=['dashboard','appro','inventaire','accouplement','workflows'];
|
||||||
|
// Note : 'stats' retiré (sera dans le dashboard)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2 : Créer le lookup inversé BREEDING_BY_PARENTS**
|
||||||
|
|
||||||
|
Après `BREEDING_RECIPES` (ligne ~677) :
|
||||||
|
```javascript
|
||||||
|
// Lookup inversé : "ParentA|ParentB" → babyRace
|
||||||
|
const BREEDING_BY_PARENTS={};
|
||||||
|
Object.entries(BREEDING_RECIPES).forEach(([baby,[a,b]])=>{
|
||||||
|
BREEDING_BY_PARENTS[a+'|'+b]=baby;
|
||||||
|
if(a!==b) BREEDING_BY_PARENTS[b+'|'+a]=baby;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Lookup : pour un parent donné, quels partenaires sont possibles ?
|
||||||
|
const COMPATIBLE_PARTNERS={};
|
||||||
|
Object.entries(BREEDING_RECIPES).forEach(([baby,[a,b]])=>{
|
||||||
|
if(!COMPATIBLE_PARTNERS[a])COMPATIBLE_PARTNERS[a]=[];
|
||||||
|
COMPATIBLE_PARTNERS[a].push({partner:b,baby,gen:RACE_GEN[baby]});
|
||||||
|
if(a!==b){
|
||||||
|
if(!COMPATIBLE_PARTNERS[b])COMPATIBLE_PARTNERS[b]=[];
|
||||||
|
COMPATIBLE_PARTNERS[b].push({partner:a,baby,gen:RACE_GEN[baby]});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3 : Initialiser S.accouplements**
|
||||||
|
|
||||||
|
Dans le chargement de S (après `load()`) :
|
||||||
|
```javascript
|
||||||
|
if(!S.accouplements) S.accouplements=[];
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 4 : Commit**
|
||||||
|
|
||||||
|
```
|
||||||
|
feat: ajoute structures données accouplement et lookup inversé
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 3 : Onglet Accouplement — rendu UI
|
||||||
|
|
||||||
|
**But :** Interface complète de l'onglet accouplement.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/index.html` (CSS + JS)
|
||||||
|
|
||||||
|
**Step 1 : CSS pour l'onglet accouplement**
|
||||||
|
|
||||||
|
Ajouter dans la section `<style>` (avant `</style>`, ligne ~313) :
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Accouplement */
|
||||||
|
.accoup-grid{display:flex;flex-wrap:wrap;gap:10px;justify-content:center}
|
||||||
|
.accoup-card{background:var(--bg2);border:2px solid var(--border);border-radius:var(--r);padding:10px;cursor:pointer;text-align:center;width:100px;transition:border-color .2s,transform .15s}
|
||||||
|
.accoup-card:hover{border-color:var(--text);transform:translateY(-2px)}
|
||||||
|
.accoup-card.selected{border-color:var(--ok);box-shadow:0 0 12px rgba(40,232,136,.25)}
|
||||||
|
.accoup-result{display:flex;align-items:center;gap:20px;justify-content:center;flex-wrap:wrap;padding:20px 0}
|
||||||
|
.accoup-arrow{font-size:2rem;color:var(--muted)}
|
||||||
|
.accoup-inputs{display:flex;gap:16px;align-items:center;justify-content:center;flex-wrap:wrap;padding:16px 0}
|
||||||
|
.accoup-input-group{display:flex;flex-direction:column;align-items:center;gap:6px}
|
||||||
|
.accoup-input-group label{font-size:.82rem;color:var(--muted)}
|
||||||
|
.accoup-input-group input{width:70px;background:var(--bg2);border:1px solid var(--border);border-radius:8px;color:var(--text);padding:8px;font:700 1rem 'Nunito',sans-serif;text-align:center}
|
||||||
|
.accoup-gen-tabs{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:12px;justify-content:center}
|
||||||
|
.accoup-gen-tab{padding:4px 12px;border-radius:8px;font-size:.78rem;font-weight:700;cursor:pointer;border:1px solid var(--border);background:var(--bg2);color:var(--muted);transition:all .2s}
|
||||||
|
.accoup-gen-tab.active{background:var(--ok);color:#fff;border-color:var(--ok)}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2 : État local de l'onglet**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const accoupState={parent1:null,parent2:null,filterGen:null,couples:'',babies:''};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3 : Fonction renderAccouplement()**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function renderAccouplement(){
|
||||||
|
const c=document.getElementById('enclos-content');
|
||||||
|
c.removeAttribute('data-enc');
|
||||||
|
|
||||||
|
const allRaces=Object.keys(RACE_GEN).sort((a,b)=>(RACE_GEN[a]-RACE_GEN[b])||a.localeCompare(b));
|
||||||
|
const gens=[...new Set(Object.values(RACE_GEN))].sort((a,b)=>a-b);
|
||||||
|
|
||||||
|
// Si parent1 sélectionné, filtrer les partenaires compatibles
|
||||||
|
let availableRaces=allRaces;
|
||||||
|
if(accoupState.parent1){
|
||||||
|
const partners=COMPATIBLE_PARTNERS[accoupState.parent1]||[];
|
||||||
|
availableRaces=partners.map(p=>p.partner);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filtrer par gen
|
||||||
|
const fg=accoupState.parent1?null:accoupState.filterGen;
|
||||||
|
const filtered=fg?availableRaces.filter(r=>RACE_GEN[r]===fg):availableRaces;
|
||||||
|
|
||||||
|
// Déduire le bébé
|
||||||
|
let baby=null,babyGen=null;
|
||||||
|
if(accoupState.parent1&&accoupState.parent2){
|
||||||
|
baby=BREEDING_BY_PARENTS[accoupState.parent1+'|'+accoupState.parent2]||null;
|
||||||
|
if(baby)babyGen=RACE_GEN[baby];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gen tabs (uniquement si pas de parent1 sélectionné)
|
||||||
|
let genTabs='';
|
||||||
|
if(!accoupState.parent1){
|
||||||
|
genTabs=`<div class="accoup-gen-tabs">
|
||||||
|
<div class="accoup-gen-tab ${!fg?'active':''}" onclick="accoupState.filterGen=null;renderAccouplement()">Toutes</div>
|
||||||
|
${gens.map(g=>`<div class="accoup-gen-tab ${fg===g?'active':''}" style="${fg===g?'background:'+GEN_COLORS[g]+';border-color:'+GEN_COLORS[g]:''}" onclick="accoupState.filterGen=${g};renderAccouplement()">Gen ${g}</div>`).join('')}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let html=`<div style="font-family:'Cinzel',serif;font-size:.8rem;letter-spacing:2.5px;text-transform:uppercase;color:var(--muted);margin-bottom:12px">Accouplement</div>`;
|
||||||
|
|
||||||
|
// Étape 1 ou 2 : sélection parent
|
||||||
|
const stepLabel=accoupState.parent1?'Sélectionne le Parent 2':'Sélectionne le Parent 1';
|
||||||
|
const selectedParent1=accoupState.parent1;
|
||||||
|
|
||||||
|
if(selectedParent1){
|
||||||
|
const gc1=GEN_COLORS[RACE_GEN[selectedParent1]]||'#888';
|
||||||
|
html+=`<div class="card" style="border-color:${gc1}">
|
||||||
|
<div style="display:flex;align-items:center;gap:12px;margin-bottom:10px">
|
||||||
|
<span class="card-title" style="margin:0">Parent 1</span>
|
||||||
|
<button class="btn btn-ghost" style="padding:4px 12px;font-size:.8rem" onclick="accoupState.parent1=null;accoupState.parent2=null;renderAccouplement()">✕ Changer</button>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;align-items:center;gap:10px">
|
||||||
|
${getDDImage(selectedParent1)}
|
||||||
|
<div>
|
||||||
|
<div style="font-weight:800;color:var(--text)">${selectedParent1}</div>
|
||||||
|
<span style="background:${gc1};color:#fff;font-size:.65rem;font-weight:800;padding:1px 6px;border-radius:8px">Gen ${RACE_GEN[selectedParent1]}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Résultat si les deux parents sont sélectionnés
|
||||||
|
if(baby){
|
||||||
|
const gcb=GEN_COLORS[babyGen]||'#888';
|
||||||
|
const gc2=GEN_COLORS[RACE_GEN[accoupState.parent2]]||'#888';
|
||||||
|
html+=`<div class="card" style="border-color:${gc2}">
|
||||||
|
<span class="card-title">Parent 2 — ${accoupState.parent2}</span>
|
||||||
|
<div style="display:flex;align-items:center;gap:10px">
|
||||||
|
${getDDImage(accoupState.parent2)}
|
||||||
|
<div>
|
||||||
|
<div style="font-weight:800;color:var(--text)">${accoupState.parent2}</div>
|
||||||
|
<span style="background:${gc2};color:#fff;font-size:.65rem;font-weight:800;padding:1px 6px;border-radius:8px">Gen ${RACE_GEN[accoupState.parent2]}</span>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-ghost" style="padding:4px 12px;font-size:.8rem" onclick="accoupState.parent2=null;renderAccouplement()">✕ Changer</button>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
html+=`<div class="card" style="border-color:${gcb}">
|
||||||
|
<span class="card-title">Bébé à naître</span>
|
||||||
|
<div class="accoup-result">
|
||||||
|
${getDDImage(accoupState.parent1)}
|
||||||
|
<span class="accoup-arrow">+</span>
|
||||||
|
${getDDImage(accoupState.parent2)}
|
||||||
|
<span class="accoup-arrow">➜</span>
|
||||||
|
<div style="text-align:center">
|
||||||
|
${getDDImage(baby)}
|
||||||
|
<div style="font-weight:800;color:var(--text);margin-top:4px">${baby}</div>
|
||||||
|
<span style="background:${gcb};color:#fff;font-size:.65rem;font-weight:800;padding:1px 6px;border-radius:8px">Gen ${babyGen}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="accoup-inputs">
|
||||||
|
<div class="accoup-input-group">
|
||||||
|
<label>Couples accouplés</label>
|
||||||
|
<input type="number" min="1" value="${accoupState.couples}" placeholder="—"
|
||||||
|
oninput="accoupState.couples=this.value"
|
||||||
|
onfocus="this.dataset.prev=this.value;this.value=''"
|
||||||
|
onblur="if(this.value==='')this.value=this.dataset.prev">
|
||||||
|
</div>
|
||||||
|
<div class="accoup-input-group">
|
||||||
|
<label>Bébés obtenus</label>
|
||||||
|
<input type="number" min="0" value="${accoupState.babies}" placeholder="—"
|
||||||
|
oninput="accoupState.babies=this.value"
|
||||||
|
onfocus="this.dataset.prev=this.value;this.value=''"
|
||||||
|
onblur="if(this.value==='')this.value=this.dataset.prev">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="text-align:center;padding-top:8px">
|
||||||
|
<button class="btn btn-start" style="padding:10px 28px;font-size:.95rem" onclick="enregistrerAccouplement()">
|
||||||
|
✅ Enregistrer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}else{
|
||||||
|
// Grille de sélection
|
||||||
|
html+=`<div class="card">
|
||||||
|
<span class="card-title">${stepLabel}</span>
|
||||||
|
${genTabs}
|
||||||
|
<div class="accoup-grid">
|
||||||
|
${filtered.map(race=>{
|
||||||
|
const gc=GEN_COLORS[RACE_GEN[race]]||'#888';
|
||||||
|
return`<div class="accoup-card" onclick="selectAccoupParent('${race.replace(/'/g,"\\'")}')" title="${race}">
|
||||||
|
${getDDImage(race)}
|
||||||
|
<div style="font-weight:700;font-size:.75rem;color:var(--text);margin-top:4px;line-height:1.1">${race}</div>
|
||||||
|
<span style="background:${gc};color:#fff;font-size:.6rem;font-weight:800;padding:1px 5px;border-radius:6px">Gen ${RACE_GEN[race]}</span>
|
||||||
|
</div>`;
|
||||||
|
}).join('')}
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
c.innerHTML=html;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 4 : Fonctions d'interaction**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function selectAccoupParent(race){
|
||||||
|
if(!accoupState.parent1){
|
||||||
|
accoupState.parent1=race;
|
||||||
|
accoupState.parent2=null;
|
||||||
|
}else{
|
||||||
|
accoupState.parent2=race;
|
||||||
|
}
|
||||||
|
accoupState.couples='';
|
||||||
|
accoupState.babies='';
|
||||||
|
renderAccouplement();
|
||||||
|
}
|
||||||
|
|
||||||
|
function enregistrerAccouplement(){
|
||||||
|
const couples=parseInt(accoupState.couples)||0;
|
||||||
|
const babies=parseInt(accoupState.babies)||0;
|
||||||
|
if(couples<=0){alert('Renseigne le nombre de couples.');return;}
|
||||||
|
const baby=BREEDING_BY_PARENTS[accoupState.parent1+'|'+accoupState.parent2];
|
||||||
|
if(!baby)return;
|
||||||
|
|
||||||
|
if(!S.accouplements)S.accouplements=[];
|
||||||
|
S.accouplements.push({
|
||||||
|
parentA:accoupState.parent1,
|
||||||
|
parentB:accoupState.parent2,
|
||||||
|
baby,
|
||||||
|
gen:RACE_GEN[baby],
|
||||||
|
couples,
|
||||||
|
babiesObtained:babies,
|
||||||
|
date:Date.now()
|
||||||
|
});
|
||||||
|
save();
|
||||||
|
|
||||||
|
// Reset
|
||||||
|
accoupState.parent1=null;
|
||||||
|
accoupState.parent2=null;
|
||||||
|
accoupState.couples='';
|
||||||
|
accoupState.babies='';
|
||||||
|
renderAccouplement();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 5 : Router dans renderContent()**
|
||||||
|
|
||||||
|
Ligne ~1465, ajouter le cas :
|
||||||
|
```javascript
|
||||||
|
if(S.activeId==='accouplement'){renderAccouplement();return;}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 6 : Ajouter l'onglet dans renderTabs()**
|
||||||
|
|
||||||
|
Dans `renderTabs()` (ligne ~1341), ajouter un onglet "Accouplement" dans les special tabs, avec icône 💑.
|
||||||
|
|
||||||
|
**Step 7 : Commit**
|
||||||
|
|
||||||
|
```
|
||||||
|
feat: ajoute onglet Accouplement avec sélection parents et enregistrement
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 4 : Intégrer les stats dans le Dashboard + section Paramètres
|
||||||
|
|
||||||
|
**But :** Déplacer les stats et les settings dans le dashboard. Supprimer l'onglet Stats.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/index.html`
|
||||||
|
|
||||||
|
**Step 1 : Retirer 'stats' de SPECIAL_TABS**
|
||||||
|
|
||||||
|
Déjà fait dans Task 2 Step 1.
|
||||||
|
|
||||||
|
**Step 2 : Modifier renderDashboard()**
|
||||||
|
|
||||||
|
Après le contenu actuel du dashboard (grille des enclos), ajouter :
|
||||||
|
|
||||||
|
1. **Section Stats globales** : Reprendre le contenu de `renderStats()` (lignes ~2306-2427) mais en tant que section du dashboard plutôt qu'onglet séparé. Adapter pour inclure les données de `S.accouplements` en plus de `S.archivedStats`.
|
||||||
|
|
||||||
|
2. **Section Paramètres** : Déplacer le sélecteur de son (lignes ~1421-1433), le toggle notifs (lignes ~1435-1449) et le bouton ntfy (lignes ~1451-1457) depuis le header de `renderTabs()` vers une card "Paramètres" en bas du dashboard.
|
||||||
|
|
||||||
|
**Calcul des stats depuis S.accouplements :**
|
||||||
|
```javascript
|
||||||
|
// Agréger accouplements dans les stats
|
||||||
|
const accoupBabies={};
|
||||||
|
let accoupTotal=0,accoupCouples=0;
|
||||||
|
(S.accouplements||[]).forEach(a=>{
|
||||||
|
accoupBabies[a.baby]=(accoupBabies[a.baby]||0)+a.babiesObtained;
|
||||||
|
accoupTotal+=a.babiesObtained;
|
||||||
|
accoupCouples+=a.couples;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3 : Retirer les settings du header dans renderTabs()**
|
||||||
|
|
||||||
|
Supprimer les lignes ~1421-1457 (sound select, notif btn, ntfy btn) de `renderTabs()`.
|
||||||
|
|
||||||
|
**Step 4 : Supprimer renderStats()**
|
||||||
|
|
||||||
|
Supprimer `renderStats()` (lignes ~2306-2427) et `resetStats()` (lignes ~2290-2304) — le code est intégré dans le dashboard.
|
||||||
|
|
||||||
|
**Step 5 : Commit**
|
||||||
|
|
||||||
|
```
|
||||||
|
feat: intègre stats et paramètres dans le dashboard, retire onglet Stats
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 5 : Sidebar navigation
|
||||||
|
|
||||||
|
**But :** Ajouter un menu hamburger avec sidebar overlay.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/index.html` (CSS + HTML + JS)
|
||||||
|
|
||||||
|
**Step 1 : CSS de la sidebar**
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Sidebar */
|
||||||
|
.sidebar-toggle{position:fixed;top:12px;left:12px;z-index:1100;background:var(--bg3);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:1.3rem;padding:6px 10px;cursor:pointer;transition:background .2s;line-height:1}
|
||||||
|
.sidebar-toggle:hover{background:var(--bg4)}
|
||||||
|
.sidebar-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:1200;opacity:0;pointer-events:none;transition:opacity .25s}
|
||||||
|
.sidebar-overlay.open{opacity:1;pointer-events:all}
|
||||||
|
.sidebar{position:fixed;top:0;left:-280px;width:270px;height:100%;background:var(--bg2);border-right:1px solid var(--border);z-index:1300;transition:left .25s;padding:60px 0 20px;overflow-y:auto;display:flex;flex-direction:column;gap:2px}
|
||||||
|
.sidebar.open{left:0}
|
||||||
|
.sidebar-item{display:flex;align-items:center;gap:10px;padding:10px 20px;color:var(--muted);font-size:.88rem;font-weight:600;cursor:pointer;transition:background .15s,color .15s;border-left:3px solid transparent}
|
||||||
|
.sidebar-item:hover{background:var(--bg3);color:var(--text)}
|
||||||
|
.sidebar-item.active{color:var(--text);border-left-color:var(--ok);background:var(--bg3)}
|
||||||
|
.sidebar-sep{height:1px;background:var(--border);margin:8px 16px}
|
||||||
|
.sidebar-label{padding:8px 20px;font-size:.7rem;font-weight:800;text-transform:uppercase;letter-spacing:1.5px;color:var(--muted);opacity:.6}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2 : HTML — ajouter le bouton et le conteneur sidebar**
|
||||||
|
|
||||||
|
Juste après `<body>` (ou avant la section `<header>`) :
|
||||||
|
```html
|
||||||
|
<button class="sidebar-toggle" onclick="toggleSidebar()" title="Menu">☰</button>
|
||||||
|
<div class="sidebar-overlay" id="sidebar-overlay" onclick="closeSidebar()"></div>
|
||||||
|
<nav class="sidebar" id="sidebar"></nav>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3 : Ajouter un padding-left au header pour ne pas chevaucher le bouton hamburger**
|
||||||
|
|
||||||
|
Ajouter `padding-left:50px` au `header` ou au conteneur principal.
|
||||||
|
|
||||||
|
**Step 4 : JS — fonctions sidebar**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function toggleSidebar(){
|
||||||
|
const sb=document.getElementById('sidebar');
|
||||||
|
const ov=document.getElementById('sidebar-overlay');
|
||||||
|
const open=sb.classList.toggle('open');
|
||||||
|
ov.classList.toggle('open',open);
|
||||||
|
if(open)renderSidebar();
|
||||||
|
}
|
||||||
|
function closeSidebar(){
|
||||||
|
document.getElementById('sidebar').classList.remove('open');
|
||||||
|
document.getElementById('sidebar-overlay').classList.remove('open');
|
||||||
|
}
|
||||||
|
function sidebarNav(id){
|
||||||
|
selectEnclos(id);
|
||||||
|
closeSidebar();
|
||||||
|
}
|
||||||
|
function renderSidebar(){
|
||||||
|
const sb=document.getElementById('sidebar');
|
||||||
|
let html=`<div class="sidebar-item ${S.activeId==='dashboard'?'active':''}" onclick="sidebarNav('dashboard')">📊 Dashboard</div>`;
|
||||||
|
html+=`<div class="sidebar-sep"></div>`;
|
||||||
|
html+=`<div class="sidebar-label">Enclos</div>`;
|
||||||
|
S.enclos.forEach((enc,i)=>{
|
||||||
|
const label=enc.name||('Enclos '+(i+1));
|
||||||
|
const isActive=S.activeId===enc.id;
|
||||||
|
html+=`<div class="sidebar-item ${isActive?'active':''}" onclick="sidebarNav(${enc.id})">🐉 ${label}</div>`;
|
||||||
|
});
|
||||||
|
html+=`<div class="sidebar-sep"></div>`;
|
||||||
|
html+=`<div class="sidebar-label">Outils</div>`;
|
||||||
|
html+=`<div class="sidebar-item ${S.activeId==='accouplement'?'active':''}" onclick="sidebarNav('accouplement')">💑 Accouplement</div>`;
|
||||||
|
html+=`<div class="sidebar-item ${S.activeId==='appro'?'active':''}" onclick="sidebarNav('appro')">🧬 Réappro</div>`;
|
||||||
|
html+=`<div class="sidebar-item ${S.activeId==='inventaire'?'active':''}" onclick="sidebarNav('inventaire')">📦 Inventaire</div>`;
|
||||||
|
html+=`<div class="sidebar-item ${S.activeId==='workflows'?'active':''}" onclick="sidebarNav('workflows')">📋 Workflows</div>`;
|
||||||
|
sb.innerHTML=html;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 5 : Commit**
|
||||||
|
|
||||||
|
```
|
||||||
|
feat: ajoute sidebar navigation overlay avec menu hamburger
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 6 : Réappro ♂/♀
|
||||||
|
|
||||||
|
**But :** Ajouter les contraintes de genre dans l'arbre de réapprovisionnement.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/index.html`
|
||||||
|
|
||||||
|
**Step 1 : Modifier calcAppro() pour assigner ♂/♀**
|
||||||
|
|
||||||
|
Dans `calcAppro()` (ligne ~2507), modifier chaque étape pour indiquer le genre :
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Convention : parentA = ♂, parentB = ♀ (sauf si inversé par l'utilisateur)
|
||||||
|
const inverted=approState.inverted||{};
|
||||||
|
const[rawA,rawB]=BREEDING_RECIPES[race];
|
||||||
|
const isInv=inverted[race];
|
||||||
|
const a=isInv?rawB:rawA, b=isInv?rawA:rawB;
|
||||||
|
// a = ♂, b = ♀
|
||||||
|
|
||||||
|
// Pour les besoins : marquer le genre
|
||||||
|
if(!needs[a])needs[a]={total:0,m:0,f:0};
|
||||||
|
if(!needs[b])needs[b]={total:0,m:0,f:0};
|
||||||
|
needs[a].m=(needs[a].m||0)+couplesReal;
|
||||||
|
needs[a].total=(needs[a].total||0)+couplesReal;
|
||||||
|
needs[b].f=(needs[b].f||0)+couplesReal;
|
||||||
|
needs[b].total=(needs[b].total||0)+couplesReal;
|
||||||
|
```
|
||||||
|
|
||||||
|
Le `needs` actuel est un simple nombre (`needs[race]=qty`). Il faut le transformer en objet `{total, m, f}`.
|
||||||
|
|
||||||
|
**Step 2 : Toggle d'inversion ♂/♀ par étape**
|
||||||
|
|
||||||
|
Ajouter dans `approState` :
|
||||||
|
```javascript
|
||||||
|
approState.inverted={}; // race → boolean
|
||||||
|
```
|
||||||
|
|
||||||
|
Fonction :
|
||||||
|
```javascript
|
||||||
|
function toggleApproGender(race){
|
||||||
|
if(!approState.inverted)approState.inverted={};
|
||||||
|
approState.inverted[race]=!approState.inverted[race];
|
||||||
|
calcAppro();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Bouton dans le rendu de chaque étape (à côté du toggle reproducteur) :
|
||||||
|
```html
|
||||||
|
<button class="btn btn-ghost" style="padding:4px 10px;font-size:.75rem"
|
||||||
|
onclick="toggleApproGender('${race}')" title="Inverser ♂/♀">
|
||||||
|
🔄 ♂↔♀
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3 : Affichage ♂/♀ sur les mini-cards**
|
||||||
|
|
||||||
|
Modifier la fonction `miniCard()` dans `calcAppro()` pour accepter un paramètre genre :
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function miniCard(name,qty,gen,gender){
|
||||||
|
const gc=GEN_COLORS[gen||RACE_GEN[name]]||'#888';
|
||||||
|
const genderBadge=gender==='m'?'<span style="color:#50a0ff;font-weight:800"> ♂</span>'
|
||||||
|
:gender==='f'?'<span style="color:#ff64a0;font-weight:800"> ♀</span>':'';
|
||||||
|
return`<div style="display:flex;flex-direction:column;align-items:center;gap:4px;min-width:70px">
|
||||||
|
<div style="position:relative">${getDDImage(name)}
|
||||||
|
<span style="position:absolute;top:-4px;right:-8px;background:${gc};color:#fff;font-size:0.65rem;font-weight:800;padding:1px 6px;border-radius:8px">Gen ${gen||RACE_GEN[name]}</span>
|
||||||
|
</div>
|
||||||
|
<span style="font-weight:800;font-size:0.78rem;color:var(--text);text-align:center;line-height:1.1;max-width:90px">${name}${genderBadge}</span>
|
||||||
|
<span style="font-family:'Cinzel',serif;font-size:1.1rem;font-weight:700;color:${gc}">×${qty}</span>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Appels : `miniCard(s.parentA, s.couples, RACE_GEN[s.parentA], 'm')` et `miniCard(s.parentB, s.couples, RACE_GEN[s.parentB], 'f')`.
|
||||||
|
|
||||||
|
**Step 4 : Résumé Gen 1 avec genres**
|
||||||
|
|
||||||
|
Modifier le rendu des matières premières Gen 1 pour afficher `×4 ♂` et `×4 ♀` séparément :
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
gen1Needs.forEach(([name,data])=>{
|
||||||
|
if(data.m>0) html+=miniCard(name,data.m,1,'m');
|
||||||
|
if(data.f>0) html+=miniCard(name,data.f,1,'f');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 5 : Commit**
|
||||||
|
|
||||||
|
```
|
||||||
|
feat: ajoute contraintes ♂/♀ dans réappro avec toggle d'inversion
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 7 : Mise à jour du rendu des tabs + nettoyage final
|
||||||
|
|
||||||
|
**But :** S'assurer que les tabs affichent correctement les nouveaux onglets et nettoyer le code mort.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/index.html`
|
||||||
|
|
||||||
|
**Step 1 : Mettre à jour renderTabs()**
|
||||||
|
|
||||||
|
Ajouter l'onglet Accouplement (💑) dans la barre horizontale, entre Dashboard et les enclos ou entre Réappro et Inventaire. S'assurer que l'onglet Stats n'apparaît plus.
|
||||||
|
|
||||||
|
**Step 2 : Mettre à jour renderContent()**
|
||||||
|
|
||||||
|
Vérifier le routing complet :
|
||||||
|
```javascript
|
||||||
|
function renderContent(){
|
||||||
|
clearElCache();
|
||||||
|
const c=document.getElementById('enclos-content');
|
||||||
|
if(S.activeId==='dashboard'){renderDashboard();return;}
|
||||||
|
if(S.activeId==='accouplement'){renderAccouplement();return;}
|
||||||
|
if(S.activeId==='appro'){renderApprovisionnement();return;}
|
||||||
|
if(S.activeId==='inventaire'){renderInventaire();return;}
|
||||||
|
if(S.activeId==='workflows'){renderWorkflows();return;}
|
||||||
|
// Sinon c'est un enclos
|
||||||
|
const enc=activeEnclos();
|
||||||
|
if(!enc)return;
|
||||||
|
renderEnclos(enc);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3 : Nettoyage**
|
||||||
|
|
||||||
|
- Supprimer tout code mort lié aux bébés dans les enclos
|
||||||
|
- Supprimer le rendu de l'onglet Stats dans renderTabs()
|
||||||
|
- Vérifier que `save()` persiste `S.accouplements`
|
||||||
|
- Vérifier que la migration `_migratedBabies120` fonctionne correctement
|
||||||
|
|
||||||
|
**Step 4 : Mettre à jour le README (version 1.2.0)**
|
||||||
|
|
||||||
|
**Step 5 : Commit**
|
||||||
|
|
||||||
|
```
|
||||||
|
feat: nettoyage final, routing mis à jour, v1.2.0 prêt
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ordre d'exécution
|
||||||
|
|
||||||
|
| Task | Dépendance | Description |
|
||||||
|
|------|-----------|-------------|
|
||||||
|
| 1 | Aucune | Retrait bébés + migration |
|
||||||
|
| 2 | Aucune | Données accouplement + lookup |
|
||||||
|
| 3 | Task 2 | UI onglet accouplement |
|
||||||
|
| 4 | Task 1 | Stats dans dashboard + paramètres |
|
||||||
|
| 5 | Aucune | Sidebar navigation |
|
||||||
|
| 6 | Aucune | Réappro ♂/♀ |
|
||||||
|
| 7 | Tasks 1-6 | Nettoyage + routing final |
|
||||||
|
|
||||||
|
Tasks 1, 2, 5, 6 sont indépendantes et peuvent être faites en parallèle.
|
||||||
|
Task 3 dépend de 2. Task 4 dépend de 1. Task 7 finalise le tout.
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "minuteur-dragodinde",
|
"name": "minuteur-dragodinde",
|
||||||
"version": "1.1.4",
|
"version": "1.1.5",
|
||||||
"description": "Minuteur elevage Dragodinde Dofus 3",
|
"description": "Minuteur elevage Dragodinde Dofus 3",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"author": "Mickael",
|
"author": "Mickael",
|
||||||
|
|||||||
945
src/index.html
945
src/index.html
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user