diff --git a/src/server/public/assets/js/dashboard_items.js b/src/server/public/assets/js/dashboard_items.js
index 939dc3f..3fac785 100644
--- a/src/server/public/assets/js/dashboard_items.js
+++ b/src/server/public/assets/js/dashboard_items.js
@@ -1,220 +1,59 @@
(function(){
- // read guildId from DOM data- attribute (keeps this script EJS-free)
- const guildId = (() => { try{ return (document.getElementById('itemsRoot') && document.getElementById('itemsRoot').dataset && document.getElementById('itemsRoot').dataset.guildId) || ''; }catch(e){ return ''; } })();
+ // 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 _escapeMap = {'&':'&','<':'<','>':'>','"':'"',"'":"'"};
- const escapeHtml = s => { if (s==null) return ''; return String(s).replace(/[&<>"']/g, ch => _escapeMap[ch]); };
-
- 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 itemsRoot = $('itemsRoot');
+ const guildId = itemsRoot && itemsRoot.dataset ? itemsRoot.dataset.guildId : '';
const list = $('itemsList');
async function fetchItems(){
- if(!guildId){ showPageAlert('warning','No hay servidor seleccionado. Selecciona un servidor o inicia sesión.'); return; }
- if(list) list.textContent = 'Cargando items...';
+ if(!list) return;
+ list.innerHTML = '
Cargando...
';
try{
- const res = await fetch('/api/dashboard/' + encodeURIComponent(guildId) + '/items', { headers:{ 'Accept':'application/json' } });
- if(!res.ok){
- // Show helpful message depending on status
- if(res.status === 401) { showPageAlert('danger','No autenticado. Inicia sesión y vuelve a intentarlo.'); }
- else if(res.status === 403) { showPageAlert('danger','No tienes permisos para ver items en este servidor.'); }
- else { showPageAlert('danger','Error al cargar items (HTTP ' + res.status + ')'); }
- if(list) list.innerHTML = 'Error cargando items: HTTP ' + res.status + '
';
- 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 = 'Respuesta inválida del servidor
'; return; }
- cachedItems = j.items || [];
- renderList(cachedItems);
- }catch(err){ if(list) list.innerHTML = 'Error cargando items
'; }
+ 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 = 'Error cargando items
'; }
}
+ function escapeHtml(s){ return String(s||'').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); }
+
function renderList(items){
- if(!list) return; list.innerHTML = '';
- if(!Array.isArray(items) || items.length===0){ list.innerHTML = 'No hay items definidos.
'; return; }
- items.forEach(it => {
- const card = document.createElement('div'); card.className = 'p-3 bg-[#071a2a] rounded flex items-start justify-between';
- const left = document.createElement('div');
- // build left column using DOM to avoid inline HTML/template complexity
- const titleDiv = document.createElement('div');
- titleDiv.className = 'text-white font-medium';
- titleDiv.textContent = (it.name || '') + ' (' + (it.key || '') + ')';
- 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;
+ if(!list) return;
+ list.innerHTML = '';
+ if(!items || items.length===0){ list.innerHTML = 'No hay items
'; 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));
}
- // rewards helpers
- 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 = 'Coins: ' + escapeHtml(String(it.coins||it.amount||0)) + '
'; } 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 = 'Item: ' + escapeHtml(key) + ' x' + escapeHtml(String(qty)) + '
'; } else { row.innerHTML = '' + escapeHtml(JSON.stringify(it)) + '
'; } 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); }); }
+ 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'); } }
- // small handlers
- 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'); } });
+ 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); }
- 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);
- 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
+ // initial load
fetchItems();
-}})()
+
+})();
diff --git a/src/server/public/assets/js/dashboard_items.js.tmp b/src/server/public/assets/js/dashboard_items.js.tmp
new file mode 100644
index 0000000..3fac785
--- /dev/null
+++ b/src/server/public/assets/js/dashboard_items.js.tmp
@@ -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 = 'Cargando...
';
+ 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 = 'Error cargando items
'; }
+ }
+
+ 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 = 'No hay items
'; 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();
+
+})();
diff --git a/src/server/views/partials/dashboard/dashboard_items.ejs b/src/server/views/partials/dashboard/dashboard_items.ejs
index a80d723..7c216e6 100644
--- a/src/server/views/partials/dashboard/dashboard_items.ejs
+++ b/src/server/views/partials/dashboard/dashboard_items.ejs
@@ -2,7 +2,7 @@
diff --git a/src/server/views/partials/dashboard/dashboard_sidebar.ejs b/src/server/views/partials/dashboard/dashboard_sidebar.ejs
index fdce825..2040f43 100644
--- a/src/server/views/partials/dashboard/dashboard_sidebar.ejs
+++ b/src/server/views/partials/dashboard/dashboard_sidebar.ejs
@@ -46,6 +46,13 @@