dev 8 months ago
parent d9a3592096
commit c40a0ae55f

@ -272,28 +272,33 @@
const priceWidth = priceStr ? pdfDoc.getTextWidth(priceStr) : 0;
// Si label es demasiado largo, recortamos con splitTextToSize y dibujamos en varias líneas
const lineHeight = fontSize * 0.45; // controla aquí el espaciado fino
if (labelWidth + priceWidth + 6 > maxWidth) {
// dividir label en líneas que dejen espacio para precio en la última línea
const availableForLabel = maxWidth - (priceWidth ? priceWidth + 6 : 0);
const labelLines = pdfDoc.splitTextToSize(label, availableForLabel);
for (let i = 0; i < labelLines.length; i++) {
const line = labelLines[i];
const lineY = y + i * (fontSize * 0.6 + 2);
pdfDoc.text(line, x, lineY);
// si es la última línea y hay precio, pintarlo a la derecha
labelLines.forEach((line: string, i:number) => {
const lineY = y + i * lineHeight;
pdfDoc.text(line, x + iconWidth, lineY);
if (i === labelLines.length - 1 && priceStr) {
if (priceColor) pdfDoc.setTextColor(priceColor.r, priceColor.g, priceColor.b);
if (priceFont && priceFontSize) pdfDoc.setFont(priceFont, "normal"), pdfDoc.setFontSize(priceFontSize);
if (priceFont && priceFontSize) {
pdfDoc.setFont(priceFont, "normal");
pdfDoc.setFontSize(priceFontSize);
}
pdfDoc.text(priceStr, x + maxWidth - priceWidth, lineY);
// reset font/color
pdfDoc.setFont(font, "normal");
pdfDoc.setFontSize(fontSize);
pdfDoc.setTextColor(0, 0, 0);
}
}
return y + labelLines.length * (fontSize * 0.6 + 2);
});
return y + labelLines.length * lineHeight;
} else {
// etiqueta en una línea y precio alineado a la derecha si existe
pdfDoc.text(label, x+iconWidth, y);

@ -1,5 +1,3 @@
<script lang="ts">
import { fade, scale } from 'svelte/transition';
import type {DownloadState} from "../data/DownloadTypes";
@ -7,34 +5,45 @@
let {
downloadState,
isVisible ,
isVisible,
message,
logoProject,
logoCompany,
text,
} = $props< {
tipologias = ['A1', 'P1', 'P2'],
onTipologiaSelected,
onCancel
} = $props<{
downloadState: DownloadState;
isVisible : boolean;
message : string ;
logoProject : string ;
logoCompany? : string ;
text : string
}> ();
isVisible: boolean;
message: string;
logoProject: string;
logoCompany?: string;
text: string;
tipologias?: string[];
onTipologiaSelected: (tipologia: string) => void;
onCancel: () => void;
}>();
let selectedTipologia = $state(tipologias[0] || 'A1');
let stateText = $derived (
let stateText = $derived(
downloadState === 'creating' ? 'Creando documento PDF' :
downloadState === 'downloading' ? 'Descargando documento' :
downloadState === 'success' ? '✅ PDF Generado correctamente' :
''
);
let showSpin = $derived (
let showSpin = $derived(
downloadState === 'creating' || downloadState === 'downloading'
);
// Determinar si mostrar selector o estado de generación
let showSelector = $derived(downloadState === 'idle');
function handleGenerate() {
onTipologiaSelected(selectedTipologia);
}
</script>
{#if isVisible}
@ -46,7 +55,7 @@
<!-- Contenedor del popup con entrada tipo zoom -->
<div
transition:scale={{ duration: 250, easing: cubicOut }}
class="bg-white rounded-2xl px-10 py-8 w-[420px] text-center shadow-2xl flex flex-col items-center space-y-6 "
class="bg-white rounded-2xl px-10 py-8 w-[420px] text-center shadow-2xl flex flex-col items-center space-y-6"
>
<!-- Bloque superior: logos y título -->
<div class="flex flex-col leading-none">
@ -61,28 +70,74 @@
</h2>
</div>
<!-- Contenido del estado -->
{#if downloadState !== 'error' && downloadState !== 'idle' }
<!-- SELECTOR DE TIPOLOGÍA (solo si está idle) -->
{#if showSelector}
<div class="w-full space-y-4">
<div class="text-left">
<label for="tipologia" class="block text-sm font-semibold text-gray-700 mb-2">
Seleccione la tipología de vivienda:
</label>
<select
id="tipologia"
bind:value={selectedTipologia}
class="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-600 focus:border-transparent text-lg font-medium text-gray-700 bg-white"
>
{#each tipologias as tip}
<option value={tip}>{tip}</option>
{/each}
</select>
</div>
<!-- Botones -->
<div class="flex gap-3 mt-6">
<button
type="button"
onclick={onCancel}
class="flex-1 px-4 py-3 bg-gray-200 hover:bg-gray-300 text-gray-700 font-semibold rounded-lg transition-colors"
>
Cancelar
</button>
<button
type="button"
onclick={handleGenerate}
class="flex-1 px-4 py-3 bg-green-600 hover:bg-green-700 text-white font-semibold rounded-lg transition-colors shadow-md"
>
Generar PDF
</button>
</div>
</div>
<!-- ESTADO DE GENERACIÓN/DESCARGA/ÉXITO -->
{:else if downloadState !== 'error'}
<div class="flex items-center space-x-3 mt-4 w-full">
<!-- Contenedor para reservar espacio del spinner -->
<div class="w-10 h-10 flex-shrink-0">
{#if showSpin}
<div class="w-10 h-10 border-4 border-gray-200 border-t-primary-700 border-t-opacity-75 rounded-full animate-spin"
<div class="w-10 h-10 border-4 border-gray-200 border-t-green-700 rounded-full animate-spin"
></div>
{/if}
</div>
<!-- Texto -->
<div class="text-primary-700 font-medium text-lg">
<div class="text-green-700 font-medium text-lg">
{stateText}
</div>
</div>
<!-- ESTADO DE ERROR -->
{:else}
<div class="text-red-600 text-lg font-semibold mt-4">
<div class="space-y-4">
<div class="text-red-600 text-lg font-semibold">
❌ Error al generar PDF
</div>
<p class="text-gray-600 text-sm mt-2 max-w-xs">{message}</p>
<p class="text-gray-600 text-sm max-w-xs">{message}</p>
<button
type="button"
onclick={onCancel}
class="px-6 py-2 bg-gray-200 hover:bg-gray-300 text-gray-700 font-semibold rounded-lg transition-colors"
>
Cerrar
</button>
</div>
{/if}
</div>
</div>

@ -322,7 +322,7 @@ export const dataConfig : DataConfig = {
salon: {
id: 'salon',
name: 'Salón',
text: '',
text: 'Arquitectura que respira y se adapta a tu estilo. Disfruta de un salón bañado por la luz natural gracias a sus amplios ventanales, donde el diseño de vanguardia se une al confort absoluto del suelo radiante-refrescante y el control inteligente de tu hogar mediante domótica avanzada',
help: '',
options: [],
hotspots: [
@ -339,7 +339,7 @@ export const dataConfig : DataConfig = {
bano1: {
id: 'bano1',
name: 'Baño principal',
text: '',
text: 'Un santuario privado diseñado para el relax sensorial. Con texturas que abrazan y acabados de alta gama, el baño principal incluye detalles de excelencia como su espejo retroiluminado antivaho y columna termostática, garantizando una experiencia de bienestar única cada día',
help: '',
options: [
// Grupo
@ -416,7 +416,7 @@ export const dataConfig : DataConfig = {
bano2: {
id: 'bano2',
name: 'Baño secundario',
text: '',
text: 'Elegancia y versatilidad para el día a día de toda la familia. Equipado con materiales resistentes de primera calidad, bañera y mampara incluida, este espacio refleja el equilibrio ideal entre durabilidad y diseño que define a OPEN-BOX',
help: '',
options: [
// Grupo
@ -479,7 +479,7 @@ export const dataConfig : DataConfig = {
aseo: {
id: 'aseo',
name: 'Aseo',
text: '',
text: 'Incluso los espacios más pequeños reflejan la identidad icónica del proyecto. Un aseo de cortesía con líneas limpias y sanitarios de cerámica vitrificada, pensado para impresionar a tus invitados con la sofisticación y el cuidado por el detalle de Ebrosa',
help: '',
options: [
// Grupo
@ -530,7 +530,7 @@ export const dataConfig : DataConfig = {
cocina: {
id: 'cocina',
name: 'Cocina',
text: '',
text: 'Donde la tecnología y la estética se alían en perfecta armonía. Tu cocina se entrega totalmente equipada con electrodomésticos de última generación y elegantes encimeras porcelánicas, creando un espacio funcional y moderno diseñado para perdurar',
help: '',
options: [
// Grupo

@ -0,0 +1,12 @@
// Imágenes generadas automáticamente
export const precaucionIconB64 = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAByUlEQVR4nOxVu4sTQRj/fZMzuUviwXnnq7hCuUY8Ym0364OgMRGUkN5XESwNNoKoKAoBtfZP0MoiCBamswo2ATGNhYUa8n6sMbr7SUYjCftMMIVwPxh29pv5PWZ2hhVYMHYMPLHkd+Kn4+mVSFDPj/r9YfjG5tvn3/6pQTQ0uMeg7KgfCek6gJwfHvmZ1JapLQNmGUBIFRhDQ4jY3jcvP3hxfX0DA/zkr/jvWMEAm3k/XM8V1GTilAC9Vmn2b6ia+bWmngzzzHqx8GpuA5ZyqYnoOwDbo/fVZ49UvXP15njK+7Xu52NUKv1w0nDdohYi18fiKk14WbUJHGntPph103A0aMfjexh0y40MtU243ZHJjZkNjO+77gNY9zIAsPaTcHcmg7qWPArgiiVttw/u9KwE5muNk6mYnZbtRRNsPmaQZaybe+AUNABDHeUTniuoa8mLDDptq3J4UzV7sNbUEhdcDTidDhLzQ6eY4csZrFzKOA2DmfIfpZw6ZlPb0Kjq54loy0mgd+epo/gfHFpF9ByAF7YGQlCFGQMAy3Zss970MhhQQFQmC5abXJVnDwjQtpeSbQBweV+x8GUe7tz4/3+ZCzf4FQAA//8XSHr/N7SpsQAAAABJRU5ErkJggg=="
export const advertenciaIconB64 = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAABlUlEQVR4nOyVvWtTURjGf+feEOONix9DwEkIiB/EQcE5omCIIKg4OYqDOOomCIpbof07QjukTSDQoXOXtkNKaZZOpe3WoUnTpslb7hfNx7m594ZmKPSBAznvOe/ze/Oec7gGU9Y1IFSJqBulxE0sZpxJi5/qM8eXCsDiH/Dd+90CfkVJU1E2SZksJnXghhc6xSSnCmyH5UY7A5O5PnNbSc68doUo9B9IhdfAsjNJ3XeD7V0/u6CK1CYGyAoJmqwDT+1596XrZa6+9bdskeGZekEnyGN8i5r88M1d51vuuNAj9ryDjwuQGneA32MLwOnBH1niXmwAXf4Dd0MBcBvF31gAWeQJwteRYjuHztDom5TJ6Rb0D81gVrdmbHwJKtT0rvKrUavh6qt8BN7oXCT90BkBykuFD8PBgWsqJZJYbAJZnUPv+QKIYKx9CoLskOaxytP2A4NtsHgfZG7LqI+9kbYecMQ7YF4PEBooh57Spp8chAHa9Gj0B0ZeslTJIH2PK44UdVVkf6LcSXX1P5lTB5wHAAD//0kDVOGS5cmMAAAAAElFTkSuQmCC"
Loading…
Cancel
Save

Powered by TurnKey Linux.