feat(dashboard): actualizar gestión de items con navegación a la vista de laboratorio y mejoras en la carga de datos
This commit is contained in:
@@ -1,220 +1,59 @@
|
|||||||
(function(){
|
(function(){
|
||||||
// read guildId from DOM data- attribute (keeps this script EJS-free)
|
// Lightweight replacement for the legacy dashboard items script.
|
||||||
const guildId = (() => { try{ return (document.getElementById('itemsRoot') && document.getElementById('itemsRoot').dataset && document.getElementById('itemsRoot').dataset.guildId) || ''; }catch(e){ return ''; } })();
|
// Responsibilities kept minimal during migration to Item Lab:
|
||||||
|
// - Fetch and render items list
|
||||||
|
// - Wire Delete handlers
|
||||||
|
// - Redirect Edit actions to the Item Lab
|
||||||
|
|
||||||
const $ = id => document.getElementById(id);
|
const $ = id => document.getElementById(id);
|
||||||
const _escapeMap = {'&':'&','<':'<','>':'>','"':'"',"'":"'"};
|
const itemsRoot = $('itemsRoot');
|
||||||
const escapeHtml = s => { if (s==null) return ''; return String(s).replace(/[&<>"']/g, ch => _escapeMap[ch]); };
|
const guildId = itemsRoot && itemsRoot.dataset ? itemsRoot.dataset.guildId : '';
|
||||||
|
|
||||||
function showPageAlert(type, msg, ttl){
|
|
||||||
const c = $('pageAlert'); if(!c) return;
|
|
||||||
c.classList.remove('hidden'); c.innerHTML = '';
|
|
||||||
const wrapper = document.createElement('div');
|
|
||||||
const color = (type === 'success') ? 'bg-green-600' : (type === 'danger') ? 'bg-red-700' : (type === 'warning') ? 'bg-amber-700' : 'bg-sky-600';
|
|
||||||
wrapper.className = ['p-3','rounded',color,'text-white','flex','items-center','justify-between'].join(' ');
|
|
||||||
const txt = document.createElement('div'); txt.textContent = msg || '';
|
|
||||||
const btn = document.createElement('button'); btn.id = 'pageAlertClose'; btn.className = 'ml-4 font-bold'; btn.textContent = '✕';
|
|
||||||
btn.addEventListener('click', ()=>{ c.classList.add('hidden'); c.innerHTML = ''; });
|
|
||||||
wrapper.appendChild(txt); wrapper.appendChild(btn); c.appendChild(wrapper);
|
|
||||||
if (ttl) setTimeout(()=>{ c.classList.add('hidden'); c.innerHTML = ''; }, ttl);
|
|
||||||
}
|
|
||||||
function clearPageAlert(){ const c = $('pageAlert'); if(c){ c.classList.add('hidden'); c.innerHTML=''; } }
|
|
||||||
|
|
||||||
function setModalError(msg){ const e = $('modalError'); if(!e) return; e.textContent = msg||''; e.classList.toggle('hidden', !msg); }
|
|
||||||
function clearModalError(){ setModalError(''); }
|
|
||||||
|
|
||||||
let cachedItems = [];
|
|
||||||
const list = $('itemsList');
|
const list = $('itemsList');
|
||||||
|
|
||||||
async function fetchItems(){
|
async function fetchItems(){
|
||||||
if(!guildId){ showPageAlert('warning','No hay servidor seleccionado. Selecciona un servidor o inicia sesión.'); return; }
|
if(!list) return;
|
||||||
if(list) list.textContent = 'Cargando items...';
|
list.innerHTML = '<div class="text-white/60">Cargando...</div>';
|
||||||
try{
|
try{
|
||||||
const res = await fetch('/api/dashboard/' + encodeURIComponent(guildId) + '/items', { headers:{ 'Accept':'application/json' } });
|
const res = await fetch('/api/dashboard/' + encodeURIComponent(guildId) + '/items');
|
||||||
if(!res.ok){
|
if(!res.ok) throw new Error('fetch-failed');
|
||||||
// Show helpful message depending on status
|
const j = await res.json();
|
||||||
if(res.status === 401) { showPageAlert('danger','No autenticado. Inicia sesión y vuelve a intentarlo.'); }
|
const items = (j && j.ok && Array.isArray(j.items)) ? j.items : [];
|
||||||
else if(res.status === 403) { showPageAlert('danger','No tienes permisos para ver items en este servidor.'); }
|
renderList(items);
|
||||||
else { showPageAlert('danger','Error al cargar items (HTTP ' + res.status + ')'); }
|
}catch(e){ list.innerHTML = '<div class="text-red-400">Error cargando items</div>'; }
|
||||||
if(list) list.innerHTML = '<div class="text-red-400">Error cargando items: HTTP ' + res.status + '</div>';
|
|
||||||
try{ const errBody = await res.text(); console.debug('items fetch non-ok body:', errBody); }catch(e){}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const j = await res.json(); if(!j || !j.ok) { showPageAlert('danger','Respuesta inválida del servidor al listar items'); if(list) list.innerHTML = '<div class="text-red-400">Respuesta inválida del servidor</div>'; return; }
|
|
||||||
cachedItems = j.items || [];
|
|
||||||
renderList(cachedItems);
|
|
||||||
}catch(err){ if(list) list.innerHTML = '<div class="text-red-400">Error cargando items</div>'; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function escapeHtml(s){ return String(s||'').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); }
|
||||||
|
|
||||||
function renderList(items){
|
function renderList(items){
|
||||||
if(!list) return; list.innerHTML = '';
|
if(!list) return;
|
||||||
if(!Array.isArray(items) || items.length===0){ list.innerHTML = '<div class="text-white/60">No hay items definidos.</div>'; return; }
|
list.innerHTML = '';
|
||||||
items.forEach(it => {
|
if(!items || items.length===0){ list.innerHTML = '<div class="text-white/60">No hay items</div>'; return; }
|
||||||
const card = document.createElement('div'); card.className = 'p-3 bg-[#071a2a] rounded flex items-start justify-between';
|
items.forEach(it=>{
|
||||||
const left = document.createElement('div');
|
const card = document.createElement('div');
|
||||||
// build left column using DOM to avoid inline HTML/template complexity
|
card.className = 'p-3 bg-white/3 rounded mb-2 flex justify-between items-start';
|
||||||
const titleDiv = document.createElement('div');
|
const left = document.createElement('div'); left.style.flex = '1';
|
||||||
titleDiv.className = 'text-white font-medium';
|
const titleDiv = document.createElement('div'); titleDiv.className='font-semibold text-white'; titleDiv.textContent = it.name || it.key || 'Sin nombre';
|
||||||
titleDiv.textContent = (it.name || '') + ' (' + (it.key || '') + ')';
|
const descDiv = document.createElement('div'); descDiv.className = 'text-white/60 text-sm mt-1'; descDiv.textContent = it.description || '';
|
||||||
const descDiv = document.createElement('div');
|
left.appendChild(titleDiv); left.appendChild(descDiv);
|
||||||
descDiv.className = 'text-white/60 text-sm mt-1';
|
const right = document.createElement('div'); right.className = 'flex items-center gap-2';
|
||||||
descDiv.textContent = it.description || '';
|
const editBtn = document.createElement('button'); editBtn.className='editBtn px-2 py-1 bg-indigo-600 rounded text-white text-sm'; editBtn.textContent='Editar'; editBtn.dataset.id = it.id;
|
||||||
left.appendChild(titleDiv);
|
const delBtn = document.createElement('button'); delBtn.className='delBtn px-2 py-1 bg-red-600 rounded text-white text-sm'; delBtn.textContent='Eliminar'; delBtn.dataset.id = it.id;
|
||||||
left.appendChild(descDiv);
|
|
||||||
const right = document.createElement('div');
|
|
||||||
right.className = 'flex items-center gap-2';
|
|
||||||
const editBtn = document.createElement('button');
|
|
||||||
editBtn.className = 'editBtn px-2 py-1 bg-indigo-600 rounded text-white text-sm';
|
|
||||||
editBtn.textContent = 'Editar';
|
|
||||||
editBtn.dataset.id = it.id;
|
|
||||||
const delBtn = document.createElement('button');
|
|
||||||
delBtn.className = 'delBtn px-2 py-1 bg-red-600 rounded text-white text-sm';
|
|
||||||
delBtn.textContent = 'Eliminar';
|
|
||||||
delBtn.dataset.id = it.id;
|
|
||||||
right.appendChild(editBtn); right.appendChild(delBtn);
|
right.appendChild(editBtn); right.appendChild(delBtn);
|
||||||
card.appendChild(left); card.appendChild(right); list.appendChild(card);
|
card.appendChild(left); card.appendChild(right); list.appendChild(card);
|
||||||
});
|
});
|
||||||
|
|
||||||
Array.from(list.querySelectorAll('.editBtn')).forEach(b=>b.addEventListener('click', onEdit));
|
Array.from(list.querySelectorAll('.editBtn')).forEach(b=>b.addEventListener('click', onEdit));
|
||||||
Array.from(list.querySelectorAll('.delBtn')).forEach(b=>b.addEventListener('click', onDelete));
|
Array.from(list.querySelectorAll('.delBtn')).forEach(b=>b.addEventListener('click', onDelete));
|
||||||
}
|
}
|
||||||
|
|
||||||
// rewards helpers
|
async function onDelete(e){ const id = e.currentTarget && e.currentTarget.dataset ? e.currentTarget.dataset.id : null; if(!id) return; if(!confirm('Eliminar item?')) return; try{ const res = await fetch('/api/dashboard/' + encodeURIComponent(guildId) + '/items/' + encodeURIComponent(id), { method:'DELETE' }); if(!res.ok) throw new Error('delete-failed'); await fetchItems(); alert('Item eliminado'); }catch(err){ alert('Error al eliminar'); } }
|
||||||
function getCurrentRewards(){ try{ const r = $('rewardsList'); return r && r.dataset && r.dataset.rewards ? JSON.parse(r.dataset.rewards) : []; }catch(e){ return []; } }
|
|
||||||
function setCurrentRewards(arr){ const r = $('rewardsList'); if(!r) return; r.dataset.rewards = JSON.stringify(arr||[]); }
|
|
||||||
function renderRewardsList(arr){ const container = $('rewardsList'); if(!container) return; container.innerHTML = ''; if(!Array.isArray(arr) || arr.length===0) return; arr.forEach((it,idx)=>{ const row = document.createElement('div'); row.className='flex items-center gap-2'; if(it.coins || it.type==='coins'){ row.innerHTML = '<div class="text-white/80">Coins: <strong class="text-white">' + escapeHtml(String(it.coins||it.amount||0)) + '</strong></div>'; } else if(it.items || it.itemKey){ const key = it.itemKey || (it.items && it.items[0] && it.items[0].key) || ''; const qty = it.quantity || (it.items && it.items[0] && it.items[0].quantity) || 1; row.innerHTML = '<div class="text-white/80">Item: <strong class="text-white">' + escapeHtml(key) + '</strong> x' + escapeHtml(String(qty)) + '</div>'; } else { row.innerHTML = '<div class="text-white/80">' + escapeHtml(JSON.stringify(it)) + '</div>'; } const del = document.createElement('button'); del.className='px-2 py-1 bg-red-600 text-white rounded text-sm ml-2'; del.textContent='Eliminar'; del.addEventListener('click', ()=>{ const cur = getCurrentRewards(); cur.splice(idx,1); setCurrentRewards(cur); renderRewardsList(cur); }); row.appendChild(del); container.appendChild(row); }); }
|
|
||||||
|
|
||||||
// small handlers
|
function onEdit(e){ const id = e.currentTarget && e.currentTarget.dataset ? e.currentTarget.dataset.id : null; if(!id) return; window.location.href = '/dashboard/' + encodeURIComponent(guildId) + '/items/lab?edit=' + encodeURIComponent(id); }
|
||||||
const addRewardBtn = $('addRewardBtn'); if(addRewardBtn) addRewardBtn.addEventListener('click', ()=>{
|
|
||||||
clearModalError(); const type = $('newRewardType') ? $('newRewardType').value : 'items'; const amtRaw = $('newRewardAmount') ? $('newRewardAmount').value : ''; const amt = Number(amtRaw); const key = $('newRewardItemKey') ? $('newRewardItemKey').value.trim() : ''; const cur = getCurrentRewards();
|
|
||||||
if(type==='coins'){ if(!amtRaw || isNaN(amt) || amt<=0){ setModalError('Cantidad de coins debe ser mayor a 0'); return; } cur.push({ coins: amt }); }
|
|
||||||
else { if(!key){ setModalError('item.key requerido'); return; } if(!amtRaw || isNaN(amt) || amt<=0){ setModalError('Cantidad de item debe ser mayor a 0'); return; } cur.push({ items:[{ key, quantity: amt||1 }] }); }
|
|
||||||
setCurrentRewards(cur); renderRewardsList(cur); if($('newRewardAmount')) $('newRewardAmount').value=''; if($('newRewardItemKey')) $('newRewardItemKey').value=''; clearModalError();
|
|
||||||
});
|
|
||||||
const newRewardType = $('newRewardType'); if(newRewardType) newRewardType.addEventListener('change', ()=>{ const k = $('newRewardItemKey'); if(!k) return; if(newRewardType.value==='coins'){ k.disabled = true; k.classList.add('opacity-50'); } else { k.disabled = false; k.classList.remove('opacity-50'); } });
|
|
||||||
|
|
||||||
async function tryLoadRawProps(id){ const msg = $('loadRawMsg'); if(!id) return null; if(msg) msg.textContent = 'cargando...'; try{ const res = await fetch('/api/dashboard/' + encodeURIComponent(guildId) + '/items/' + encodeURIComponent(id) + '/raw', { headers:{ 'Accept':'application/json' } }); if(!res.ok){ if(msg) msg.textContent = 'error'; return null; } const j = await res.json(); if(!j || !j.ok){ if(msg) msg.textContent = 'no data'; return null; } if(msg) msg.textContent = 'raw cargado'; return j.item || null; }catch(e){ if(msg) msg.textContent = 'error'; return null; } }
|
// wire create button (if present) to navigate to lab
|
||||||
|
const createBtn = $('createItemBtn'); if(createBtn) createBtn.addEventListener('click', ()=>{ window.location.href = '/dashboard/' + encodeURIComponent(guildId) + '/items/lab'; });
|
||||||
|
|
||||||
async function onEdit(e){ clearModalError(); const id = e.currentTarget && e.currentTarget.dataset ? e.currentTarget.dataset.id : null; if(!id) return; let item = (cachedItems.find(x=>String(x.id)===String(id))||null);
|
// initial load
|
||||||
if(!item){ try{ const res = await fetch('/api/dashboard/' + encodeURIComponent(guildId) + '/items/' + encodeURIComponent(id)); if(!res.ok) throw new Error('fetch'); const j = await res.json(); if(j && j.ok) { item = j.item; cachedItems.push(item); } }catch(err){ setModalError('Error cargando item'); return; } }
|
|
||||||
if(!item) return setModalError('Item no encontrado');
|
|
||||||
// map fields
|
|
||||||
if($('itemId')) $('itemId').value = item.id || '';
|
|
||||||
if($('fieldKey')) $('fieldKey').value = item.key || '';
|
|
||||||
if($('fieldName')) $('fieldName').value = item.name || '';
|
|
||||||
if($('fieldCategory')) $('fieldCategory').value = item.category || '';
|
|
||||||
if($('fieldIcon')) $('fieldIcon').value = item.icon || '';
|
|
||||||
if($('fieldDescription')) $('fieldDescription').value = item.description || '';
|
|
||||||
if($('fieldTags')) $('fieldTags').value = Array.isArray(item.tags)?item.tags.join(','):item.tags||'';
|
|
||||||
if($('fieldMaxPer')) $('fieldMaxPer').value = item.maxPerInventory||'';
|
|
||||||
const p = item.props || {};
|
|
||||||
try{
|
|
||||||
if($('propCraftable')) $('propCraftable').checked = !!p.craftable;
|
|
||||||
if($('propRecipeKey')) $('propRecipeKey').value = p.recipe && p.recipe.key ? p.recipe.key : '';
|
|
||||||
if($('propEquipable')) $('propEquipable').checked = !!p.equipable;
|
|
||||||
if($('propSlot')) $('propSlot').value = p.slot || '';
|
|
||||||
if($('propAttack')) $('propAttack').value = p.attack || '';
|
|
||||||
if($('propDefense')) $('propDefense').value = p.defense || '';
|
|
||||||
if($('propDurability')) $('propDurability').value = p.durability || '';
|
|
||||||
if($('propMaxDurability')) $('propMaxDurability').value = p.maxDurability || '';
|
|
||||||
if($('propToolType')) $('propToolType').value = (p.tool && p.tool.type) ? p.tool.type : '';
|
|
||||||
if($('propToolTier')) $('propToolTier').value = (p.tool && typeof p.tool.tier !== 'undefined') ? String(p.tool.tier) : '';
|
|
||||||
if($('propBreakable')) $('propBreakable').checked = !!(p.breakable && p.breakable.enabled);
|
|
||||||
if($('propDurabilityPerUse')) $('propDurabilityPerUse').value = (p.breakable && p.breakable.durabilityPerUse) ? String(p.breakable.durabilityPerUse) : '';
|
|
||||||
if($('propUsable')) $('propUsable').checked = !!p.usable;
|
|
||||||
if($('propPurgeAllEffects')) $('propPurgeAllEffects').checked = !!p.purgeAllEffects;
|
|
||||||
if($('propHealAmount')) $('propHealAmount').value = p.heal || '';
|
|
||||||
if($('propDamage')) $('propDamage').value = p.damage || '';
|
|
||||||
if($('propDamageBonus')) $('propDamageBonus').value = p.damageBonus || '';
|
|
||||||
if($('statAttack')) $('statAttack').value = p.stats && typeof p.stats.attack !== 'undefined' ? String(p.stats.attack) : '';
|
|
||||||
if($('statHp')) $('statHp').value = p.stats && typeof p.stats.hp !== 'undefined' ? String(p.stats.hp) : '';
|
|
||||||
if($('statDefense')) $('statDefense').value = p.stats && typeof p.stats.defense !== 'undefined' ? String(p.stats.defense) : '';
|
|
||||||
if($('statXpReward')) $('statXpReward').value = p.stats && typeof p.stats.xpReward !== 'undefined' ? String(p.stats.xpReward) : '';
|
|
||||||
if($('reqToolRequired')) $('reqToolRequired').checked = !!(p.requirements && p.requirements.tool && p.requirements.tool.required);
|
|
||||||
if($('reqToolType')) $('reqToolType').value = (p.requirements && p.requirements.tool && p.requirements.tool.toolType) ? p.requirements.tool.toolType : '';
|
|
||||||
if($('reqMinTier')) $('reqMinTier').value = (p.requirements && p.requirements.tool && typeof p.requirements.tool.minTier !== 'undefined') ? String(p.requirements.tool.minTier) : '';
|
|
||||||
if($('propSellPrice')) $('propSellPrice').value = p.sellPrice || '';
|
|
||||||
if($('propBuyPrice')) $('propBuyPrice').value = p.buyPrice || '';
|
|
||||||
if($('propChestEnabled')) $('propChestEnabled').checked = !!(p.chest && p.chest.enabled);
|
|
||||||
const rewards = p.chest && p.chest.rewards ? p.chest.rewards : [];
|
|
||||||
setCurrentRewards(rewards||[]); renderRewardsList(rewards||[]);
|
|
||||||
if($('propCustomJson')){ const copy = Object.assign({}, p); ['craftable','recipe','equipable','slot','attack','defense','durability','maxDurability'].forEach(k=>delete copy[k]); $('propCustomJson').value = JSON.stringify(copy, null, 2); }
|
|
||||||
}catch(e){}
|
|
||||||
const m = item.metadata || {};
|
|
||||||
if($('metaRarity')) $('metaRarity').value = m.rarity || 'common';
|
|
||||||
if($('metaWeight')) $('metaWeight').value = typeof m.weight !== 'undefined' ? String(m.weight) : '';
|
|
||||||
if($('metaCustomJson')){ const copyM = Object.assign({}, m); delete copyM.rarity; delete copyM.weight; $('metaCustomJson').value = JSON.stringify(copyM, null, 2); }
|
|
||||||
renderTagChips();
|
|
||||||
if($('modalTitle')) $('modalTitle').textContent = 'Editar item';
|
|
||||||
if($('itemModal')){ $('itemModal').classList.remove('hidden'); $('itemModal').classList.add('flex'); }
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onDelete(e){ const id = e.currentTarget && e.currentTarget.dataset ? e.currentTarget.dataset.id : null; if(!id) return; if(!confirm('Eliminar item?')) return; try{ const res = await fetch('/api/dashboard/' + encodeURIComponent(guildId) + '/items/' + encodeURIComponent(id), { method:'DELETE' }); if(!res.ok) throw new Error('delete-failed'); await fetchItems(); showPageAlert('success','Item eliminado',3000); }catch(err){ showPageAlert('danger','Error al eliminar'); } }
|
|
||||||
|
|
||||||
// create/cancel
|
|
||||||
const createBtn = $('createItemBtn'); if(createBtn) createBtn.addEventListener('click', ()=>{ clearModalError(); resetFormForCreate(); if($('modalTitle')) $('modalTitle').textContent = 'Crear item'; if($('itemModal')){ $('itemModal').classList.remove('hidden'); $('itemModal').classList.add('flex'); } });
|
|
||||||
const cancelBtn = $('cancelItemBtn'); if(cancelBtn) cancelBtn.addEventListener('click', ()=>{ clearModalError(); if($('itemModal')){ $('itemModal').classList.add('hidden'); $('itemModal').classList.remove('flex'); } });
|
|
||||||
|
|
||||||
function resetFormForCreate(){ try{ const ids = ['itemId','fieldKey','fieldName','fieldCategory','fieldIcon','fieldDescription','fieldTags','fieldMaxPer']; ids.forEach(id=>{ const e=$(id); if(e) e.value=''; }); const checks = ['propCraftable','propEquipable','propBreakable','propUsable','propPurgeAllEffects','propChestEnabled','reqToolRequired']; checks.forEach(id=>{ const e=$(id); if(e && e.type==='checkbox') e.checked=false; }); if($('propCustomJson')) $('propCustomJson').value='{}'; if($('metaCustomJson')) $('metaCustomJson').value='{}'; setCurrentRewards([]); renderRewardsList([]); renderTagChips(); }catch(e){}
|
|
||||||
}
|
|
||||||
|
|
||||||
// tag chips
|
|
||||||
const tagsChips = document.createElement('div'); tagsChips.id='tagsChips'; tagsChips.className='mt-2 flex flex-wrap gap-2'; if($('fieldTags') && $('fieldTags').parentNode) $('fieldTags').parentNode.appendChild(tagsChips);
|
|
||||||
function renderTagChips(){ try{ const base = $('fieldTags') && $('fieldTags').value ? $('fieldTags').value.split(',').map(s=>s.trim()).filter(Boolean) : []; const auto=[]; if($('propCraftable') && $('propCraftable').checked) auto.push('craftable'); if($('propToolType') && $('propToolType').value.trim()) auto.push('tool'); if(($('propDamage') && $('propDamage').value.trim())||($('propAttack') && $('propAttack').value.trim())) auto.push('weapon'); if($('propUsable') && $('propUsable').checked) auto.push('consumable'); if($('propEquipable') && $('propEquipable').checked) auto.push('equipable'); if($('propChestEnabled') && $('propChestEnabled').checked) auto.push('chest'); if($('propBreakable') && $('propBreakable').checked) auto.push('breakable'); if($('propSellPrice') && $('propSellPrice').value.trim()) auto.push('sellable'); if($('propBuyPrice') && $('propBuyPrice').value.trim()) auto.push('buyable'); const merged = Array.from(new Set([...(base||[]), ...auto])); tagsChips.innerHTML=''; merged.forEach(t=>{ const chip = document.createElement('span'); chip.className='px-2 py-1 rounded bg-white/6 text-sm text-white'; chip.textContent = t; tagsChips.appendChild(chip); }); }catch(e){}
|
|
||||||
['propCraftable','propToolType','propDamage','propAttack','propUsable','propEquipable','propChestEnabled','propBreakable','propSellPrice','propBuyPrice','fieldTags'].forEach(id=>{ const e=$(id); if(!e) return; e.addEventListener('input', renderTagChips); e.addEventListener('change', renderTagChips); });
|
|
||||||
|
|
||||||
// form submit
|
|
||||||
const form = $('itemForm'); if(form) form.addEventListener('submit', async ev=>{
|
|
||||||
ev.preventDefault(); clearModalError(); try{
|
|
||||||
const id = $('itemId') ? $('itemId').value : '';
|
|
||||||
const parsedProps = {};
|
|
||||||
if($('propCraftable') && $('propCraftable').checked){ parsedProps.craftable = true; const rk = $('propRecipeKey') ? $('propRecipeKey').value.trim() : ''; if(rk) parsedProps.recipe = { key: rk }; }
|
|
||||||
if($('propEquipable') && $('propEquipable').checked){ parsedProps.equipable = true; const slot = $('propSlot') ? $('propSlot').value : ''; if(slot) parsedProps.slot = slot; }
|
|
||||||
const ttype = $('propToolType') ? $('propToolType').value.trim() : ''; const ttier = $('propToolTier') ? $('propToolTier').value.trim() : ''; if(ttype) parsedProps.tool = Object.assign({}, parsedProps.tool||{}, { type: ttype }); if(ttier) parsedProps.tool = Object.assign({}, parsedProps.tool||{}, { tier: Number(ttier) });
|
|
||||||
if($('propBreakable') && $('propBreakable').checked){ parsedProps.breakable = parsedProps.breakable||{}; parsedProps.breakable.enabled = true; const dpu = $('propDurabilityPerUse') ? $('propDurabilityPerUse').value.trim() : ''; if(dpu) parsedProps.breakable.durabilityPerUse = Number(dpu); }
|
|
||||||
const attack = $('propAttack') ? $('propAttack').value.trim() : ''; if(attack) parsedProps.attack = Number(attack);
|
|
||||||
const defense = $('propDefense') ? $('propDefense').value.trim() : ''; if(defense) parsedProps.defense = Number(defense);
|
|
||||||
const durability = $('propDurability') ? $('propDurability').value.trim() : ''; if(durability) parsedProps.durability = Number(durability);
|
|
||||||
const maxDur = $('propMaxDurability') ? $('propMaxDurability').value.trim() : ''; if(maxDur) parsedProps.maxDurability = Number(maxDur);
|
|
||||||
if($('propUsable') && $('propUsable').checked){ parsedProps.usable = true; if($('propPurgeAllEffects') && $('propPurgeAllEffects').checked) parsedProps.purgeAllEffects = true; const heal = $('propHealAmount') ? $('propHealAmount').value.trim() : ''; if(heal) parsedProps.heal = Number(heal); }
|
|
||||||
const dmg = $('propDamage') ? $('propDamage').value.trim() : ''; if(dmg) parsedProps.damage = Number(dmg);
|
|
||||||
const dmgBonus = $('propDamageBonus') ? $('propDamageBonus').value.trim() : ''; if(dmgBonus) parsedProps.damageBonus = Number(dmgBonus);
|
|
||||||
const sAtk = $('statAttack') ? $('statAttack').value.trim() : ''; if(sAtk) { parsedProps.stats = parsedProps.stats||{}; parsedProps.stats.attack = Number(sAtk); }
|
|
||||||
const sHp = $('statHp') ? $('statHp').value.trim() : ''; if(sHp) { parsedProps.stats = parsedProps.stats||{}; parsedProps.stats.hp = Number(sHp); }
|
|
||||||
const sDef = $('statDefense') ? $('statDefense').value.trim() : ''; if(sDef) { parsedProps.stats = parsedProps.stats||{}; parsedProps.stats.defense = Number(sDef); }
|
|
||||||
const sXp = $('statXpReward') ? $('statXpReward').value.trim() : ''; if(sXp) { parsedProps.stats = parsedProps.stats||{}; parsedProps.stats.xpReward = Number(sXp); }
|
|
||||||
if($('reqToolRequired') && $('reqToolRequired').checked){ parsedProps.requirements = parsedProps.requirements||{}; parsedProps.requirements.tool = { required: true }; const rtype = $('reqToolType') ? $('reqToolType').value.trim() : ''; if(rtype) parsedProps.requirements.tool.toolType = rtype; const rmin = $('reqMinTier') ? $('reqMinTier').value.trim() : ''; if(rmin) parsedProps.requirements.tool.minTier = Number(rmin); }
|
|
||||||
const sell = $('propSellPrice') ? $('propSellPrice').value.trim() : ''; if(sell) parsedProps.sellPrice = Number(sell);
|
|
||||||
const buy = $('propBuyPrice') ? $('propBuyPrice').value.trim() : ''; if(buy) parsedProps.buyPrice = Number(buy);
|
|
||||||
if($('propChestEnabled') && $('propChestEnabled').checked){ parsedProps.chest = parsedProps.chest||{}; parsedProps.chest.enabled = true; }
|
|
||||||
try{ const rawRewards = $('rewardsList') ? $('rewardsList').dataset.rewards : null; const rewards = rawRewards ? JSON.parse(rawRewards) : null; if(Array.isArray(rewards)) parsedProps.chest = Object.assign(parsedProps.chest||{}, { rewards }); }catch(e){ setModalError('JSON inválido en Drops/Chest: ' + (e && e.message ? e.message : String(e))); return; }
|
|
||||||
try{ const custom = $('propCustomJson') ? JSON.parse($('propCustomJson').value || '{}') : {}; if(custom && typeof custom === 'object') Object.assign(parsedProps, custom); }catch(e){ setModalError('JSON inválido en Props personalizado: ' + (e && e.message ? e.message : String(e))); return; }
|
|
||||||
|
|
||||||
const parsedMeta = { rarity: ($('metaRarity') ? $('metaRarity').value : 'common') };
|
|
||||||
const w = $('metaWeight') ? $('metaWeight').value.trim() : ''; if(w) parsedMeta.weight = Number(w);
|
|
||||||
try{ const customM = $('metaCustomJson') ? JSON.parse($('metaCustomJson').value || '{}') : {}; if(customM && typeof customM === 'object') Object.assign(parsedMeta, customM); }catch(e){ setModalError('JSON inválido en Metadata personalizado: ' + (e && e.message ? e.message : String(e))); return; }
|
|
||||||
|
|
||||||
const payload = {
|
|
||||||
key: ($('fieldKey') ? $('fieldKey').value.trim() : ''),
|
|
||||||
name: ($('fieldName') ? $('fieldName').value.trim() : ''),
|
|
||||||
category: ($('fieldCategory') ? $('fieldCategory').value.trim() : ''),
|
|
||||||
icon: ($('fieldIcon') ? $('fieldIcon').value.trim() : ''),
|
|
||||||
description: ($('fieldDescription') ? $('fieldDescription').value.trim() : ''),
|
|
||||||
tags: ($('fieldTags') ? $('fieldTags').value.split(',').map(s=>s.trim()).filter(Boolean) : []),
|
|
||||||
maxPerInventory: ($('fieldMaxPer') ? Number($('fieldMaxPer').value) || null : null),
|
|
||||||
props: Object.keys(parsedProps).length ? parsedProps : null,
|
|
||||||
metadata: Object.keys(parsedMeta).length ? parsedMeta : null,
|
|
||||||
};
|
|
||||||
try{ const auto = new Set(payload.tags || []); if(parsedProps.craftable) auto.add('craftable'); if(parsedProps.tool) auto.add('tool'); if(parsedProps.damage||parsedProps.attack||parsedProps.stats) auto.add('weapon'); if(parsedProps.usable) auto.add('consumable'); if(parsedProps.equipable) auto.add('equipable'); if(parsedProps.chest) auto.add('chest'); if(parsedProps.breakable) auto.add('breakable'); if(parsedProps.sellPrice) auto.add('sellable'); if(parsedProps.buyPrice) auto.add('buyable'); payload.tags = Array.from(auto).filter(Boolean); }catch(e){}
|
|
||||||
|
|
||||||
try{
|
|
||||||
if(id){ const res = await fetch('/api/dashboard/' + encodeURIComponent(guildId) + '/items/' + encodeURIComponent(id), { method:'PUT', headers:{ 'Content-Type':'application/json' }, body: JSON.stringify(payload) }); if(!res.ok) throw new Error('save-failed'); }
|
|
||||||
else { const res = await fetch('/api/dashboard/' + encodeURIComponent(guildId) + '/items', { method:'POST', headers:{ 'Content-Type':'application/json' }, body: JSON.stringify(payload) }); if(!res.ok) throw new Error('create-failed'); }
|
|
||||||
if($('itemModal')){ $('itemModal').classList.add('hidden'); $('itemModal').classList.remove('flex'); }
|
|
||||||
clearModalError(); await fetchItems();
|
|
||||||
}catch(e){ setModalError('Error al guardar item. ¿JSON válido en Props/Metadata?'); }
|
|
||||||
}catch(e){ setModalError('Error interno: ' + (e && e.message ? e.message : String(e))); }
|
|
||||||
});
|
|
||||||
|
|
||||||
// initial
|
|
||||||
fetchItems();
|
fetchItems();
|
||||||
}})()
|
|
||||||
|
})();
|
||||||
|
|||||||
59
src/server/public/assets/js/dashboard_items.js.tmp
Normal file
59
src/server/public/assets/js/dashboard_items.js.tmp
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
(function(){
|
||||||
|
// Lightweight replacement for the legacy dashboard items script.
|
||||||
|
// Responsibilities kept minimal during migration to Item Lab:
|
||||||
|
// - Fetch and render items list
|
||||||
|
// - Wire Delete handlers
|
||||||
|
// - Redirect Edit actions to the Item Lab
|
||||||
|
|
||||||
|
const $ = id => document.getElementById(id);
|
||||||
|
const itemsRoot = $('itemsRoot');
|
||||||
|
const guildId = itemsRoot && itemsRoot.dataset ? itemsRoot.dataset.guildId : '';
|
||||||
|
const list = $('itemsList');
|
||||||
|
|
||||||
|
async function fetchItems(){
|
||||||
|
if(!list) return;
|
||||||
|
list.innerHTML = '<div class="text-white/60">Cargando...</div>';
|
||||||
|
try{
|
||||||
|
const res = await fetch('/api/dashboard/' + encodeURIComponent(guildId) + '/items');
|
||||||
|
if(!res.ok) throw new Error('fetch-failed');
|
||||||
|
const j = await res.json();
|
||||||
|
const items = (j && j.ok && Array.isArray(j.items)) ? j.items : [];
|
||||||
|
renderList(items);
|
||||||
|
}catch(e){ list.innerHTML = '<div class="text-red-400">Error cargando items</div>'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(s){ return String(s||'').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); }
|
||||||
|
|
||||||
|
function renderList(items){
|
||||||
|
if(!list) return;
|
||||||
|
list.innerHTML = '';
|
||||||
|
if(!items || items.length===0){ list.innerHTML = '<div class="text-white/60">No hay items</div>'; return; }
|
||||||
|
items.forEach(it=>{
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.className = 'p-3 bg-white/3 rounded mb-2 flex justify-between items-start';
|
||||||
|
const left = document.createElement('div'); left.style.flex = '1';
|
||||||
|
const titleDiv = document.createElement('div'); titleDiv.className='font-semibold text-white'; titleDiv.textContent = it.name || it.key || 'Sin nombre';
|
||||||
|
const descDiv = document.createElement('div'); descDiv.className = 'text-white/60 text-sm mt-1'; descDiv.textContent = it.description || '';
|
||||||
|
left.appendChild(titleDiv); left.appendChild(descDiv);
|
||||||
|
const right = document.createElement('div'); right.className = 'flex items-center gap-2';
|
||||||
|
const editBtn = document.createElement('button'); editBtn.className='editBtn px-2 py-1 bg-indigo-600 rounded text-white text-sm'; editBtn.textContent='Editar'; editBtn.dataset.id = it.id;
|
||||||
|
const delBtn = document.createElement('button'); delBtn.className='delBtn px-2 py-1 bg-red-600 rounded text-white text-sm'; delBtn.textContent='Eliminar'; delBtn.dataset.id = it.id;
|
||||||
|
right.appendChild(editBtn); right.appendChild(delBtn);
|
||||||
|
card.appendChild(left); card.appendChild(right); list.appendChild(card);
|
||||||
|
});
|
||||||
|
|
||||||
|
Array.from(list.querySelectorAll('.editBtn')).forEach(b=>b.addEventListener('click', onEdit));
|
||||||
|
Array.from(list.querySelectorAll('.delBtn')).forEach(b=>b.addEventListener('click', onDelete));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onDelete(e){ const id = e.currentTarget && e.currentTarget.dataset ? e.currentTarget.dataset.id : null; if(!id) return; if(!confirm('Eliminar item?')) return; try{ const res = await fetch('/api/dashboard/' + encodeURIComponent(guildId) + '/items/' + encodeURIComponent(id), { method:'DELETE' }); if(!res.ok) throw new Error('delete-failed'); await fetchItems(); alert('Item eliminado'); }catch(err){ alert('Error al eliminar'); } }
|
||||||
|
|
||||||
|
function onEdit(e){ const id = e.currentTarget && e.currentTarget.dataset ? e.currentTarget.dataset.id : null; if(!id) return; window.location.href = '/dashboard/' + encodeURIComponent(guildId) + '/items/lab?edit=' + encodeURIComponent(id); }
|
||||||
|
|
||||||
|
// wire create button (if present) to navigate to lab
|
||||||
|
const createBtn = $('createItemBtn'); if(createBtn) createBtn.addEventListener('click', ()=>{ window.location.href = '/dashboard/' + encodeURIComponent(guildId) + '/items/lab'; });
|
||||||
|
|
||||||
|
// initial load
|
||||||
|
fetchItems();
|
||||||
|
|
||||||
|
})();
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h2 class="text-white text-lg font-semibold">Items</h2>
|
<h2 class="text-white text-lg font-semibold">Items</h2>
|
||||||
<div>
|
<div>
|
||||||
<button id="createItemBtn" class="inline-flex items-center gap-2 px-3 py-1 bg-indigo-600 rounded text-white">Crear item</button>
|
<a id="createItemBtn" href="/dashboard/<%= selectedGuildId %>/items/lab" class="inline-flex items-center gap-2 px-3 py-1 bg-indigo-600 rounded text-white">Crear item</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,13 @@
|
|||||||
<ul class="mt-2 space-y-3 text-slate-200">
|
<ul class="mt-2 space-y-3 text-slate-200">
|
||||||
<li class="text-sm"><a href="/dashboard/<%= selectedGuild %>/settings" class="flex items-center gap-3"><span class="opacity-80">⚙️</span> General Settings</a></li>
|
<li class="text-sm"><a href="/dashboard/<%= selectedGuild %>/settings" class="flex items-center gap-3"><span class="opacity-80">⚙️</span> General Settings</a></li>
|
||||||
<li class="text-sm"><a href="/dashboard/<%= selectedGuild %>/items" class="flex items-center gap-3"><span class="opacity-80">🧭</span> Items</a></li>
|
<li class="text-sm"><a href="/dashboard/<%= selectedGuild %>/items" class="flex items-center gap-3"><span class="opacity-80">🧭</span> Items</a></li>
|
||||||
|
<li class="text-sm">
|
||||||
|
<a href="/dashboard/<%= selectedGuild %>/items/lab" class="flex items-center gap-3">
|
||||||
|
<span class="inline-flex items-center justify-center w-6 h-6 rounded-full bg-white/6 animate-pulse">🔬</span>
|
||||||
|
<span>Lab</span>
|
||||||
|
<span id="labSpinner" class="ml-auto opacity-0 text-xs">⏳</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
Reference in New Issue
Block a user