popup, modificado materiales, añadido precio a las opciones y añadidos

master
dev 1 year ago
parent 3e6d8dc0fd
commit 32cb5eca27

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 MiB

After

Width:  |  Height:  |  Size: 597 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 842 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 715 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 MiB

After

Width:  |  Height:  |  Size: 674 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

@ -1,12 +1,12 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import './app.css';
import type {ActiveSection} from "./lib/types/types";
import Navegacion from "./lib/components/Navegacion.svelte"
import ProyectoView from "./lib/views/ProyectoView.svelte";
import ConfiguradorView from "./lib/views/ConfiguradorView.svelte";
import SeleccionadosView from "./lib/views/SeleccionadosView.svelte";
import ModalWindow from "./lib/components/ModalWindow.svelte";
import Popup from "./lib/components/Popup.svelte";
import { sectionState } from "./lib/stores/router.svelte";
@ -41,4 +41,6 @@
</main>
<ModalWindow />
<ModalWindow />
<Popup />

@ -15,8 +15,8 @@ body {
@layer utilities {
.bg-azul-s {
/* background-color: #18355D; */
background-color: #4A4A4B;
background-color: #18355D;
/*background-color: #4A4A4B;*/
}
}

@ -104,13 +104,13 @@ export const proyecto : Proyecto = {
'ducha' : {
nombre : 'Baño con Ducha',
ref : 'ducha',
thumb : './imgs/quaddro2/mat/tn_ducha.jpg',
thumb : './imgs/quaddro2/mat/tn_ducha.png',
},
'banera' : {
nombre : 'Baño con Bañera',
ref : 'banera',
thumb : './imgs/quaddro2/mat/tn_banera.jpg',
thumb : './imgs/quaddro2/mat/tn_banera.png',
},
},
@ -146,11 +146,15 @@ export const proyecto : Proyecto = {
nombre : 'Cocina',
imagen : null,
adds : {
'ab' : {
'abr' : {
nombre : 'Cocina abierta',
imagen : './imgs/quaddro2/ads/ccn_diab.jpg',
thumb : './imgs/quaddro2/ads/ccn_diab_tn.png',
hideImg: true
},
'led' : {
nombre : 'Tira led encimera',
precio : +197.84
}
},
opciones: [
{
@ -224,7 +228,12 @@ export const proyecto : Proyecto = {
'kit' : {
nombre : 'Kit Sanitario',
imagen : 'imgs/quaddro2/ads/bnp_diks.jpg',
thumb : 'imgs/quaddro2/ads/bnp_diks_tn.png'
thumb : 'imgs/quaddro2/ads/bnp_diks_tn.png',
precio : +513.47
},
'ptc' : {
nombre : 'Puerta Corredera',
precio : +609.45
}
},
opciones: [
@ -246,15 +255,15 @@ export const proyecto : Proyecto = {
{
nombre : 'Alicatados Ducha',
opciones: [
{
nombre : 'Imitación Madera',
material: 'ducha_madera',
value : 'alma',
},
{
nombre : 'Agar Mate',
material: 'alicatado_agar',
value : 'alag',
},
{
nombre : 'Imitación Madera',
material: 'ducha_madera',
value : 'alma',
}
]
},
@ -279,11 +288,23 @@ export const proyecto : Proyecto = {
// Baño Secundario
'bns' : {
nombre : 'Baño Secundario',
adds : null,
adds : {
'kit' : {
nombre : 'Kit Sanitario',
imagen : 'imgs/quaddro2/ads/bnp_diks.jpg',
thumb : 'imgs/quaddro2/ads/bnp_diks_tn.png',
precio : +513.47
},
'ptc' : {
nombre : 'Puerta Corredera',
precio : +609.45,
texto : 'La opción de puerta corredera solo está disponible en determinadas tipologías de vivienda, consulte la suya',
}
},
imagen : null,
opciones: [
{
nombre : 'Formato',
nombre : 'Ducha / Bañera',
opciones: [
{
nombre: 'Con Ducha',
@ -291,9 +312,10 @@ export const proyecto : Proyecto = {
material: 'ducha',
},
{
nombre: 'Con Bañera',
value : 'diba',
nombre : 'Con Bañera',
value : 'diba',
material: 'banera',
precio : +1924.25
},
]
},

@ -37,6 +37,7 @@ export interface Opcion {
value : string;
material: string;
texto? : string;
precio? : number;
}
@ -49,8 +50,11 @@ export interface OpcionesSet {
export interface Add {
nombre : string;
imagen : string;
thumb : string
imagen? : string;
thumb ? : string;
hideImg? : boolean;
texto? : string;
precio? : number;
}
export interface Estancia {

@ -1,6 +1,5 @@
<script lang="ts">
import {appState, getAvailableGroups, imageSrc, selectedAdds, selections} from "../stores/sel.svelte";
import { openModal } from "../stores/modal.svelte";
import {proyecto} from "../../data/data_proyecto";
import BotonModalMaterial from "./BotonModalMaterial.svelte";

@ -1,11 +1,14 @@
<script lang="ts">
import {formatearPrecio} from "../utils/formaters";
let { nombre,
seleccionada,
onclick,
imagenSrc
} = $props<{ nombre: string, imagenSrc?: string, seleccionada?: boolean, onclick?: () => void }>();
imagenSrc,
precio,
} = $props<{ nombre: string, imagenSrc?: string, precio? : number, seleccionada?: boolean, onclick?: () => void }>();
@ -13,26 +16,35 @@
</script>
<button
type="button"
class="group relative p-2 rounded-lg border transition-all duration-200
class="group relative p-0 rounded-lg border transition-all duration-200 cursor-pointer
flex flex-col h-full text-center
{seleccionada
? 'border-blue-200 bg-blue-50 ring-1 ring-blue-400'
: 'border-gray-400 hover:border-gray-600'
? 'border-blue-200 bg-blue-50 ring-1 ring-blue-200'
: 'border-gray-200 hover:border-gray-400'
}"
aria-pressed={seleccionada}
onclick={onclick}
>
<div class="aspect-[2/1] mb-2">
<img src={imagenSrc} alt={nombre} class="w-full h-full object-cover rounded" />
<div class="p-2">
<div class="aspect-[2/1] mb-2">
<img src={imagenSrc} alt={nombre} class="w-full h-full object-cover rounded" />
</div>
<p class="text-xs font-medium text-gray-700 leading-tight">{nombre}</p>
</div>
<p class="text-xs text-center font-medium text-gray-700 leading-tight">{nombre}</p>
{#if precio}
<p class="w-full bg-gray-100 font-medium text-xs text-gray-800 mt-auto pt-1 pb-1 rounded-b-lg
{ seleccionada ? 'bg-blue-200' : 'bg-gray-200' }"
>
+{formatearPrecio(precio)}
</p>
{/if}
{#if seleccionada}
<div class="absolute -top-1 -right-1 w-5 h-5 bg-blue-600 rounded-full flex items-center justify-center shadow-md">
<div class="absolute -top-1 -right-1 w-5 h-5 bg-green-600 rounded-full flex items-center justify-center shadow-md">
<svg class="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
<path
fill-rule="evenodd"

@ -8,11 +8,8 @@
<button type="button"
class="w-full text-left p-2 rounded-lg border transition-all duration-200 text-primary-700"
class:border-blue-300={seleccionada}
class:bg-blue-50={seleccionada}
class:border-gray-300={!seleccionada}
class:bg-gray-50={!seleccionada}
class="w-full text-left p-2 rounded-lg border transition-all duration-200 text-primary-700 cursor-pointer hover:border-gray-400 hover:bg-gray-100
{ seleccionada ? 'border-blue-300 bg-blue-50' : 'border-gray-300 bg-gray-50' }"
onclick={onclick}
>
<div class="flex items-center space-x-3">

@ -1,5 +1,5 @@
<script lang="ts">
import {openModal} from "../stores/modal.svelte";
import { modalWindow} from "../stores/modal.svelte";
let { nombre, titulo, imagen } = $props <{
@ -9,16 +9,24 @@ import {openModal} from "../stores/modal.svelte";
}>();
</script>
<div role="button"
tabindex="0"
class="group flex items-center justify-between p-2 rounded-lg bg-gray-200 hover:bg-slate-400 cursor-pointer transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-cyan-500 text-gray-900 hover:text-white"
onclick={() => { openModal(imagen , titulo ?? 'Sin Nombre'); }}
onkeydown={(e) => { if (e.key === 'Enter' || e.key === 'Space') { openModal(imagen ?? 'https://placehold.co/400x400', titulo ?? 'Sin Nombre'); } }}
>
<span class="text-xs font-medium">{nombre ?? 'Sin Nombre'}</span>
</div>
{#if imagen}
<div role="button"
tabindex="0"
class="group flex items-center justify-between p-2 rounded-lg bg-gray-200 hover:bg-slate-400 cursor-pointer transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-cyan-500 text-gray-900 hover:text-white"
onclick={() => { modalWindow.open(imagen , titulo ?? 'Sin Nombre'); }}
onkeydown={(e) => { if (e.key === 'Enter' || e.key === 'Space') { modalWindow.open(imagen ?? 'https://placehold.co/400x400', titulo ?? 'Sin Nombre'); } }}
>
<span class="text-xs font-medium">{nombre ?? 'Sin Nombre'}</span>
</div>
{:else}
<div class="group flex items-center justify-between p-2 rounded-lg bg-white-200 border border-blue-200">
<span class="text-xs font-medium">{nombre ?? 'Sin Nombre'}</span>
</div>
{/if}

@ -0,0 +1,59 @@
<script lang="ts">
import {formatearPrecio} from "../utils/formaters";
import type {Add} from "../../data/data_types";
import {showWarning} from "../stores/popup.svelte";
let { add, seleccionada, onclick } = $props<{
add: Add,
seleccionada: boolean,
onclick?: (event: Event) => void}>();
function handleClick (event: Event) {
console.log('seleccionada ', seleccionada, add.texto);
if (!seleccionada && add.texto) {
console.log("debe de aparecer el popuip")
showWarning(add.texto)
}
onclick(event);
}
</script>
<button
type="button"
class="group relative p-2 rounded-lg border transition-all duration-200 w-full cursor-pointer
{seleccionada
? 'border-blue-200 bg-blue-50 ring-1 ring-blue-200'
: 'border-gray-200 hover:border-gray-400'
}"
aria-pressed={seleccionada}
onclick={(e) => handleClick(e)}
>
<div class="flex justify-between items-center w-full p-2 rounded-lg group">
<!-- Nombre a la izquierda -->
<span class="font-medium text-xs text-gray-800 group-hover:text-gray-900 truncate">{add.nombre}</span>
<!-- Precio a la derecha -->
{#if add.precio}
<span class="font-medium text-xs text-gray-800 group-hover:text-gray-900">+{formatearPrecio(add.precio)}</span>
{/if}
</div>
{#if seleccionada}
<div class="absolute -top-1 -right-1 w-5 h-5 bg-green-600 rounded-full flex items-center justify-center shadow-md">
<svg class="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
<path
fill-rule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clip-rule="evenodd"
/>
</svg>
</div>
{/if}
</button>

@ -1,30 +1,27 @@
<script lang="ts">
import { getModalInfo, closeModal } from '../stores/modal.svelte';
import { modalWindow } from '../stores/modal.svelte';
let isLoading = $state(true);
$effect(() => {
const info = getModalInfo();
const info = modalWindow.info;
if (info?.imagen) {
isLoading = true;
}
});
function handleImageLoad() {
isLoading = false;
}
function handleImageLoad() { isLoading = false; }
function handleOverlayClick() { modalWindow.close(); }
function handleOverlayClick() {
closeModal();
}
function handleKeydown(e: KeyboardEvent) { if (e.key === 'Escape') modalWindow.close(); }
function handleKeydown(e: KeyboardEvent) {
if (e.key === 'Escape') closeModal();
}
</script>
{#if getModalInfo()}
{#if modalWindow.info }
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
@ -44,7 +41,7 @@
role="document"
>
<h2 class="text-2xl font-bold text-center mb-4 text-gray-800">
Opción de {getModalInfo()!.texto}
Opción de {modalWindow.info.texto}
</h2>
{#if isLoading}
@ -54,7 +51,7 @@
{/if}
<img
src={getModalInfo()!.imagen}
src={modalWindow.info.imagen}
alt="Imagen ampliada"
class="w-full max-h-[70vh] object-contain rounded-lg mb-4 transition-opacity duration-500"
onload={handleImageLoad}
@ -64,7 +61,7 @@
<button
type="button"
class="w-full px-4 py-2 bg-azul-s text-white rounded-lg hover:bg-blue-900 focus:ring-4 focus:ring-blue-300 font-medium transition-all duration-200"
onclick={closeModal}
onclick={() => modalWindow.close()}
>
Cerrar
</button>

@ -3,7 +3,7 @@
import { proyecto } from "../../data/data_proyecto";
import {dataApp} from "../../data/data_app";
import { sectionState, type ActiveSection } from "../stores/router.svelte";
import {closeModal} from "../stores/modal.svelte";
import { modalWindow } from "../stores/modal.svelte";
/*
let {
@ -29,7 +29,7 @@
}
function handleKeydown(e: KeyboardEvent) {
if (e.key === 'Escape') closeModal();
if (e.key === 'Escape') modalWindow.close();
}

@ -0,0 +1,51 @@
<script lang="ts">
import {popupState, hideWarning} from "../stores/popup.svelte";
const handleClose = () => {
hideWarning();
};
const handleKeyClose = (e: KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
handleClose();
}
};
</script>
<div
class="fixed inset-0 z-50 flex items-center justify-center p-4 transition-opacity duration-300"
style="background-color: rgba(0, 0, 0, 0.5);"
class:hidden={!popupState.isVisible}
onclick={handleClose}
role="button"
tabindex="0"
onkeydown={handleKeyClose}
>
<div
class="relative max-w-lg w-full p-6 bg-yellow-100 border-l-4 border-yellow-500 rounded-lg shadow-xl"
role="alert presentation"
onclick={(e) => { e.stopPropagation() }}
>
<div class="flex items-start">
<svg class="h-6 w-6 text-yellow-500 mr-4 mt-1 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.3 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="text-yellow-800">
<h3 class="text-lg font-semibold mb-1">Advertencia Importante</h3>
<p class="text-sm leading-relaxed">{popupState.message}</p>
</div>
<button
type="button"
class="absolute top-3 right-3 text-yellow-600 hover:text-yellow-800 transition-colors duration-150"
aria-label="Cerrar advertencia"
onclick={handleClose}
>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>

@ -9,6 +9,7 @@ export type ModalInfo = {
// Estado interno del modal usando $state
let modalInfo = $state<ModalInfo>(null);
/*
// Exportar una función getter para acceder al estado
export function getModalInfo() { return modalInfo; }
@ -20,3 +21,10 @@ export function closeModal() {
modalInfo = null;
}
*/
export const modalWindow = {
get info() : ModalInfo { return modalInfo; },
close : () => modalInfo = null,
open : (imagen: string, texto: string) => { modalInfo = { imagen, texto } }
}

@ -0,0 +1,26 @@
// 1. Define la forma del estado
interface PopupState {
isVisible: boolean;
message: string;
}
export const popupState = $state<PopupState>({
isVisible: false,
message: ''
});
// 3. Define funciones para manipular el estado
export const showWarning = (message: string) => {
popupState.message = message;
popupState.isVisible = true;
};
export const hideWarning = () => {
popupState.isVisible = false;
};

@ -26,7 +26,7 @@ export const sectionState = {
},
sectionsInfo: [
{ id: 'proyecto', label: '', menu: false },
{ id: 'configurador', label: 'Personalizacion', menu: true },
{ id: 'configurador', label: 'Personalización', menu: true },
{ id: 'materiales', label: 'Mi Selección', menu: true },
] as SectionInfo[],
}

@ -0,0 +1,11 @@
export const formatearPrecio = (precio?: number): string => {
if (precio === undefined) return 'No disponible';
return new Intl.NumberFormat('es-ES', {
style: 'currency',
currency: 'EUR',
}).format(precio);
};

@ -2,12 +2,10 @@
import BotonAcabado from "../components/BotonAcabado.svelte";
import BotonEstancia from "../components/BotonEstancia.svelte";
import {selections, appState, getAvailableGroups, imageSrc, selectedAdds} from '../stores/sel.svelte.js';
import { openModal } from "../stores/modal.svelte.js";
import {proyecto} from '../../data/data_proyecto';
import AcabadosEstancia from "../components/AcabadosEstancia.svelte";
import BotonOpcion from "../components/BotonOpcion.svelte";
const DEFAULT_IMAGE = "https://placehold.co/80x40/cccccc/333333?text=N/A";
@ -62,6 +60,7 @@
{#each grupo.opciones as opcion }
<BotonAcabado
nombre={opcion.nombre}
precio={opcion.precio}
imagenSrc={opcion.material && proyecto.materiales[opcion.material]?.thumb || DEFAULT_IMAGE }
seleccionada={selections[appState.estanciaSeleccionada]?.[grupo.nombre] === opcion.value}
onclick={() => { selections[appState.estanciaSeleccionada][grupo.nombre] = opcion.value; }}
@ -74,33 +73,12 @@
{#if proyecto.estancias[appState.estanciaSeleccionada].adds}
{@const adds = proyecto.estancias[appState.estanciaSeleccionada].adds }
{#if adds && typeof adds === 'object' && !Array.isArray(adds) }
<div class="mt-8 border-t border-gray-200 pt-6">
<h3 class="text-sm font-medium text-gray-700 mb-4 uppercase tracking-wide">Otras
Opciones</h3>
<div class="space-y-4">
<div class="mt-8 border-t border-gray-200 pt-6 pb-6">
<h3 class="text-sm font-medium text-gray-700 mb-4 uppercase tracking-wide">Otras Opciones</h3>
<div class="space-y-2">
{#each Object.entries(adds) as [addKey, add]}
<div class="bg-white rounded-lg shadow-sm p-4 space-y-3">
<label class="flex items-center cursor-pointer group">
<input
type="checkbox"
checked={selectedAdds[appState.estanciaSeleccionada]?.[addKey] ?? false}
onchange={() => (selectedAdds[appState.estanciaSeleccionada][addKey] = !selectedAdds[appState.estanciaSeleccionada][addKey])}
class="w-5 h-5 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/>
<span class="ml-3 text-sm font-medium text-gray-800 group-hover:text-gray-900">{add.nombre}</span>
</label>
<button
type="button"
class="p-0 border-none bg-transparent cursor-pointer"
onclick={() => openModal(add.imagen,add.nombre,)}
>
<img
src={add.thumb}
alt={`Vista previa de ${add.nombre}`}
class="mt-2 w-[100px] h-[100px] object-cover rounded-lg shadow-md hover:opacity-90 transition-opacity duration-200"
/>
</button>
</div>
{@const seleccionada = selectedAdds[appState.estanciaSeleccionada]?.[addKey] ?? false}
<BotonOpcion add={add} seleccionada={seleccionada} onclick={() => (selectedAdds[appState.estanciaSeleccionada][addKey] = !selectedAdds[appState.estanciaSeleccionada][addKey])} />
{/each}
</div>
</div>

Loading…
Cancel
Save

Powered by TurnKey Linux.