Init
This commit is contained in:
276
frontend/src/App.svelte
Normal file
276
frontend/src/App.svelte
Normal file
@@ -0,0 +1,276 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { API_BASE, ENABLE_POLLING } from './config';
|
||||
import { getCiStatus, getMenu, getPrices } from './services/api';
|
||||
import { prettify } from './utils/text';
|
||||
|
||||
let menu = null;
|
||||
let prices = [];
|
||||
let loadingMenu = true;
|
||||
let loadingPrices = true;
|
||||
let errorMessage = '';
|
||||
let ciStatus = null;
|
||||
let loadingCiStatus = true;
|
||||
|
||||
async function fetchMenu() {
|
||||
loadingMenu = true;
|
||||
try {
|
||||
menu = await getMenu();
|
||||
} catch (error) {
|
||||
errorMessage = `Hoy no cafete (good ending): ${error.message}`;
|
||||
} finally {
|
||||
loadingMenu = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchPrices() {
|
||||
loadingPrices = true;
|
||||
try {
|
||||
prices = await getPrices();
|
||||
} catch (error) {
|
||||
errorMessage = `No pudimos cargar los precios: ${error.message}`;
|
||||
} finally {
|
||||
loadingPrices = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchCiStatus() {
|
||||
loadingCiStatus = true;
|
||||
try {
|
||||
ciStatus = await getCiStatus();
|
||||
} catch (error) {
|
||||
errorMessage = `No se pudo obtener el estado del sistema: ${error.message}`;
|
||||
} finally {
|
||||
loadingCiStatus = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadInitialData() {
|
||||
await Promise.all([fetchMenu(), fetchPrices(), fetchCiStatus()]);
|
||||
}
|
||||
onMount(() => {
|
||||
loadInitialData();
|
||||
|
||||
if (!ENABLE_POLLING) return;
|
||||
|
||||
const pricesInterval = setInterval(fetchPrices, 8000);
|
||||
const ciInterval = setInterval(fetchCiStatus, 10000);
|
||||
|
||||
return () => {
|
||||
clearInterval(pricesInterval);
|
||||
clearInterval(ciInterval);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<section class="hero">
|
||||
<div class="hero-copy">
|
||||
<p class="eyebrow">Taller CI/CD con Jenkins</p>
|
||||
<h1>UwU</h1>
|
||||
<p class="lede">
|
||||
FastAPI + Svelte para pipelines CI/CD. No se nos ha ocurrido nada mejor para el
|
||||
taller así que hemos hecho un proyectito basado en una cafetería que para nada nada
|
||||
está inspirada en la de nuestra querida escuela.
|
||||
</p>
|
||||
<div class="actions">
|
||||
<button on:click={() => fetchMenu()} class="ghost">Refrescar menú</button>
|
||||
<button on:click={() => fetchPrices()}>Recalcular desayunos</button>
|
||||
</div>
|
||||
<p class="meta">
|
||||
Backend: {API_BASE} · Endpoints: /menu · /prices · /prices/:item · /health
|
||||
</p>
|
||||
</div>
|
||||
<div class="price-hero">
|
||||
<p class="pill">Convenio universitario</p>
|
||||
{#if menu}
|
||||
<div class="price-stack">
|
||||
<span class="old">€ {menu.university_deal.old_price.toFixed(2)}</span>
|
||||
<span class="new">€ {menu.university_deal.current_price.toFixed(2)}</span>
|
||||
</div>
|
||||
<p class="tiny">{menu.university_deal.note}</p>
|
||||
{:else}
|
||||
<p class="tiny">Esperando el menú...</p>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{#if errorMessage}
|
||||
<div class="banner error">
|
||||
<p>{errorMessage}</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<section class="cards">
|
||||
<article class="card menu-card">
|
||||
<div class="card-head">
|
||||
<div>
|
||||
<p class="label">Menú del día</p>
|
||||
</div>
|
||||
{#if loadingMenu}
|
||||
<span class="tag">cargando...</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if menu}
|
||||
<div class={`menu-layout ${menu?.alternative?.items?.length ? 'with-alternative' : ''}`}>
|
||||
<div class="menu-primary">
|
||||
<div class="menu-grid">
|
||||
<div>
|
||||
<p class="section-title">Primeros</p>
|
||||
<ul>
|
||||
{#each menu.starters as starter}
|
||||
<li>{starter}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<p class="section-title">Segundos</p>
|
||||
<ul>
|
||||
{#each menu.mains as main}
|
||||
<li class:fish={main.toLowerCase().includes('pescado') || main.toLowerCase().includes('pedro')}>{main}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<p class="section-title">Guarnición</p>
|
||||
<ul>
|
||||
{#each menu.garnish as garnish}
|
||||
<li>{garnish}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<p class="section-title">Postres</p>
|
||||
<ul>
|
||||
{#each menu.desserts as dessert}
|
||||
<li>{dessert}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="notes">
|
||||
{#each menu.notes as note}
|
||||
<span>{note}</span>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{#if menu.alternative?.items?.length}
|
||||
<aside class="menu-alternative">
|
||||
<p class="label alt-label">{menu.alternative.title}</p>
|
||||
<p class="alt-price">
|
||||
<span class="alt-price-value"
|
||||
>€ {menu.alternative.price?.toFixed(2) ?? '5.00'}</span
|
||||
>
|
||||
<span class="alt-price-meta">Pues por si no quieres el menú</span>
|
||||
</p>
|
||||
<ul class="alt-items">
|
||||
{#each menu.alternative.items as alternativeItem}
|
||||
<li>{alternativeItem}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<span class="chip subtle">Mira si te estás planteando esto, mejor quédate sin comer</span>
|
||||
</aside>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="deal">
|
||||
<div>
|
||||
<p class="caption">Menú convenio</p>
|
||||
<div class="price-line">
|
||||
<span class="old">€ {menu.university_deal.old_price.toFixed(2)}</span>
|
||||
<span class="new"
|
||||
>€ {menu.university_deal.current_price.toFixed(2)}</span
|
||||
>
|
||||
</div>
|
||||
<small class="tiny">{menu.university_deal.note}</small>
|
||||
</div>
|
||||
<div class="cta">
|
||||
<span class="chip">El espeto consejo del día: {menu.espetos_tip}</span>
|
||||
<span class="chip subtle">
|
||||
Última actualización {menu.availability.last_updated}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{:else if !loadingMenu}
|
||||
<p>No pudimos leer el menú.</p>
|
||||
{/if}
|
||||
</article>
|
||||
|
||||
<article class="card ci-card">
|
||||
<div class="card-head">
|
||||
<div>
|
||||
<p class="label">Estado del sistema</p>
|
||||
<p class="sub">Información de build y backend</p>
|
||||
</div>
|
||||
{#if loadingCiStatus}
|
||||
<span class="tag">comprobando...</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if ciStatus}
|
||||
<div class="ci-grid">
|
||||
<div class="ci-item">
|
||||
<p class="caption">API</p>
|
||||
<p class="highlight">
|
||||
{ciStatus.status === 'ok' ? '🟢 Operativa' : '🔴 Caída'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="ci-item">
|
||||
<p class="caption">Build</p>
|
||||
<p class="highlight">
|
||||
#{ciStatus.build}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="ci-item">
|
||||
<p class="caption">Commit</p>
|
||||
<p class="highlight mono">
|
||||
{ciStatus.commit?.slice(0, 7)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="ci-item">
|
||||
<p class="caption">Uptime</p>
|
||||
<p class="highlight">
|
||||
{ciStatus.uptime_seconds}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="meta">
|
||||
Autor: {ciStatus.author}
|
||||
</p>
|
||||
{:else if !loadingCiStatus}
|
||||
<p>No se pudo obtener el estado del sistema.</p>
|
||||
{/if}
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<div class="card-head">
|
||||
<div class="label">Desayunos</div>
|
||||
{#if loadingPrices}
|
||||
<span class="tag">cargando...</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if prices.length}
|
||||
<div class="price-grid">
|
||||
{#each prices as price}
|
||||
<div class="price-card">
|
||||
<p class="item">{prettify(price.item)}</p>
|
||||
<p class="value">{price.price} €</p>
|
||||
<p class="timestamp">{price.generated_at}</p>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<p class="meta">
|
||||
Dependiendo de si vas por la mañana o por la tarde los precios cambian. No sé,
|
||||
como no ponen los precios al público... :p
|
||||
</p>
|
||||
{:else if !loadingPrices}
|
||||
<p>No hay precios que mostrar.</p>
|
||||
{/if}
|
||||
</article>
|
||||
</section>
|
||||
</main>
|
||||
Reference in New Issue
Block a user