|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 2.5 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 37 KiB |
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "EBROSA · Quaddro2",
|
||||
"short_name": "Quaddro2",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#ffffff",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicon-64.png",
|
||||
"sizes": "64x64",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/favicon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/favicon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,165 @@
|
||||
<script lang="ts">
|
||||
import { appState } from "../data/AppState.svelte";
|
||||
import type { OptionReference } from "../data/DataConfigTypes";
|
||||
import {dataConfig} from "../data/DataConfig";
|
||||
import {formatPrice} from "../utils/formats";
|
||||
import {popupMessage} from "../data/PopsState.svelte";
|
||||
|
||||
const imgPath : string = dataConfig.sources.imgValues
|
||||
|
||||
let {
|
||||
optionRef,
|
||||
groupId,
|
||||
viewId,
|
||||
isDisabled = false,
|
||||
isSelected = false
|
||||
} = $props<{
|
||||
optionRef: OptionReference;
|
||||
groupId: string;
|
||||
viewId: string;
|
||||
isDisabled?: boolean;
|
||||
isSelected?: boolean;
|
||||
}>();
|
||||
|
||||
const textWithCost = dataConfig.texts.with_cost;
|
||||
|
||||
// Forzar que estos sean reactivos
|
||||
const optionValue = $derived(appState.getOptionValue(optionRef.id));
|
||||
const consultation = $derived(appState.requiresConsultation(viewId, groupId, optionRef.id));
|
||||
|
||||
const imgOptSrc = $derived(imgPath + optionValue.thumb)
|
||||
|
||||
// Añadir log solo cuando isSelected cambia
|
||||
$effect(() => {
|
||||
if (isSelected) {
|
||||
console.log(`✓ ${optionRef.id} SELECCIONADO en ${viewId}/${groupId}`);
|
||||
}
|
||||
});
|
||||
|
||||
function handleClick() {
|
||||
if (isSelected) {
|
||||
return;
|
||||
}
|
||||
console.log('🔵 CLICK:', { viewId, groupId, optionId: optionRef.id });
|
||||
if (consultation.message) {
|
||||
popupMessage.open(consultation.message);
|
||||
}
|
||||
appState.setOption(viewId, groupId, optionRef.id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Obtener el precio desde optionRef.amount
|
||||
const hasPrice = $derived(
|
||||
optionRef.amount !== undefined &&
|
||||
optionRef.amount === textWithCost || (
|
||||
typeof optionRef.amount === 'number' &&
|
||||
optionRef.amount > 0)
|
||||
);
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<button
|
||||
class="relative border-2 rounded-lg p-3 text-left transition-all hover:shadow-md flex flex-col items-center"
|
||||
class:border-green-600={isSelected}
|
||||
class:ring-offset-1={isSelected}
|
||||
class:ring-green-600={isSelected}
|
||||
|
||||
|
||||
class:border-gray-200={!isSelected}
|
||||
|
||||
class:cursor-not-allowed={isDisabled}
|
||||
class:cursor-pointer={!isDisabled && !isSelected}
|
||||
onclick={handleClick}
|
||||
>
|
||||
|
||||
<!-- Warning indicator (simple) -->
|
||||
|
||||
{#if consultation.message}
|
||||
<div class="absolute -top-3 -left-1 w-5 h-5 rounded-full flex items-center justify-center text-white text-s font-bold shadow-md p-3">
|
||||
⚠
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
<!-- Selected indicator (simple) -->
|
||||
{#if isSelected}
|
||||
<div class="absolute -top-1 {consultation.message ? '-right-1' : '-right-1'} w-5 h-5 bg-green-600 rounded-full flex items-center justify-center text-white text-xs font-bold shadow-md">
|
||||
✓
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Imagen -->
|
||||
{#if optionValue?.thumb}
|
||||
<img
|
||||
class="h-20 object-cover mb-2"
|
||||
src={imgOptSrc}
|
||||
alt={optionValue.name}
|
||||
|
||||
/>
|
||||
{:else}
|
||||
<!--
|
||||
<div class="w-full h-20 bg-gray-100 rounded mb-2 flex items-center justify-center">
|
||||
<span class="text-gray-400 text-xs">Sin imagen</span>
|
||||
</div>
|
||||
-->
|
||||
{/if}
|
||||
|
||||
<!-- Nombre -->
|
||||
<div class="font-medium text-xs mb-1 text-center">
|
||||
{optionValue?.name || optionRef.id}
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ✅ Badge "No disponible" para opciones deshabilitadas -->
|
||||
{#if isDisabled}
|
||||
<div class="absolute -top-2 -right-1 w-5 h-5 rounded-full flex items-center justify-center text-white text-s font-bold shadow-md">
|
||||
🚫
|
||||
</div>
|
||||
<!--
|
||||
<div class="absolute top-1 right-1 bg-red-500 text-white px-2 py-0.5 rounded-full text-xs font-semibold">
|
||||
🚫
|
||||
</div>
|
||||
-->
|
||||
{/if}
|
||||
|
||||
<!-- Texto adicional -->
|
||||
<!--
|
||||
{#if optionValue?.text}
|
||||
<div class="text-xs text-gray-600 mb-1">
|
||||
{optionValue.text}
|
||||
</div>
|
||||
{/if}
|
||||
-->
|
||||
|
||||
|
||||
<!-- Precio -->
|
||||
{#if hasPrice}
|
||||
<div class="text-center mt-1">
|
||||
<span class="w-full inline-block bg-gray-200 px-2 py-1 text-gray-800 text-xm font-semibold rounded">
|
||||
+ {formatPrice(optionRef.amount)}
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
<!-- Badge consultar -->
|
||||
<!--
|
||||
{#if consultation.required}
|
||||
<span class="inline-block px-2 py-0.5 bg-yellow-100 text-yellow-800 text-xs rounded mt-1">
|
||||
Consultar
|
||||
</span>
|
||||
{/if}
|
||||
-->
|
||||
</button>
|
||||
|
||||
<!-- Advertencia -->
|
||||
<!--
|
||||
{#if consultation.message && isSelected}
|
||||
<div class="col-span-2 p-3 bg-yellow-50 border-l-4 border-yellow-400 text-xs text-yellow-800 rounded">
|
||||
⚠️ {consultation.message}
|
||||
</div>
|
||||
{/if}
|
||||
-->
|
||||