feat: Agregar manejo de edición de items con control de estado y validaciones

This commit is contained in:
Shni
2025-10-15 13:52:53 -05:00
parent f41ffebd76
commit 15639b876b

View File

@@ -217,6 +217,7 @@
(function(){
const guildId = '<%= selectedGuildId ? selectedGuildId : '' %>';
let cachedItems = [];
let currentOpenEditId = 0;
function setModalError(msg) {
const el = document.getElementById('modalError');
if (!el) return;
@@ -270,7 +271,8 @@
document.getElementById('propBuyPrice').value = '';
document.getElementById('propChestEnabled').checked = false;
// rewards UI reset
document.getElementById('rewardsList').innerHTML = '';
const rewardsEl = document.getElementById('rewardsList');
if (rewardsEl) { rewardsEl.innerHTML = ''; rewardsEl.dataset.rewards = JSON.stringify([]); }
document.getElementById('newRewardType').value = 'coins';
document.getElementById('newRewardAmount').value = '';
document.getElementById('newRewardItemKey').value = '';
@@ -446,14 +448,14 @@
clearModalError();
try {
// Prefer using cached items to avoid an extra network call and potential race conditions
let it = (cachedItems || []).find(x=>x.id===id);
let it = (cachedItems || []).find(x=>String(x.id) === String(id));
if (!it) {
// fallback to fetch single list if not in cache
try {
const res = await fetch(`/api/dashboard/${encodeURIComponent(guildId)}/items`);
if (res && res.ok) {
const j = await res.json();
it = (j.items || []).find(x=>x.id===id);
it = (j.items || []).find(x=>String(x.id) === String(id));
}
} catch (e) {
// ignore and continue
@@ -545,87 +547,91 @@
}
}
createBtn.addEventListener('click', ()=>{ modalTitle.textContent = 'Crear item'; showModal(false); });
cancelBtn.addEventListener('click', hideModal);
createBtn.addEventListener('click', ()=>{ clearModalError(); currentOpenEditId++; hideModal(); modalTitle.textContent = 'Crear item'; showModal(false); });
cancelBtn.addEventListener('click', ()=>{ hideModal(); clearModalError(); });
form.addEventListener('submit', async (ev)=>{
ev.preventDefault();
const id = document.getElementById('itemId').value;
// validate JSON fields before sending
// Build props object from UI
let parsedProps = {};
if (document.getElementById('propCraftable').checked) {
parsedProps.craftable = true;
const rk = document.getElementById('propRecipeKey').value.trim(); if (rk) parsedProps.recipe = { key: rk };
}
if (document.getElementById('propEquipable').checked) {
parsedProps.equipable = true;
const slot = document.getElementById('propSlot').value; if (slot) parsedProps.slot = slot;
}
// tool
const ttype = document.getElementById('propToolType').value.trim();
const ttier = document.getElementById('propToolTier').value.trim();
if (ttype) parsedProps.tool = Object.assign({}, parsedProps.tool || {}, { type: ttype });
if (ttier) parsedProps.tool = Object.assign({}, parsedProps.tool || {}, { tier: Number(ttier) });
// breakable
if (document.getElementById('propBreakable').checked) {
parsedProps.breakable = parsedProps.breakable || {};
parsedProps.breakable.enabled = true;
const dpu = document.getElementById('propDurabilityPerUse').value.trim(); if (dpu) parsedProps.breakable.durabilityPerUse = Number(dpu);
}
const attack = document.getElementById('propAttack').value.trim(); if (attack) parsedProps.attack = Number(attack);
const defense = document.getElementById('propDefense').value.trim(); if (defense) parsedProps.defense = Number(defense);
const durability = document.getElementById('propDurability').value.trim(); if (durability) parsedProps.durability = Number(durability);
const maxDur = document.getElementById('propMaxDurability').value.trim(); if (maxDur) parsedProps.maxDurability = Number(maxDur);
// usable
if (document.getElementById('propUsable').checked) {
parsedProps.usable = true;
if (document.getElementById('propPurgeAllEffects').checked) parsedProps.purgeAllEffects = true;
const heal = document.getElementById('propHealAmount').value.trim(); if (heal) parsedProps.heal = Number(heal);
}
// damage/bonuses
const dmg = document.getElementById('propDamage').value.trim(); if (dmg) parsedProps.damage = Number(dmg);
const dmgBonus = document.getElementById('propDamageBonus').value.trim(); if (dmgBonus) parsedProps.damageBonus = Number(dmgBonus);
// stats
const sAtk = document.getElementById('statAttack').value.trim(); if (sAtk) parsedProps.stats = parsedProps.stats || {}, parsedProps.stats.attack = Number(sAtk);
const sHp = document.getElementById('statHp').value.trim(); if (sHp) parsedProps.stats = parsedProps.stats || {}, parsedProps.stats.hp = Number(sHp);
const sDef = document.getElementById('statDefense').value.trim(); if (sDef) parsedProps.stats = parsedProps.stats || {}, parsedProps.stats.defense = Number(sDef);
const sXp = document.getElementById('statXpReward').value.trim(); if (sXp) parsedProps.stats = parsedProps.stats || {}, parsedProps.stats.xpReward = Number(sXp);
// requirements
if (document.getElementById('reqToolRequired').checked) {
parsedProps.requirements = parsedProps.requirements || {};
parsedProps.requirements.tool = { required: true };
const rtype = document.getElementById('reqToolType').value.trim(); if (rtype) parsedProps.requirements.tool.toolType = rtype;
const rmin = document.getElementById('reqMinTier').value.trim(); if (rmin) parsedProps.requirements.tool.minTier = Number(rmin);
}
// economy
const sell = document.getElementById('propSellPrice').value.trim(); if (sell) parsedProps.sellPrice = Number(sell);
const buy = document.getElementById('propBuyPrice').value.trim(); if (buy) parsedProps.buyPrice = Number(buy);
// chest/drops
if (document.getElementById('propChestEnabled').checked) {
parsedProps.chest = parsedProps.chest || {};
parsedProps.chest.enabled = true;
}
async function openEdit(id) {
clearModalError();
const myOpenId = ++currentOpenEditId;
try {
// prefer structured rewards list
const rawRewards = document.getElementById('rewardsList').dataset.rewards;
const rewards = rawRewards ? JSON.parse(rawRewards) : null;
if (Array.isArray(rewards)) {
parsedProps.chest = Object.assign(parsedProps.chest || {}, { rewards });
} else {
// fallback parse old textarea if present
const el = document.getElementById('propDropsJson');
if (el) {
const drops = JSON.parse(el.value || '{}');
if (drops && typeof drops === 'object') parsedProps.chest = Object.assign(parsedProps.chest || {}, { rewards: drops.rewards || drops });
// Prefer using cached items to avoid an extra network call and potential race conditions
let it = (cachedItems || []).find(x=>String(x.id) === String(id));
if (!it) {
// fallback to fetch single list if not in cache
try {
const res = await fetch(`/api/dashboard/${encodeURIComponent(guildId)}/items`);
if (res && res.ok) {
const j = await res.json();
it = (j.items || []).find(x=>String(x.id) === String(id));
}
} catch (e) {
// ignore and continue
}
}
} catch (e) { return alert('JSON inválido en Drops/Chest: ' + (e && e.message ? e.message : String(e))); }
// if another open request happened meanwhile, abort applying
if (myOpenId !== currentOpenEditId) return;
if (!it) { setModalError('Item no encontrado'); return; }
document.getElementById('itemId').value = it.id;
document.getElementById('fieldKey').value = it.key || '';
document.getElementById('fieldName').value = it.name || '';
document.getElementById('fieldCategory').value = it.category || '';
document.getElementById('fieldIcon').value = it.icon || '';
document.getElementById('fieldDescription').value = it.description || '';
document.getElementById('fieldTags').value = (Array.isArray(it.tags) ? it.tags.join(',') : '');
document.getElementById('fieldMaxPer').value = it.maxPerInventory || '';
// For security we do NOT expose stored props/metadata via the API. UI fields will be empty/default and admin can attempt raw load.
document.getElementById('propCraftable').checked = false;
document.getElementById('propRecipeKey').value = '';
document.getElementById('propEquipable').checked = false;
document.getElementById('propSlot').value = '';
document.getElementById('propAttack').value = '';
document.getElementById('propDefense').value = '';
document.getElementById('propDurability').value = '';
document.getElementById('propMaxDurability').value = '';
document.getElementById('propCustomJson').value = '{}';
document.getElementById('metaRarity').value = 'common';
document.getElementById('metaWeight').value = '';
document.getElementById('metaCustomJson').value = '{}';
// new fields defaults on edit
document.getElementById('propToolType').value = '';
document.getElementById('propToolTier').value = '';
document.getElementById('propBreakable').checked = false;
document.getElementById('propDurabilityPerUse').value = '';
document.getElementById('propUsable').checked = false;
document.getElementById('propPurgeAllEffects').checked = false;
document.getElementById('propHealAmount').value = '';
document.getElementById('propDamage').value = '';
document.getElementById('propDamageBonus').value = '';
document.getElementById('statAttack').value = '';
document.getElementById('statHp').value = '';
document.getElementById('statDefense').value = '';
document.getElementById('statXpReward').value = '';
document.getElementById('reqToolRequired').checked = false;
document.getElementById('reqToolType').value = '';
document.getElementById('reqMinTier').value = '';
document.getElementById('propSellPrice').value = '';
document.getElementById('propBuyPrice').value = '';
document.getElementById('propChestEnabled').checked = false;
document.getElementById('propDropsJson').value = '{}';
// populate rewards if we have a cached representation in the item (not exposed by default)
const rewardsEl2 = document.getElementById('rewardsList');
if (rewardsEl2) { rewardsEl2.dataset.rewards = JSON.stringify([]); }
renderRewardsList([]);
modalTitle.textContent = 'Editar item';
showModal(true);
} catch (err) {
setModalError('Error al abrir item');
}
}
}
}
} catch (e) { setModalError('JSON inválido en Drops/Chest: ' + (e && e.message ? e.message : String(e))); return; }
// merge custom JSON
try {
const custom = JSON.parse(document.getElementById('propCustomJson').value || '{}');
if (custom && typeof custom === 'object') Object.assign(parsedProps, custom);
} catch (e) { return alert('JSON inválido en Props personalizado: ' + (e && e.message ? e.message : String(e))); }
} catch (e) { setModalError('JSON inválido en Props personalizado: ' + (e && e.message ? e.message : String(e))); return; }
// Build metadata
let parsedMeta = { rarity: document.getElementById('metaRarity').value };
@@ -633,7 +639,7 @@
try {
const customM = JSON.parse(document.getElementById('metaCustomJson').value || '{}');
if (customM && typeof customM === 'object') Object.assign(parsedMeta, customM);
} catch (e) { return alert('JSON inválido en Metadata personalizado: ' + (e && e.message ? e.message : String(e))); }
} catch (e) { setModalError('JSON inválido en Metadata personalizado: ' + (e && e.message ? e.message : String(e))); return; }
const payload = {
key: document.getElementById('fieldKey').value.trim(),
@@ -672,9 +678,10 @@
if (!res.ok) throw new Error('create-failed');
}
hideModal();
clearModalError();
await fetchItems();
} catch (err) {
alert('Error al guardar item. ¿JSON válido en Props/Metadata?');
setModalError('Error al guardar item. ¿JSON válido en Props/Metadata?');
}
});
@@ -739,7 +746,7 @@
document.getElementById('metaCustomJson').value = JSON.stringify(copyM, null, 2);
}
} catch (e) {
alert('Error al mapear raw props: ' + (e && e.message ? e.message : String(e)));
setModalError('Error al mapear raw props: ' + (e && e.message ? e.message : String(e)));
}
});