JudgePole/Public/ecma/Views/UsersView.ecma.js
2026-09-20 17:12:09 +02:00

289 lines
14 KiB
JavaScript

"use strict";
const UsersView = (function(){
const UsersView = function(judge_pole){
const self = this;
let container = null;
let cards_wrapper = null;
let filter_query = "";
this.go = () => {
judge_pole.content_area.innerHTML = "";
judge_pole.html(judge_pole.content_area, build());
};
const build = () => {
const is_admin = judge_pole.session.is_admin();
const topbar_actions = [
["input", {
"type" : "search",
"class" : "jp-search-input",
"data-i18n" : "search_users_placeholder",
"data-i18n-without" : "true",
"placeholder" : judge_pole.i18n.get("search_users_placeholder"),
"value" : filter_query,
"oninput" : (input) => {
filter_query = input.value.trim().toLowerCase();
render_cards();
}
}]
];
// Sólo añadimos el botón si es admin
if(is_admin){
topbar_actions.push(["button", {
"type" : "button",
"class" : "jp-btn-primary",
"data-i18n" : "btn_new_user",
"onclick" : () => show_user_modal()
}, judge_pole.i18n.get("btn_new_user")]);
}
container = judge_pole.html(["div", { "class" : "jp-view jp-users-view" }, [
["div", { "class" : "jp-view-topbar" }, [
["h2", { "data-i18n" : "users_title" }, judge_pole.i18n.get("users_title")],
["div", { "style" : "display:flex; gap:0.5em; align-items:center;" }, topbar_actions]
]],
cards_wrapper = judge_pole.html(["div", { "class" : "jp-cards-grid", "id" : "jp-users-cards" }])[0]
]])[0];
render_cards();
return container;
};
const render_cards = () => {
if(!cards_wrapper) return;
cards_wrapper.innerHTML = "";
const users = judge_pole.db.get_data().users;
const is_admin = judge_pole.session.is_admin();
if(!users.length){
cards_wrapper.appendChild(judge_pole.html(["p", { "class" : "jp-empty", "data-i18n" : "no_users" }, judge_pole.i18n.get("no_users")])[0]);
return;
}
const filtered_users = users.filter(user => {
if(!filter_query) return true;
const match_nick = user.nick.toLowerCase().includes(filter_query);
const match_desc = user.description ? user.description.toLowerCase().includes(filter_query) : false;
const match_links = user.links ? user.links.some(l => l.name.toLowerCase().includes(filter_query)) : false;
return match_nick || match_desc || match_links;
});
if(!filtered_users.length){
cards_wrapper.appendChild(judge_pole.html(["p", { "class" : "jp-empty", "data-i18n" : "no_users_found" }, judge_pole.i18n.get("no_users_found")])[0]);
return;
}
filtered_users.forEach(user => {
const links_html = user.links.map(link => ["a", { "href" : link.url, "target" : "_blank", "class" : "jp-tag-link" }, link.name]);
const is_ai = user.type === "ai";
const type_badge = ["span", {
"class" : "jp-badge " + (is_ai ? "jp-badge-type-ai" : "jp-badge-type-human")
}, is_ai ? "🤖 IA" : "👤 Humano"];
const avatar_element = user.avatar
? ["img", { "src" : user.avatar, "class" : "jp-avatar", "alt" : user.nick }]
: ["div", { "class" : "jp-avatar-placeholder" }, (user.nick ? user.nick[0].toUpperCase() : (is_ai ? "🤖" : "👤"))];
const card_elements = [];
// Botones sólo para Administradores
if(is_admin){
card_elements.push(["button", {
"type" : "button",
"class" : "jp-card-btn-delete",
"data-i18n" : "btn_delete",
"data-i18n-without" : "true",
"title" : judge_pole.i18n.get("btn_delete"),
"onclick" : () => {
const confirm_msg = judge_pole.i18n.get("confirm_delete_user", { nick : user.nick });
if(confirm(confirm_msg)){
judge_pole.db.delete_user(user.id);
render_cards();
}
}
}, "✕"]);
card_elements.push(["button", {
"type" : "button",
"class" : "jp-card-btn-edit",
"data-i18n" : "btn_edit",
"data-i18n-without" : "true",
"title" : judge_pole.i18n.get("btn_edit"),
"onclick" : () => show_user_modal(user.id)
}, "✏️"]);
}
card_elements.push(
avatar_element,
["div", { "style" : "display:flex; justify-content:space-between; align-items:center; margin-bottom:0.3em;" }, [
["h3", { "style" : "margin:0;" }, user.nick],
type_badge
]],
["p", { "class" : "jp-desc" }, user.description],
["div", { "class" : "jp-tags-group" }, links_html],
["div", { "class" : "jp-meta" }, [
["small", {}, judge_pole.i18n.get("created_at") + " " + user.created_at.substring(0, 10)],
["small", {}, judge_pole.i18n.get("updated_at") + " " + user.updated_at.substring(0, 10)]
]]
);
const card = judge_pole.html(["div", { "class" : "jp-card" }, card_elements])[0];
cards_wrapper.appendChild(card);
});
};
const show_user_modal = (user_id = null) => {
const current_user = user_id
? judge_pole.db.get_data().users.find(u => u.id === user_id)
: null;
let links_container = null;
const add_link_row = (name = "", url = "") => {
const row = judge_pole.html(["div", { "class" : "jp-link-row" }, [
["input", { "type" : "text", "placeholder" : "Nombre (ej: GitHub)", "class" : "link-name", "value" : name }],
["input", { "type" : "url", "placeholder" : "https://...", "class" : "link-url", "value" : url }],
["button", {
"type" : "button",
"class" : "jp-btn-danger",
"onclick" : (btn) => btn.parentNode.remove()
}, "✕"]
]])[0];
links_container.appendChild(row);
};
const close_modal = () => {
if(modal_overlay && modal_overlay.parentNode)
modal_overlay.remove();
};
const modal_title = current_user
? judge_pole.i18n.get("modal_edit_user_title", { nick : current_user.nick })
: judge_pole.i18n.get("modal_new_user_title");
const modal_overlay = judge_pole.html(["div", {
"class" : "jp-modal-overlay",
"onclick" : (overlay, e) => {
if(e.target === overlay) close_modal();
}
}, [
["div", { "class" : "jp-modal-window" }, [
["div", { "class" : "jp-modal-header" }, [
["h3", {}, modal_title],
["button", { "type" : "button", "class" : "jp-btn-danger", "onclick" : () => close_modal() }, "✕"]
]],
["div", { "class" : "jp-modal-body" }]
]]
]])[0];
const modal_body = modal_overlay.querySelector(".jp-modal-body");
const form = judge_pole.html(["form", {
"onsubmit" : (form_element, event) => {
event.preventDefault();
const links = [];
links_container.querySelectorAll(".jp-link-row").forEach(row => {
const name = row.querySelector(".link-name").value.trim();
const url = row.querySelector(".link-url").value.trim();
if(name && url) links.push({ name : name, url : url });
});
const select_type = form_element.querySelector("[name='type']");
const pass_input = form_element.querySelector("[name='password']"); // <--- Extraer password
const user_payload = {
type : select_type ? select_type.value : "human",
nick : form_element.querySelector("[name='nick']").value.trim(),
password : pass_input ? pass_input.value.trim() : "", // <--- Incluir en payload
description : form_element.querySelector("[name='description']").value.trim(),
avatar : form_element.querySelector("[name='avatar']").value.trim(),
links : links
};
if(current_user) {
judge_pole.db.update_user(current_user.id, user_payload);
} else {
judge_pole.db.add_user(user_payload);
}
close_modal();
judge_pole.render_header(); // <--- Forzar refresco del header por si cambió el estado de seguridad
render_cards();
}
}, [
["div", { "class" : "jp-field" }, [
["label", {"data-i18n" : "user_type"}, judge_pole.i18n.get("user_type")],
["select", { "name" : "type" }, [
["option", { "value" : "human", "data-i18n" : "user_type_human", ...(current_user && current_user.type === "human" ? { "selected" : "selected" } : {}) }, judge_pole.i18n.get("user_type_human")],
["option", { "value" : "ai", "data-i18n" : "user_type_ai", ...(current_user && current_user.type === "ai" ? { "selected" : "selected" } : {}) }, judge_pole.i18n.get("user_type_ai")]
]]
]],
["div", { "class" : "jp-field" }, [
["label", {"data-i18n" : "user_nick"}, judge_pole.i18n.get("user_nick")],
["input", {
"type" : "text",
"name" : "nick",
"required" : "required",
"value" : current_user ? current_user.nick : ""
}]
]],
["div", { "class" : "jp-field" }, [
["label", { "data-i18n" : "user_pass_label" }, judge_pole.i18n.get("user_pass_label")],
["input", {
"type" : "password",
"name" : "password",
"placeholder" : judge_pole.i18n.get("user_pass_placeholder"),
"value" : current_user ? (current_user.password || "") : ""
}]
]],
["div", { "class" : "jp-field" }, [
["label", {"data-i18n" : "user_avatar"}, judge_pole.i18n.get("user_avatar")],
["input", {
"type" : "text",
"name" : "avatar",
"placeholder" : "./data/avatars/yo.png",
"value" : current_user ? current_user.avatar : ""
}]
]],
["div", { "class" : "jp-field" }, [
["label", {"data-i18n" : "user_desc"}, judge_pole.i18n.get("user_desc")],
["textarea", { "name" : "description", "rows" : "3" }, current_user ? current_user.description : ""]
]],
["div", { "class" : "jp-field" }, [
["label", {"data-i18n" : "user_links"}, judge_pole.i18n.get("user_links")],
links_container = judge_pole.html(["div", { "class" : "jp-links-list" }])[0],
["button", { "type" : "button", "class" : "jp-btn-secondary", "data-i18n" : "btn_add_link", "onclick" : () => add_link_row() }, judge_pole.i18n.get("btn_add_link")]
]],
["div", { "style" : "display:flex; justify-content:flex-end; gap:0.5em; margin-top:1em;" }, [
["button", { "type" : "button", "class" : "jp-btn", "data-i18n" : "btn_cancel_edit", "onclick" : () => close_modal() }, judge_pole.i18n.get("btn_cancel_edit")],
["button", {
"type" : "submit",
"class" : "jp-btn-primary",
"data-i18n" : current_user ? "btn_update_user" : "btn_save_user"
},
current_user ? judge_pole.i18n.get("btn_update_user") : judge_pole.i18n.get("btn_save_user")
]
]]
]])[0];
modal_body.appendChild(form);
if(current_user && current_user.links && current_user.links.length) {
current_user.links.forEach(link => add_link_row(link.name, link.url));
}
judge_pole.item_self.appendChild(modal_overlay);
};
};
return UsersView;
})();