$(function() { const galleryMain = $("#adGalleryMain"); let galleryPanFrame = null; let galleryPanState = { image: null, currentX: 0, currentY: 0, targetX: 0, targetY: 0, width: 0, height: 0 }; $(".gallery-thumb").on("click", function() { const thumb = $(this); const imageUrl = thumb.data("gallery-src"); const imageAlt = thumb.data("gallery-alt") || "Foto do anúncio"; const mainImage = $("#adGalleryMain img"); if (!imageUrl || !mainImage.length) { return; } mainImage.attr("src", imageUrl); mainImage.attr("alt", imageAlt); resetGalleryZoom(galleryMain, mainImage); $(".gallery-thumb").removeClass("active"); thumb.addClass("active"); }); galleryMain.on("mousemove", function(event) { const main = $(this); const image = main.find("img"); if (!image.length) { return; } const pan = getGalleryImagePan(this, image[0], event); galleryPanState.image = image; galleryPanState.targetX = pan.x; galleryPanState.targetY = pan.y; galleryPanState.width = pan.width; galleryPanState.height = pan.height; if (!galleryPanFrame) { galleryPanFrame = window.requestAnimationFrame(animateGalleryPan); } main.addClass("is-zooming"); }); galleryMain.on("mouseleave", function() { if (galleryPanFrame) { window.cancelAnimationFrame(galleryPanFrame); galleryPanFrame = null; } galleryPanState.image = null; galleryPanState.currentX = 0; galleryPanState.currentY = 0; galleryPanState.targetX = 0; galleryPanState.targetY = 0; resetGalleryZoom($(this), $(this).find("img")); }); galleryMain.on("dragstart", "img", function(event) { event.preventDefault(); }); $(document).on("click", ".ad-admin-action", async function(event) { event.preventDefault(); const button = $(this); const publicId = button.data("ad-id"); const action = button.data("ad-action"); if (button.prop("disabled") || button.data("loading")) { return; } if (!publicId || action !== "block") { popZ("Ação administrativa inválida para este anúncio.", "warning"); return; } const confirmed = await confirmAdAdminAction({ title: "Bloquear anúncio?", text: "O anúncio será removido do catálogo público e ficará marcado como bloqueado.", confirmButtonText: "Sim, bloquear" }); if (!confirmed) { return; } button.data("loading", true).prop("disabled", true).attr("aria-busy", "true"); $.ajax({ type: "POST", url: "/api/admin/ads", dataType: "json", data: { action: "update_status", public_id: publicId, ad_action: action }, success: function(response) { handleAdAdminResponse(response); }, error: function(jqXhr) { handleAdAdminResponse(jqXhr.responseJSON || {}); }, complete: function() { button.data("loading", false).prop("disabled", false).removeAttr("aria-busy"); } }); }); function animateGalleryPan() { if (!galleryPanState.image || !galleryPanState.image.length) { galleryPanFrame = null; return; } galleryPanState.currentX += (galleryPanState.targetX - galleryPanState.currentX) * 0.14; galleryPanState.currentY += (galleryPanState.targetY - galleryPanState.currentY) * 0.14; applyGalleryPan(galleryPanState); if ( Math.abs(galleryPanState.targetX - galleryPanState.currentX) > 0.2 || Math.abs(galleryPanState.targetY - galleryPanState.currentY) > 0.2 ) { galleryPanFrame = window.requestAnimationFrame(animateGalleryPan); return; } galleryPanState.currentX = galleryPanState.targetX; galleryPanState.currentY = galleryPanState.targetY; applyGalleryPan(galleryPanState); galleryPanFrame = null; } }); function applyGalleryPan(state) { if (!state || !state.image || !state.image.length) { return; } state.image.css({ objectPosition: "center center", width: state.width + "px", height: state.height + "px", transform: "translate3d(-50%, -50%, 0) translate3d(" + state.currentX + "px, " + state.currentY + "px, 0)" }).addClass("is-panning"); } function resetGalleryZoom(galleryMain, image) { galleryMain.removeClass("is-zooming"); if (!image || !image.length) { return; } image.css({ objectPosition: "center center", width: "", height: "", transform: "" }).removeClass("is-panning"); } function getGalleryImagePan(container, image, event) { const bounds = container.getBoundingClientRect(); const naturalWidth = image.naturalWidth || bounds.width; const naturalHeight = image.naturalHeight || bounds.height; const containerRatio = bounds.width / bounds.height; const imageRatio = naturalWidth / naturalHeight; let renderedWidth = bounds.width; let renderedHeight = bounds.height; if (imageRatio > containerRatio) { renderedHeight = bounds.height; renderedWidth = renderedHeight * imageRatio; } else { renderedWidth = bounds.width; renderedHeight = renderedWidth / imageRatio; } const overflowX = Math.max(0, renderedWidth - bounds.width); const overflowY = Math.max(0, renderedHeight - bounds.height); const mouseX = clampGalleryRatio((event.clientX - bounds.left) / bounds.width); const mouseY = clampGalleryRatio((event.clientY - bounds.top) / bounds.height); return { width: Math.round(renderedWidth), height: Math.round(renderedHeight), x: overflowX ? roundGalleryPan((0.5 - mouseX) * overflowX * 0.88) : 0, y: overflowY ? roundGalleryPan((0.5 - mouseY) * overflowY * 0.88) : 0 }; } function clampGalleryRatio(value) { return Math.max(0.08, Math.min(0.92, value)); } function roundGalleryPan(value) { return Math.round(value * 10) / 10; } async function confirmAdAdminAction(options) { if (typeof Swal === "undefined") { return window.confirm(options.title || "Deseja continuar?"); } const result = await Swal.fire({ icon: "warning", title: options.title, text: options.text, showCancelButton: true, confirmButtonText: options.confirmButtonText || "Confirmar", cancelButtonText: "Cancelar", reverseButtons: true, confirmButtonColor: "#e63946", cancelButtonColor: "#6b7785" }); return result.isConfirmed; } function handleAdAdminResponse(response) { switch (response.result) { case "admin_ad_status_updated": popZ("Anúncio bloqueado com sucesso.", "success"); setTimeout(function() { window.location.href = "/anuncios"; }, 700); break; case "admin_ad_not_found": popZ("Anúncio não encontrado.", "warning"); break; case "invalid_admin_ad_status_transition": popZ("Este anúncio não permite essa alteração.", "warning"); break; case "invalid_admin_ad_action": popZ("Ação inválida para este anúncio.", "warning"); break; case "admin_ad_status_update_failed": popZ("Não foi possível bloquear o anúncio. Tente novamente.", "error"); break; default: popZ("Não foi possível executar a ação no anúncio.", "error"); break; } }