| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- <script>
- import { createEventDispatcher } from 'svelte';
- export let ordensCompra = [];
- export let ordensVenda = [];
- export let ultimaVenda = { valor: 0, quantidade: 0 };
- export let emptyMessage = '';
- const dispatch = createEventDispatcher();
- function formatBRL(n) { return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(Number(n || 0)); }
- function formatQty(n) { return new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 0, maximumFractionDigits: 0 }).format(Number(n || 0)); }
- $: listaCompra = (ordensCompra || []).map((d) => ({
- ...d,
- valor: Number(d?.valor ?? 0),
- quantidade: Number(d?.quantidade ?? 0),
- city: d?.city ?? d?.cityName ?? '',
- cityName: d?.cityName ?? d?.city ?? '',
- measureUnitName: d?.measureUnitName ?? ''
- }));
- $: listaCompraView = (listaCompra || []).map((o, __idx) => ({ ...o, __idx })).slice().reverse();
- $: listaVenda = (ordensVenda || []).map((d) => ({
- ...d,
- valor: Number(d?.valor ?? 0),
- quantidade: Number(d?.quantidade ?? 0),
- city: d?.city ?? d?.cityName ?? '',
- cityName: d?.cityName ?? d?.city ?? '',
- measureUnitName: d?.measureUnitName ?? '',
- raw: d?.raw ?? d
- }));
- $: listaVendaView = (listaVenda || []).map((o, __idx) => ({ ...o, __idx })).slice().reverse();
- // `ultimaVenda` agora vem por prop do pai
- let flashCompra = new Set();
- let flashVenda = new Set();
- let prevCompraLen = listaCompra?.length || 0;
- let prevVendaLen = listaVenda?.length || 0;
- let hoverTimer = null;
- let tooltipVisible = false;
- let tooltipCity = '';
- let tooltipPrice = 0;
- let tooltipQty = 0;
- let tooltipStyle = 'top:0px;left:0px;';
- let tooltipTotal = 0;
- function scheduleTooltip(event, order) {
- if (hoverTimer) clearTimeout(hoverTimer);
- const rect = event.currentTarget?.getBoundingClientRect?.();
- if (!rect) return;
- const top = rect.top + (typeof window !== 'undefined' ? window.scrollY : 0) + rect.height / 2;
- const left = rect.right + (typeof window !== 'undefined' ? window.scrollX : 0) + 12;
- hoverTimer = setTimeout(() => {
- tooltipCity = order?.cityName || order?.city || '';
- tooltipPrice = Number(order?.valor ?? 0);
- tooltipQty = Number(order?.quantidade ?? 0);
- tooltipTotal = Number(order?.total ?? order?.raw?.token_commodities_value ?? (tooltipPrice * tooltipQty));
- tooltipStyle = `top:${top}px;left:${left}px;`;
- tooltipVisible = true;
- }, 300);
- }
- function hideTooltip() {
- if (hoverTimer) {
- clearTimeout(hoverTimer);
- hoverTimer = null;
- }
- tooltipVisible = false;
- }
- $: {
- const curr = listaCompra?.length || 0;
- if (curr > prevCompraLen) {
- for (let i = prevCompraLen; i < curr; i++) {
- flashCompra.add(i);
- setTimeout(() => { flashCompra.delete(i); }, 1500);
- }
- }
- prevCompraLen = curr;
- }
- $: {
- const curr = listaVenda?.length || 0;
- if (curr > prevVendaLen) {
- for (let i = prevVendaLen; i < curr; i++) {
- flashVenda.add(i);
- setTimeout(() => { flashVenda.delete(i); }, 1500);
- }
- }
- prevVendaLen = curr;
- }
- </script>
- <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm h-full">
- <div class="px-4 py-2 bg-yellow-100 dark:bg-yellow-900/40 border-b border-yellow-300 dark:border-yellow-700 text-sm text-yellow-900 dark:text-yellow-200">
- <div class="flex items-center justify-between">
- <span>Última Venda</span>
- <span class="font-medium">{formatBRL(ultimaVenda?.valor)} · {formatQty(ultimaVenda?.quantidade)}</span>
- </div>
- </div>
- <div class="h-[70vh] overflow-auto grid grid-cols-2 divide-x divide-gray-200 dark:divide-gray-700 scroll-ordens">
- <div class="p-4">
- <div class="flex flex-col items-center justify-between mb-2">
- <h3 class="text-sm font-semibold text-green-600">Ordem de Compra</h3>
- <div class="text-xs text-gray-500">VALOR · QUANTIDADE</div>
- </div>
- <div class="space-y-1">
- {#if listaCompraView.length === 0}
- <div class="text-xs text-gray-500 dark:text-gray-400 text-center py-4 border border-dashed border-gray-200 dark:border-gray-700 rounded">
- {emptyMessage || 'Nenhuma ordem de compra disponível.'}
- </div>
- {:else}
- {#each listaCompraView as o}
- <div
- class={`flex items-center justify-between text-sm rounded px-1 py-0.5 transition-colors duration-700 ${flashCompra.has(o.__idx) ? 'bg-green-100 dark:bg-green-900/30' : ''}`}
- role="listitem"
- on:mouseenter={(event) => scheduleTooltip(event, o)}
- on:mouseleave={hideTooltip}
- >
- <span class="text-green-600">{formatBRL(o.valor)}</span>
- <span class="text-gray-700 dark:text-gray-200">{formatQty(o.quantidade)}</span>
- </div>
- {/each}
- {/if}
- </div>
- </div>
- <div class="p-4">
- <div class="flex flex-col items-center justify-between mb-2">
- <h3 class="text-sm font-semibold text-red-600">Ordem de Venda</h3>
- <div class="text-xs text-gray-500">VALOR · QUANTIDADE</div>
- </div>
- <div class="space-y-1">
- {#if listaVendaView.length === 0}
- <div class="text-xs text-gray-500 dark:text-gray-400 text-center py-4 border border-dashed border-gray-200 dark:border-gray-700 rounded">
- {emptyMessage || 'Nenhuma ordem de venda disponível.'}
- </div>
- {:else}
- {#each listaVendaView as o}
- <button
- type="button"
- class={`flex w-full items-center justify-between text-sm rounded px-1 py-0.5 transition-colors duration-700 cursor-pointer hover:bg-red-50 dark:hover:bg-red-900/40 ${flashVenda.has(o.__idx) ? 'bg-red-100 dark:bg-red-900/30' : ''}`}
- on:mouseenter={(event) => scheduleTooltip(event, o)}
- on:mouseleave={hideTooltip}
- on:focus={(event) => scheduleTooltip(event, o)}
- on:blur={hideTooltip}
- on:click={() =>
- dispatch('selectSellOrder', {
- valor: o.valor,
- quantidade: o.quantidade,
- city: o.city,
- cityName: o.cityName,
- measureUnitName: o.measureUnitName,
- orderbookId: o.orderbookId ?? o.orderbook_id ?? o.raw?.orderbook_id ?? null,
- tokenExternalId: o.tokenExternalId ?? o.token_external_id ?? o.raw?.token_external_id ?? '',
- raw: o.raw ?? o
- })}
- >
- <span class="text-red-600">{formatBRL(o.valor)}</span>
- <span class="text-gray-700 dark:text-gray-200">{formatQty(o.quantidade)}</span>
- </button>
- {/each}
- {/if}
- </div>
- </div>
- </div>
- {#if tooltipVisible}
- <div class="order-tooltip" style={tooltipStyle}>
- <div class="space-y-1">
- <div>
- <div class="text-[11px] uppercase tracking-wide text-gray-400 dark:text-gray-500">Cidade</div>
- <div class="text-sm text-gray-800 dark:text-gray-100">{tooltipCity || 'Não informado'}</div>
- </div>
- <div class="grid grid-cols-2 gap-3 text-sm">
- <div>
- <div class="text-[11px] uppercase tracking-wide text-gray-400 dark:text-gray-500">Preço</div>
- <div class="text-gray-800 dark:text-gray-100">{formatBRL(tooltipPrice)}</div>
- </div>
- <div>
- <div class="text-[11px] uppercase tracking-wide text-gray-400 dark:text-gray-500">Quantidade</div>
- <div class="text-gray-800 dark:text-gray-100">{formatQty(tooltipQty)}</div>
- </div>
- </div>
- <div class="text-sm pt-1 border-t border-gray-100 dark:border-gray-700">
- <div class="text-[11px] uppercase tracking-wide text-gray-400 dark:text-gray-500">Valor total</div>
- <div class="text-gray-900 dark:text-gray-100 font-semibold">{formatBRL(tooltipTotal)}</div>
- </div>
- </div>
- </div>
- {/if}
- </div>
- <style>
- :global(.scroll-ordens) {
- scrollbar-width: thin; /* Firefox */
- scrollbar-color: #cbd5e1 #f3f4f6; /* thumb track */
- }
- :global(.dark .scroll-ordens) {
- scrollbar-color: #374151 #1f2937; /* thumb track */
- }
- :global(.scroll-ordens::-webkit-scrollbar) {
- width: 10px;
- height: 10px;
- }
- :global(.scroll-ordens::-webkit-scrollbar-track) {
- background: #f3f4f6; /* gray-100 */
- }
- :global(.scroll-ordens::-webkit-scrollbar-thumb) {
- background-color: #cbd5e1; /* slate-300 */
- border-radius: 9999px;
- border: 2px solid #f3f4f6; /* match track */
- }
- :global(.dark .scroll-ordens::-webkit-scrollbar-track) {
- background: #1f2937; /* gray-800 */
- }
- :global(.dark .scroll-ordens::-webkit-scrollbar-thumb) {
- background-color: #374151; /* gray-700 */
- border-color: #1f2937; /* match dark track */
- }
- .order-tooltip {
- position: fixed;
- z-index: 40;
- padding: 0.5rem 0.65rem;
- background: #fff;
- border-radius: 0.375rem;
- border: 1px solid #e5e7eb;
- box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1);
- pointer-events: none;
- min-width: 120px;
- transform: translateY(-50%);
- }
- :global(.dark) .order-tooltip {
- background: #1f2937;
- border-color: #374151;
- }
- </style>
|