loading state

fixes #60
This commit is contained in:
Cat /dev/Nulo 2024-09-22 18:54:22 -03:00
parent e96bf8dc74
commit 0be2ff8911
7 changed files with 92 additions and 36 deletions

View file

@ -0,0 +1,17 @@
<script lang="ts">
// Parent has to be position relative
import { LoaderCircle } from 'lucide-svelte';
export let loading = false;
</script>
{#if loading}
<div class="absolute inset-0 flex items-center justify-center">
<LoaderCircle class="h-4 w-4 animate-spin" />
</div>
<div class="invisible">
<slot />
</div>
{:else}
<slot />
{/if}

View file

@ -1,17 +1,25 @@
<script lang="ts"> <script lang="ts">
import { Input } from '$lib/components/ui/input'; import { Input } from '$lib/components/ui/input';
import { Button } from '$lib/components/ui/button'; import { Button } from '$lib/components/ui/button';
import { goto } from '$app/navigation'; import { afterNavigate, beforeNavigate, goto } from '$app/navigation';
import { page } from '$app/stores'; import { page } from '$app/stores';
let search = $page.params.query ?? ''; let search = $page.params.query ?? '';
let loading = false;
beforeNavigate(() => {
loading = true;
});
afterNavigate(() => {
loading = false;
});
function handleSubmit() { function handleSubmit() {
goto(`/search/${encodeURIComponent(search)}`); goto(`/search/${encodeURIComponent(search)}`);
} }
</script> </script>
<form class="flex gap-2" on:submit|preventDefault={handleSubmit}> <form class="flex gap-2" on:submit|preventDefault={handleSubmit}>
<Input placeholder="Buscar productos" bind:value={search} /> <Input placeholder="Buscar productos" bind:value={search} disabled={loading} />
<Button type="submit">Buscar</Button> <Button type="submit" {loading}>Buscar</Button>
</form> </form>

View file

@ -2,6 +2,7 @@
import { Button as ButtonPrimitive } from 'bits-ui'; import { Button as ButtonPrimitive } from 'bits-ui';
import { type Events, type Props, buttonVariants } from './index.js'; import { type Events, type Props, buttonVariants } from './index.js';
import { cn } from '$lib/utils.js'; import { cn } from '$lib/utils.js';
import Loading from '$lib/components/Loading.svelte';
type $$Props = Props; type $$Props = Props;
type $$Events = Events; type $$Events = Events;
@ -10,16 +11,19 @@
export let variant: $$Props['variant'] = 'default'; export let variant: $$Props['variant'] = 'default';
export let size: $$Props['size'] = 'default'; export let size: $$Props['size'] = 'default';
export let builders: $$Props['builders'] = []; export let builders: $$Props['builders'] = [];
export let loading = false;
export { className as class }; export { className as class };
</script> </script>
<ButtonPrimitive.Root <ButtonPrimitive.Root
{builders} {builders}
class={cn(buttonVariants({ variant, size, className }))} class={cn(buttonVariants({ variant, size, className }), 'relative')}
type="button" type="button"
{...$$restProps} {...$$restProps}
on:click on:click
on:keydown on:keydown
> >
<Loading {loading}>
<slot /> <slot />
</Loading>
</ButtonPrimitive.Root> </ButtonPrimitive.Root>

View file

@ -32,6 +32,7 @@ type Size = VariantProps<typeof buttonVariants>['size'];
type Props = ButtonPrimitive.Props & { type Props = ButtonPrimitive.Props & {
variant?: Variant; variant?: Variant;
size?: Size; size?: Size;
loading?: boolean;
}; };
type Events = ButtonPrimitive.Events; type Events = ButtonPrimitive.Events;

View file

@ -28,3 +28,14 @@ export const pesosFormatter = new Intl.NumberFormat('es-AR', {
style: 'currency', style: 'currency',
currency: 'ARS' currency: 'ARS'
}); });
export function parseMarcas(marcas: readonly string[]) {
const x = marcas
.map((m) => m.trim().replaceAll(/['`´]/g, ''))
.filter((m) => !['sin marca', 'VARIOS'].includes(m))
.filter((m) => m.length > 0);
if (x.length === 0) {
return ['n/a'];
}
return Array.from(new Set(x));
}

View file

@ -6,19 +6,9 @@
import { ArrowLeft } from 'lucide-svelte'; import { ArrowLeft } from 'lucide-svelte';
import type { PageData } from './$types'; import type { PageData } from './$types';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import ProductCard from './ProductCard.svelte';
export let data: PageData; export let data: PageData;
function parseMarcas(marcas: readonly string[]) {
const x = marcas
.map((m) => m.trim().replaceAll(/['`´]/g, ''))
.filter((m) => !['sin marca', 'VARIOS'].includes(m))
.filter((m) => m.length > 0);
if (x.length === 0) {
return ['n/a'];
}
return Array.from(new Set(x));
}
</script> </script>
<svelte:head> <svelte:head>
@ -39,26 +29,7 @@
</p> </p>
{:else} {:else}
{#each data.collapsedProductos as producto} {#each data.collapsedProductos as producto}
<a href={`/id_producto/${producto.id_producto}`} class="my-2 block"> <ProductCard {producto} />
<Card.Root class="transition-colors duration-200 hover:bg-gray-100">
<Card.Header class="block px-3 py-2 pb-0">
<Badge>{parseMarcas(Array.from(producto.marcas)).join('/')}</Badge>
<Badge variant="outline"
>en
{producto.in_datasets_count} cadena{#if producto.in_datasets_count > 1}s{/if}
</Badge>
<Badge variant="outline">EAN {producto.id_producto}</Badge>
</Card.Header>
<Card.Content class="px-3 py-2">
{#each producto.descriptions as description}
<span>{description}</span>
{#if description !== producto.descriptions[producto.descriptions.length - 1]}
<span class="text-gray-500"></span>{' '}
{/if}
{/each}
</Card.Content>
</Card.Root>
</a>
{/each} {/each}
{/if} {/if}
</div> </div>

View file

@ -0,0 +1,44 @@
<script lang="ts">
import * as Card from '$lib/components/ui/card';
import { Badge } from '$lib/components/ui/badge';
import { parseMarcas } from '$lib/sepa-utils';
import { beforeNavigate } from '$app/navigation';
import Loading from '$lib/components/Loading.svelte';
export let producto: {
id_producto: string;
marcas: string[];
in_datasets_count: number;
descriptions: string[];
};
let loading = false;
beforeNavigate((x) => {
if (x.to?.params?.id === producto.id_producto) {
loading = true;
}
});
</script>
<a href={`/id_producto/${producto.id_producto}`} class="my-2 block">
<Card.Root class="relative transition-colors duration-200 hover:bg-gray-100">
<Loading {loading}>
<Card.Header class="block px-3 py-2 pb-0">
<Badge>{parseMarcas(Array.from(producto.marcas)).join('/')}</Badge>
<Badge variant="outline"
>en
{producto.in_datasets_count} cadena{#if producto.in_datasets_count > 1}s{/if}
</Badge>
<Badge variant="outline">EAN {producto.id_producto}</Badge>
</Card.Header>
<Card.Content class="px-3 py-2">
{#each producto.descriptions as description}
<span>{description}</span>
{#if description !== producto.descriptions[producto.descriptions.length - 1]}
<span class="text-gray-500"></span>{' '}
{/if}
{/each}
</Card.Content>
</Loading>
</Card.Root>
</a>