feat: Agregar manejo de edición de items con control de estado y validaciones
This commit is contained in:
@@ -217,6 +217,7 @@
|
|||||||
(function(){
|
(function(){
|
||||||
const guildId = '<%= selectedGuildId ? selectedGuildId : '' %>';
|
const guildId = '<%= selectedGuildId ? selectedGuildId : '' %>';
|
||||||
let cachedItems = [];
|
let cachedItems = [];
|
||||||
|
let currentOpenEditId = 0;
|
||||||
function setModalError(msg) {
|
function setModalError(msg) {
|
||||||
const el = document.getElementById('modalError');
|
const el = document.getElementById('modalError');
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
@@ -270,7 +271,8 @@
|
|||||||
document.getElementById('propBuyPrice').value = '';
|
document.getElementById('propBuyPrice').value = '';
|
||||||
document.getElementById('propChestEnabled').checked = false;
|
document.getElementById('propChestEnabled').checked = false;
|
||||||
// rewards UI reset
|
// 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('newRewardType').value = 'coins';
|
||||||
document.getElementById('newRewardAmount').value = '';
|
document.getElementById('newRewardAmount').value = '';
|
||||||
document.getElementById('newRewardItemKey').value = '';
|
document.getElementById('newRewardItemKey').value = '';
|
||||||
@@ -446,14 +448,14 @@
|
|||||||
clearModalError();
|
clearModalError();
|
||||||
try {
|
try {
|
||||||
// Prefer using cached items to avoid an extra network call and potential race conditions
|
// 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) {
|
if (!it) {
|
||||||
// fallback to fetch single list if not in cache
|
// fallback to fetch single list if not in cache
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/dashboard/${encodeURIComponent(guildId)}/items`);
|
const res = await fetch(`/api/dashboard/${encodeURIComponent(guildId)}/items`);
|
||||||
if (res && res.ok) {
|
if (res && res.ok) {
|
||||||
const j = await res.json();
|
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) {
|
} catch (e) {
|
||||||
// ignore and continue
|
// ignore and continue
|
||||||
@@ -545,87 +547,91 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createBtn.addEventListener('click', ()=>{ modalTitle.textContent = 'Crear item'; showModal(false); });
|
createBtn.addEventListener('click', ()=>{ clearModalError(); currentOpenEditId++; hideModal(); modalTitle.textContent = 'Crear item'; showModal(false); });
|
||||||
cancelBtn.addEventListener('click', hideModal);
|
cancelBtn.addEventListener('click', ()=>{ hideModal(); clearModalError(); });
|
||||||
|
|
||||||
form.addEventListener('submit', async (ev)=>{
|
form.addEventListener('submit', async (ev)=>{
|
||||||
ev.preventDefault();
|
async function openEdit(id) {
|
||||||
const id = document.getElementById('itemId').value;
|
clearModalError();
|
||||||
// validate JSON fields before sending
|
const myOpenId = ++currentOpenEditId;
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
// prefer structured rewards list
|
// Prefer using cached items to avoid an extra network call and potential race conditions
|
||||||
const rawRewards = document.getElementById('rewardsList').dataset.rewards;
|
let it = (cachedItems || []).find(x=>String(x.id) === String(id));
|
||||||
const rewards = rawRewards ? JSON.parse(rawRewards) : null;
|
if (!it) {
|
||||||
if (Array.isArray(rewards)) {
|
// fallback to fetch single list if not in cache
|
||||||
parsedProps.chest = Object.assign(parsedProps.chest || {}, { rewards });
|
try {
|
||||||
} else {
|
const res = await fetch(`/api/dashboard/${encodeURIComponent(guildId)}/items`);
|
||||||
// fallback parse old textarea if present
|
if (res && res.ok) {
|
||||||
const el = document.getElementById('propDropsJson');
|
const j = await res.json();
|
||||||
if (el) {
|
it = (j.items || []).find(x=>String(x.id) === String(id));
|
||||||
const drops = JSON.parse(el.value || '{}');
|
}
|
||||||
if (drops && typeof drops === 'object') parsedProps.chest = Object.assign(parsedProps.chest || {}, { rewards: drops.rewards || drops });
|
} 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
|
// merge custom JSON
|
||||||
try {
|
try {
|
||||||
const custom = JSON.parse(document.getElementById('propCustomJson').value || '{}');
|
const custom = JSON.parse(document.getElementById('propCustomJson').value || '{}');
|
||||||
if (custom && typeof custom === 'object') Object.assign(parsedProps, custom);
|
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
|
// Build metadata
|
||||||
let parsedMeta = { rarity: document.getElementById('metaRarity').value };
|
let parsedMeta = { rarity: document.getElementById('metaRarity').value };
|
||||||
@@ -633,7 +639,7 @@
|
|||||||
try {
|
try {
|
||||||
const customM = JSON.parse(document.getElementById('metaCustomJson').value || '{}');
|
const customM = JSON.parse(document.getElementById('metaCustomJson').value || '{}');
|
||||||
if (customM && typeof customM === 'object') Object.assign(parsedMeta, customM);
|
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 = {
|
const payload = {
|
||||||
key: document.getElementById('fieldKey').value.trim(),
|
key: document.getElementById('fieldKey').value.trim(),
|
||||||
@@ -672,9 +678,10 @@
|
|||||||
if (!res.ok) throw new Error('create-failed');
|
if (!res.ok) throw new Error('create-failed');
|
||||||
}
|
}
|
||||||
hideModal();
|
hideModal();
|
||||||
|
clearModalError();
|
||||||
await fetchItems();
|
await fetchItems();
|
||||||
} catch (err) {
|
} 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);
|
document.getElementById('metaCustomJson').value = JSON.stringify(copyM, null, 2);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} 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)));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user