mirror of
https://github.com/catdevnull/preciazo.git
synced 2024-11-22 14:16:19 +00:00
Compare commits
3 commits
e96bf8dc74
...
1cc14d0a0b
Author | SHA1 | Date | |
---|---|---|---|
1cc14d0a0b | |||
8ed967209f | |||
0be2ff8911 |
9 changed files with 117 additions and 41 deletions
17
sepa/sitio2/src/lib/components/Loading.svelte
Normal file
17
sepa/sitio2/src/lib/components/Loading.svelte
Normal 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}
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
|
@ -3,10 +3,13 @@
|
||||||
import { ArrowLeft } from 'lucide-svelte';
|
import { ArrowLeft } from 'lucide-svelte';
|
||||||
import Map from '$lib/components/Map.svelte';
|
import Map from '$lib/components/Map.svelte';
|
||||||
import Badge from '$lib/components/ui/badge/badge.svelte';
|
import Badge from '$lib/components/ui/badge/badge.svelte';
|
||||||
import {} from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { generateGoogleMapsLink, pesosFormatter, processBanderaNombre } from '$lib/sepa-utils';
|
import { generateGoogleMapsLink, pesosFormatter, processBanderaNombre } from '$lib/sepa-utils';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
|
const query = $page.url.searchParams.get('query');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
@ -15,7 +18,12 @@
|
||||||
|
|
||||||
<div class="flex min-h-screen flex-col">
|
<div class="flex min-h-screen flex-col">
|
||||||
<div class="max-w-screen flex items-stretch gap-3 overflow-hidden px-2">
|
<div class="max-w-screen flex items-stretch gap-3 overflow-hidden px-2">
|
||||||
<button on:click={() => window.history.back()}>
|
<button
|
||||||
|
on:click={() =>
|
||||||
|
goto(
|
||||||
|
`/search/${encodeURIComponent(query ?? data.precios[0].productos_descripcion ?? $page.params.id)}`
|
||||||
|
)}
|
||||||
|
>
|
||||||
<ArrowLeft class="size-8 flex-shrink-0" />
|
<ArrowLeft class="size-8 flex-shrink-0" />
|
||||||
</button>
|
</button>
|
||||||
<div class="flex flex-wrap items-center gap-x-2 overflow-hidden p-1">
|
<div class="flex flex-wrap items-center gap-x-2 overflow-hidden p-1">
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
import { MapPin } from 'lucide-svelte';
|
import { MapPin } from 'lucide-svelte';
|
||||||
import Button from '$lib/components/ui/button/button.svelte';
|
import Button from '$lib/components/ui/button/button.svelte';
|
||||||
import { es } from 'date-fns/locale';
|
import { es } from 'date-fns/locale';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
|
@ -23,7 +25,15 @@
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="max-w-screen flex items-stretch gap-3 overflow-hidden px-2">
|
<div class="max-w-screen flex items-stretch gap-3 overflow-hidden px-2">
|
||||||
<button on:click={() => window.history.back()}>
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
if (history.length > 1) {
|
||||||
|
history.back();
|
||||||
|
} else {
|
||||||
|
goto(`/id_producto/${$page.params.id}`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ArrowLeft class="size-8 flex-shrink-0" />
|
<ArrowLeft class="size-8 flex-shrink-0" />
|
||||||
</button>
|
</button>
|
||||||
<div class="flex flex-wrap items-center gap-x-2 overflow-hidden p-1">
|
<div class="flex flex-wrap items-center gap-x-2 overflow-hidden p-1">
|
||||||
|
|
|
@ -1,24 +1,12 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import SearchBar from '$lib/components/SearchBar.svelte';
|
import SearchBar from '$lib/components/SearchBar.svelte';
|
||||||
import Badge from '$lib/components/ui/badge/badge.svelte';
|
|
||||||
import Button from '$lib/components/ui/button/button.svelte';
|
import Button from '$lib/components/ui/button/button.svelte';
|
||||||
import * as Card from '$lib/components/ui/card/index.js';
|
|
||||||
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 +27,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} query={data.query} />
|
||||||
<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>
|
||||||
|
|
48
sepa/sitio2/src/routes/search/[query]/ProductCard.svelte
Normal file
48
sepa/sitio2/src/routes/search/[query]/ProductCard.svelte
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<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: Set<string>;
|
||||||
|
in_datasets_count: number;
|
||||||
|
descriptions: string[];
|
||||||
|
};
|
||||||
|
export let query: string | undefined;
|
||||||
|
|
||||||
|
let loading = false;
|
||||||
|
beforeNavigate((x) => {
|
||||||
|
if (x.to?.params?.id === producto.id_producto) {
|
||||||
|
loading = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href={`/id_producto/${producto.id_producto}?query=${encodeURIComponent(query ?? producto.descriptions[0])}`}
|
||||||
|
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>
|
Loading…
Reference in a new issue