"use strict"; const ProjectsView = (function(){ const ProjectsView = 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_projects_placeholder", "data-i18n-without" : "true", "placeholder" : judge_pole.i18n.get("search_projects_placeholder"), "value" : filter_query, "oninput" : (input) => { filter_query = input.value.trim().toLowerCase(); render_cards(); } }] ]; if(is_admin){ topbar_actions.push(["button", { "type" : "button", "class" : "jp-btn-primary", "data-i18n" : "btn_new_project", "onclick" : () => show_project_modal() }, judge_pole.i18n.get("btn_new_project")]); } container = judge_pole.html(["div", { "class" : "jp-view jp-projects-view" }, [ ["div", { "class" : "jp-view-topbar" }, [ ["h2", { "data-i18n" : "projects_title" }, judge_pole.i18n.get("projects_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-projects-cards" }])[0] ]])[0]; render_cards(); return container; }; const render_cards = () => { if(!cards_wrapper) return; cards_wrapper.innerHTML = ""; const db_data = judge_pole.db.get_data(); const projects = db_data.projects; const users = db_data.users; const is_admin = judge_pole.session.is_admin(); if(!projects.length){ cards_wrapper.appendChild(judge_pole.html(["p", { "class" : "jp-empty", "data-i18n" : "no_projects" }, judge_pole.i18n.get("no_projects")])[0]); return; } const filtered_projects = projects.filter(project => { if(!filter_query) return true; const project_user_ids = project.user_ids || (project.user_id ? [project.user_id] : []); const authors = users.filter(u => project_user_ids.includes(u.id)); const authors_str = authors.map(a => a.nick.toLowerCase()).join(" "); const match_name = project.name.toLowerCase().includes(filter_query); const match_author = authors_str.includes(filter_query); const match_desc = project.description ? project.description.toLowerCase().includes(filter_query) : false; const match_tags = project.tags ? project.tags.some(t => t.toLowerCase().includes(filter_query)) : false; const match_tech = project.technologies ? project.technologies.some(t => t.toLowerCase().includes(filter_query)) : false; const match_links = project.links ? project.links.some(l => l.name.toLowerCase().includes(filter_query)) : false; return match_name || match_author || match_desc || match_tags || match_tech || match_links; }); if(!filtered_projects.length){ cards_wrapper.appendChild(judge_pole.html(["p", { "class" : "jp-empty", "data-i18n" : "no_projects_found" }, judge_pole.i18n.get("no_projects_found")])[0]); return; } filtered_projects.forEach(project => { const project_user_ids = project.user_ids || (project.user_id ? [project.user_id] : []); const authors = users.filter(u => project_user_ids.includes(u.id)); const authors_str = authors.length ? authors.map(a => `${a.nick} ${a.type === 'ai' ? '🤖' : '👤'}`).join(", ") : "Unknown"; const tags_html = project.tags.map(t => ["span", { "class" : "jp-badge jp-badge-tag" }, "#" + t]); const tech_html = project.technologies.map(t => ["span", { "class" : "jp-badge jp-badge-tech" }, t]); const links_html = project.links.map(link => ["a", { "href" : link.url, "target" : "_blank", "class" : "jp-tag-link" }, link.name]); const avatar_element = project.avatar ? ["img", { "src" : project.avatar, "class" : "jp-avatar", "alt" : project.name }] : ["div", { "class" : "jp-avatar-placeholder" }, (project.name ? project.name.substring(0, 2).toUpperCase() : "")]; const card_elements = []; // Botones sólo si es Admin 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_project", { name : project.name }); if(confirm(confirm_msg)){ judge_pole.db.delete_project(project.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_project_modal(project.id) }, "✏️"]); } card_elements.push( avatar_element, ["h3", {}, project.name], ["h4", { "class" : "jp-author-tag" }, "By: " + authors_str], ["p", { "class" : "jp-desc" }, project.description], ["div", { "class" : "jp-tags-group" }, tags_html], ["div", { "class" : "jp-tags-group" }, tech_html], ["div", { "class" : "jp-tags-group" }, links_html], ["div", { "class" : "jp-meta" }, [ ["small", {}, judge_pole.i18n.get("created_at") + " " + project.created_at.substring(0, 10)], ["small", {}, judge_pole.i18n.get("updated_at") + " " + project.updated_at.substring(0, 10)] ]] ); const card = judge_pole.html(["div", { "class" : "jp-card" }, card_elements])[0]; cards_wrapper.appendChild(card); }); }; const show_project_modal = (project_id = null) => { const db_data = judge_pole.db.get_data(); const users = db_data.users; const current_project = project_id ? db_data.projects.find(p => p.id === project_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 (Repo, Demo...)", "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_project ? judge_pole.i18n.get("modal_edit_project_title", { name : current_project.name }) : judge_pole.i18n.get("modal_new_project_title"); // 1. Overlay base 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"); // 2. Construcción de la caja de autores (Picker) const current_author_ids = current_project ? (current_project.user_ids || (current_project.user_id ? [current_project.user_id] : [])) : []; const authors_picker = judge_pole.html(["div", { "class" : "jp-authors-picker" }, [ ["input", { "type" : "search", "class" : "jp-authors-search-input", "data-i18n" : "search_authors_placeholder", "data-i18n-without" : "true", "placeholder" : judge_pole.i18n.get("search_authors_placeholder"), "oninput" : (input) => { const query = input.value.trim().toLowerCase(); authors_list_box.querySelectorAll(".jp-check-label").forEach(label => { const nick = label.getAttribute("data-nick") || ""; label.style.display = (!query || nick.includes(query)) ? "flex" : "none"; }); } }], ["div", { "class" : "jp-authors-select-box" }] ]])[0]; const authors_list_box = authors_picker.querySelector(".jp-authors-select-box"); users.forEach(user => { const is_checked = current_author_ids.includes(user.id); const is_ai = user.type === "ai"; const user_label_text = (is_ai ? "🤖 " : "👤 ") + user.nick; const row = judge_pole.html(["label", { "class" : "jp-check-label", "data-nick" : user.nick.toLowerCase() }, [ ["input", { "type" : "checkbox", "name" : "author_ids", "value" : user.id, ...(is_checked ? { "checked" : "checked" } : {}) }], user_label_text ]])[0]; authors_list_box.appendChild(row); }); // 3. Formulario principal const form = judge_pole.html(["form", { "onsubmit" : (form_element, event) => { event.preventDefault(); const selected_authors = []; form_element.querySelectorAll("[name='author_ids']:checked").forEach(input => { selected_authors.push(input.value); }); if(!selected_authors.length){ alert(judge_pole.i18n.get("no_authors_selected")); return; } 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 tags_raw = form_element.querySelector("[name='tags']").value; const tech_raw = form_element.querySelector("[name='technologies']").value; const project_payload = { user_ids : selected_authors, name : form_element.querySelector("[name='name']").value.trim(), avatar : form_element.querySelector("[name='avatar']").value.trim(), description : form_element.querySelector("[name='description']").value.trim(), tags : tags_raw.split(",").map(t => t.trim()).filter(t => t), technologies : tech_raw.split(",").map(t => t.trim()).filter(t => t), links : links }; if(current_project) { judge_pole.db.update_project(current_project.id, project_payload); } else { judge_pole.db.add_project(project_payload); } close_modal(); render_cards(); } }, [ ["div", { "class" : "jp-field jp-field-authors" }, [ ["label", {"data-i18n" : "project_authors"}, judge_pole.i18n.get("project_authors")] ]], ["div", { "class" : "jp-field" }, [ ["label", {"data-i18n" : "project_name"}, judge_pole.i18n.get("project_name")], ["input", { "type" : "text", "name" : "name", "required" : "required", "value" : current_project ? current_project.name : "" }] ]], ["div", { "class" : "jp-field" }, [ ["label", {"data-i18n" : "project_avatar"}, judge_pole.i18n.get("project_avatar")], ["input", { "type" : "text", "name" : "avatar", "placeholder" : "./data/projects/logo.png", "value" : current_project ? current_project.avatar : "" }] ]], ["div", { "class" : "jp-field" }, [ ["label", {"data-i18n" : "project_desc"}, judge_pole.i18n.get("project_desc")], ["textarea", { "name" : "description", "rows" : "3" }, current_project ? current_project.description : ""] ]], ["div", { "class" : "jp-field" }, [ ["label", {"data-i18n" : "project_tags"}, judge_pole.i18n.get("project_tags")], ["input", { "type" : "text", "name" : "tags", "placeholder" : "retro, troll, shader", "value" : current_project ? current_project.tags.join(", ") : "" }] ]], ["div", { "class" : "jp-field" }, [ ["label", {"data-i18n" : "project_tech"}, judge_pole.i18n.get("project_tech")], ["input", { "type" : "text", "name" : "technologies", "placeholder" : "C++, Raylib, WebGL", "value" : current_project ? current_project.technologies.join(", ") : "" }] ]], ["div", { "class" : "jp-field" }, [ ["label", {"data-i18n" : "project_links"}, judge_pole.i18n.get("project_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_project ? "btn_update_project" : "btn_save_project" }, current_project ? judge_pole.i18n.get("btn_update_project") : judge_pole.i18n.get("btn_save_project") ] ]] ]])[0]; // 4. Inyección manual de authors_picker en su campo del form const authors_field = form.querySelector(".jp-field-authors"); authors_field.appendChild(authors_picker); // 5. Inyección del form en el modal body modal_body.appendChild(form); if(current_project && current_project.links && current_project.links.length) { current_project.links.forEach(link => add_link_row(link.name, link.url)); } judge_pole.item_self.appendChild(modal_overlay); }; }; return ProjectsView; })();