gdias 2 месяцев назад
Родитель
Сommit
50c0b9975a

+ 1 - 1
package-lock.json

@@ -743,7 +743,7 @@
 		"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
 			"version": "4.49.0",
 			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.49.0.tgz",
-			"integrity": "sha512-2QyUyQQ1ZtwZGiq0nvODL+vLJBtciItC3/5cYN8ncDQcv5avrt2MbKt1XU/vFAJlLta5KujqyHdYtdag4YEjYQ==",
+			"integrity": "sha512-2QyUyQQ1ZtwZGiq0nvODL+vLJEasyCoiniItC3/5cYN8ncDQcv5avrt2MbKt1XU/vFAJlLta5KujqyHdYtdag4YEjYQ==",
 			"cpu": [
 				"loong64"
 			],

+ 4 - 3
src/lib/assets/icons/sidebar/operations.svg

@@ -1,3 +1,4 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
-  <path fill-rule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd"/>
-</svg>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+  <polyline points="23 6 13.5 15.5 8.5 10.5 1 18" />
+  <polyline points="17 6 23 6 23 12" />
+</svg>

+ 0 - 0
src/lib/components/containers/DashboardGuard.svelte → src/lib/components/DashboardGuard.svelte


+ 0 - 0
src/lib/components/containers/SectionMenu.svelte → src/lib/components/SectionMenu.svelte


+ 0 - 0
src/lib/components/containers/StatsCard.svelte → src/lib/components/StatsCard.svelte


+ 12 - 9
src/lib/components/containers/Tables.svelte → src/lib/components/Tables.svelte

@@ -17,6 +17,7 @@
   // Controla exibição da coluna de ações
   export let showActions = true;
   // Controla botões individuais de ação
+  export let showAdd = true;
   export let showEdit = true;
   export let showDelete = true;
 
@@ -38,15 +39,17 @@
 <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm">
   <div class="flex items-center justify-between px-5 py-4 border-b border-gray-200 dark:border-gray-700">
     <h3 class="text-base font-semibold text-gray-800 dark:text-gray-100">{title}</h3>
-    <button
-      class="inline-flex items-center justify-center rounded-md bg-blue-600 hover:bg-blue-700 text-white w-10 h-10 focus:outline-none focus:ring-2 focus:ring-blue-500"
-      type="button"
-      title="Adicionar"
-      on:click={handleAddTop}
-    >
-      <img src={plusWhite} alt="Adicionar" class="w-6 h-6" />
-      <span class="sr-only">Adicionar</span>
-    </button>
+    {#if showAdd}
+      <button
+        class="inline-flex items-center justify-center rounded-md bg-blue-600 hover:bg-blue-700 text-white w-10 h-10 focus:outline-none focus:ring-2 focus:ring-blue-500"
+        type="button"
+        title="Adicionar"
+        on:click={handleAddTop}
+      >
+        <img src={plusWhite} alt="Adicionar" class="w-6 h-6" />
+        <span class="sr-only">Adicionar</span>
+      </button>
+    {/if}
   </div>
 
   <div class="overflow-x-auto">

+ 0 - 0
src/lib/components/containers/Tabs.svelte → src/lib/components/Tabs.svelte


+ 0 - 0
src/lib/components/containers/commodities/CommodityEditModal.svelte → src/lib/components/commodities/CommodityEditModal.svelte


+ 0 - 0
src/lib/components/containers/commodities/RegisterCommodity.svelte → src/lib/components/commodities/RegisterCommodity.svelte


+ 0 - 0
src/lib/components/containers/cpr/ContractCpr.svelte → src/lib/components/commodities/cpr/ContractCpr.svelte


+ 0 - 0
src/lib/components/containers/cpr/CprEditModal.svelte → src/lib/components/commodities/cpr/CprEditModal.svelte


+ 0 - 0
src/lib/components/containers/cpr/EmissionCpr.svelte → src/lib/components/commodities/cpr/EmissionCpr.svelte


+ 0 - 0
src/lib/components/containers/cpr/RegisterCpr.svelte → src/lib/components/commodities/cpr/RegisterCpr.svelte


+ 71 - 0
src/lib/components/trading/BuyPanel.svelte

@@ -0,0 +1,71 @@
+<script>
+  import { createEventDispatcher } from 'svelte';
+  export let referencePrice;
+
+  const dispatch = createEventDispatcher();
+
+  let tab = 'market';
+  let amountFiat = '';
+  let limitPrice = '';
+
+  function formatBRL(n) {
+    return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(Number(n || 0));
+  }
+  function formatEasyCoin(n) {
+    return new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 6, maximumFractionDigits: 6 }).format(Number(n || 0));
+  }
+
+  $: priceUsed = tab === 'limit' ? Number(limitPrice) : Number(referencePrice);
+  $: computedEasyCoin = (Number(amountFiat) && priceUsed) ? Number(amountFiat) / priceUsed : 0;
+
+  function confirm() {
+    const price = priceUsed;
+    const total = Number(amountFiat);
+    if (!price || !total) return;
+    const amount = computedEasyCoin;
+    dispatch('confirm', { type: tab, price, amount, total });
+    amountFiat = '';
+    limitPrice = '';
+  }
+</script>
+
+<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm">
+  <div class="px-5 py-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
+    <h3 class="text-base font-semibold text-gray-800 dark:text-gray-100">Comprar</h3>
+    <div class="inline-flex rounded-md border border-gray-200 dark:border-gray-600 overflow-hidden">
+      <button class={`px-3 py-1.5 text-sm ${tab === 'market' ? 'bg-green-600 text-white' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300'}`} on:click={() => (tab = 'market')}>Mercado</button>
+      <button class={`px-3 py-1.5 text-sm border-l border-gray-200 dark:border-gray-600 ${tab === 'limit' ? 'bg-green-600 text-white' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300'}`} on:click={() => (tab = 'limit')}>Limitada</button>
+    </div>
+  </div>
+  <div class="p-5 space-y-4">
+    {#if tab === 'limit'}
+      <div>
+        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Preço limite (R$)</label>
+        <input type="number" min="0" step="0.01" bind:value={limitPrice} class="mt-1 block w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700/70 text-gray-900 dark:text-gray-200 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" />
+      </div>
+    {/if}
+    <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
+      <div>
+        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Valor (R$)</label>
+        <input type="number" min="0" step="0.01" bind:value={amountFiat} class="mt-1 block w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700/70 text-gray-900 dark:text-gray-200 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" />
+      </div>
+      <div>
+        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">EasyCoin</label>
+        <input value={formatEasyCoin(computedEasyCoin || 0)} readonly class="mt-1 block w-full rounded border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/40 text-gray-900 dark:text-gray-200 px-3 py-2" />
+      </div>
+    </div>
+    <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
+      <div>
+        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Valor a receber</label>
+        <input value={formatEasyCoin(computedEasyCoin || 0)} readonly class="mt-1 block w-full rounded border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/40 text-gray-900 dark:text-gray-200 px-3 py-2" />
+      </div>
+      <div>
+        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Preço de referência</label>
+        <input value={referencePrice ? formatBRL(referencePrice) : '—'} readonly class="mt-1 block w-full rounded border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/40 text-gray-900 dark:text-gray-200 px-3 py-2" />
+      </div>
+    </div>
+    <div class="flex justify-end">
+      <button class="px-4 py-2 rounded bg-green-600 hover:bg-green-700 text-white focus:outline-none focus:ring-2 focus:ring-green-500" on:click={confirm}>Confirmar</button>
+    </div>
+  </div>
+</div>

+ 71 - 0
src/lib/components/trading/SellPanel.svelte

@@ -0,0 +1,71 @@
+<script>
+  import { createEventDispatcher } from 'svelte';
+  export let referencePrice;
+
+  const dispatch = createEventDispatcher();
+
+  let tab = 'market';
+  let amountEasyCoin = '';
+  let limitPrice = '';
+
+  function formatBRL(n) {
+    return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(Number(n || 0));
+  }
+  function formatEasyCoin(n) {
+    return new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 6, maximumFractionDigits: 6 }).format(Number(n || 0));
+  }
+
+  $: priceUsed = tab === 'limit' ? Number(limitPrice) : Number(referencePrice);
+  $: computedFiat = (Number(amountEasyCoin) && priceUsed) ? Number(amountEasyCoin) * priceUsed : 0;
+
+  function confirm() {
+    const price = priceUsed;
+    const amount = Number(amountEasyCoin);
+    if (!price || !amount) return;
+    const total = computedFiat;
+    dispatch('confirm', { type: tab, price, amount, total });
+    amountEasyCoin = '';
+    limitPrice = '';
+  }
+</script>
+
+<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm">
+  <div class="px-5 py-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
+    <h3 class="text-base font-semibold text-gray-800 dark:text-gray-100">Vender</h3>
+    <div class="inline-flex rounded-md border border-gray-200 dark:border-gray-600 overflow-hidden">
+      <button class={`px-3 py-1.5 text-sm ${tab === 'market' ? 'bg-red-600 text-white' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300'}`} on:click={() => (tab = 'market')}>Mercado</button>
+      <button class={`px-3 py-1.5 text-sm border-l border-gray-200 dark:border-gray-600 ${tab === 'limit' ? 'bg-red-600 text-white' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300'}`} on:click={() => (tab = 'limit')}>Limitada</button>
+    </div>
+  </div>
+  <div class="p-5 space-y-4">
+    {#if tab === 'limit'}
+      <div>
+        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Preço limite (R$)</label>
+        <input type="number" min="0" step="0.01" bind:value={limitPrice} class="mt-1 block w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700/70 text-gray-900 dark:text-gray-200 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" />
+      </div>
+    {/if}
+    <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
+      <div>
+        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">EasyCoin</label>
+        <input type="number" min="0" step="0.000001" bind:value={amountEasyCoin} class="mt-1 block w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700/70 text-gray-900 dark:text-gray-200 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" />
+      </div>
+      <div>
+        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Valor (R$)</label>
+        <input value={formatBRL(computedFiat || 0)} readonly class="mt-1 block w-full rounded border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/40 text-gray-900 dark:text-gray-200 px-3 py-2" />
+      </div>
+    </div>
+    <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
+      <div>
+        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Valor a receber</label>
+        <input value={formatBRL(computedFiat || 0)} readonly class="mt-1 block w-full rounded border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/40 text-gray-900 dark:text-gray-200 px-3 py-2" />
+      </div>
+      <div>
+        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Preço de referência</label>
+        <input value={referencePrice ? formatBRL(referencePrice) : '—'} readonly class="mt-1 block w-full rounded border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/40 text-gray-900 dark:text-gray-200 px-3 py-2" />
+      </div>
+    </div>
+    <div class="flex justify-end">
+      <button class="px-4 py-2 rounded bg-red-600 hover:bg-red-700 text-white focus:outline-none focus:ring-2 focus:ring-red-500" on:click={confirm}>Confirmar</button>
+    </div>
+  </div>
+</div>

+ 1 - 1
src/routes/+layout.svelte

@@ -3,7 +3,7 @@
 	import favicon from '$lib/assets/favicon.svg';
 	import logo from '$lib/assets/logo.png';
 	import Sidebar from '$lib/layout/SideBar.svelte';
-	import DashboardGuard from '$lib/components/containers/DashboardGuard.svelte';
+	import DashboardGuard from '$lib/components/DashboardGuard.svelte';
 	import { browser } from '$app/environment';
 	import { page } from '$app/stores';
   

+ 4 - 4
src/routes/commodities/+page.svelte

@@ -1,9 +1,9 @@
 <script>
   import Header from '$lib/layout/Header.svelte';
-  import Tables from '$lib/components/containers/Tables.svelte';
-  import RegisterCommodity from '$lib/components/containers/commodities/RegisterCommodity.svelte';
-  import Tabs from '$lib/components/containers/Tabs.svelte';
-  import CommodityEditModal from '$lib/components/containers/commodities/CommodityEditModal.svelte';
+  import Tables from '$lib/components/Tables.svelte';
+  import RegisterCommodity from '$lib/components/commodities/RegisterCommodity.svelte';
+  import Tabs from '$lib/components/Tabs.svelte';
+  import CommodityEditModal from '$lib/components/commodities/CommodityEditModal.svelte';
   import ConfirmModal from '$lib/components/ui/PopUpDelete.svelte';
 
   const breadcrumb = [{ label: 'Início' }, { label: 'Commodities', active: true }];

+ 6 - 6
src/routes/cpr/+page.svelte

@@ -1,11 +1,11 @@
 <script>
   import Header from '$lib/layout/Header.svelte';
-  import Tabs from '$lib/components/containers/Tabs.svelte';
-  import RegisterCpr from '$lib/components/containers/cpr/RegisterCpr.svelte';
-  import Tables from '$lib/components/containers/Tables.svelte';
-  import ContractCpr from '$lib/components/containers/cpr/ContractCpr.svelte';
-  import EmissionCpr from '$lib/components/containers/cpr/EmissionCpr.svelte';
-  import CprEditModal from '$lib/components/containers/cpr/CprEditModal.svelte';
+  import Tabs from '$lib/components/Tabs.svelte';
+  import RegisterCpr from '$lib/components/commodities/cpr/RegisterCpr.svelte';
+  import Tables from '$lib/components/Tables.svelte';
+  import ContractCpr from '$lib/components/commodities/cpr/ContractCpr.svelte';
+  import EmissionCpr from '$lib/components/commodities/cpr/EmissionCpr.svelte';
+  import CprEditModal from '$lib/components/commodities/cpr/CprEditModal.svelte';
   import ConfirmModal from '$lib/components/ui/PopUpDelete.svelte';
 
   // EditModal

+ 1 - 1
src/routes/dashboard/+page.svelte

@@ -1,6 +1,6 @@
 <script>
     import Header from '$lib/layout/Header.svelte';
-    import StatsCard from '$lib/components/containers/StatsCard.svelte';
+    import StatsCard from '$lib/components/StatsCard.svelte';
     import commoditiesIcon from '$lib/assets/icons/sidebar/commodities.svg?raw';
     import operationsIcon from '$lib/assets/icons/sidebar/operations.svg?raw';
     import cprIcon from '$lib/assets/icons/sidebar/cpr.svg?raw';

+ 117 - 2
src/routes/trading/+page.svelte

@@ -1,10 +1,125 @@
 <script>
   import Header from '$lib/layout/Header.svelte';
-  import EmptyState from '$lib/components/ui/EmptyState.svelte';
+  import Tables from '$lib/components/Tables.svelte';
+  import BuyPanel from '$lib/components/trading/BuyPanel.svelte';
+  import SellPanel from '$lib/components/trading/SellPanel.svelte';
+  import { writable } from 'svelte/store';
+  import { onMount } from 'svelte';
+  import { browser } from '$app/environment';
+
   const breadcrumb = [{ label: 'Início' }, { label: 'Trading', active: true }];
+
+  const orders = writable([]);
+
+  let currentPrice = 125;
+
+  onMount(() => {
+    if (browser) {
+      try {
+        const saved = localStorage.getItem('tradingOrders');
+        if (saved) orders.set(JSON.parse(saved));
+      } catch {}
+    }
+  });
+
+  function addOrder(order) {
+    orders.update((arr) => {
+      const next = [{ ...order, id: Date.now() }, ...arr];
+      if (browser) localStorage.setItem('tradingOrders', JSON.stringify(next));
+      return next;
+    });
+  }
+
+  function formatBRL(n) {
+    return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(Number(n || 0));
+  }
+  function formatEasyCoin(n) {
+    return new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 6, maximumFractionDigits: 6 }).format(Number(n || 0));
+  }
+
+  let buyTab = 'market';
+  let sellTab = 'market';
+
+  let buyAmountFiat = '';
+  let buyLimitPrice = '';
+  let sellAmountEasyCoin = '';
+  let sellLimitPrice = '';
+
+
+  $: buyComputedEasyCoin = buyTab === 'market'
+    ? (Number(buyAmountFiat) && currentPrice ? Number(buyAmountFiat) / currentPrice : 0)
+    : (Number(buyAmountFiat) && Number(buyLimitPrice) ? Number(buyAmountFiat) / Number(buyLimitPrice) : 0);
+
+  $: sellComputedFiat = sellTab === 'market'
+    ? (Number(sellAmountEasyCoin) && currentPrice ? Number(sellAmountEasyCoin) * currentPrice : 0)
+    : (Number(sellAmountEasyCoin) && Number(sellLimitPrice) ? Number(sellAmountEasyCoin) * Number(sellLimitPrice) : 0);
+
+  function confirmBuy() {
+    const price = buyTab === 'limit' ? Number(buyLimitPrice) : currentPrice;
+    const valueFiat = Number(buyAmountFiat);
+    if (!price || !valueFiat) return;
+    const amount = valueFiat / price;
+    addOrder({ side: 'Compra', price, amount, total: valueFiat, status: 'Aberta', type: buyTab });
+    buyAmountFiat = '';
+    buyLimitPrice = '';
+  }
+
+  function confirmSell() {
+    const price = sellTab === 'limit' ? Number(sellLimitPrice) : currentPrice;
+    const amount = Number(sellAmountEasyCoin);
+    if (!price || !amount) return;
+    const total = amount * price;
+    addOrder({ side: 'Venda', price, amount, total, status: 'Aberta', type: sellTab });
+    sellAmountEasyCoin = '';
+    sellLimitPrice = '';
+  }
+
+  const orderColumns = [
+    { key: 'side', label: 'Tipo' },
+    { key: 'price', label: 'Preço (R$)' },
+    { key: 'amount', label: 'Quantidade (EasyCoin)' },
+    { key: 'total', label: 'Total (R$)' },
+    { key: 'status', label: 'Status' }
+  ];
+
+$: pendingBuys = $orders.filter((o) => o.side === 'Compra' && o.type === 'limit' && o.status === 'Aberta');
+$: pendingSells = $orders.filter((o) => o.side === 'Venda' && o.type === 'limit' && o.status === 'Aberta');
+
+$: displayPendingBuys = pendingBuys.map((o) => ({
+  side: o.side,
+  price: formatBRL(o.price),
+  amount: formatEasyCoin(o.amount),
+  total: formatBRL(o.total),
+  status: o.status
+}));
+
+$: displayPendingSells = pendingSells.map((o) => ({
+  side: o.side,
+  price: formatBRL(o.price),
+  amount: formatEasyCoin(o.amount),
+  total: formatBRL(o.total),
+  status: o.status
+}));
 </script>
 
 <div>
   <Header title="Trading" subtitle="Trading do sistema" breadcrumb={breadcrumb} />
-  <EmptyState title="Área em Desenvolvimento" description="Esta funcionalidade estará disponível em breve." />
+
+  <div class="p-4 space-y-4">
+    <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
+      <BuyPanel
+        referencePrice={currentPrice}
+        on:confirm={(e) => addOrder({ side: 'Compra', price: e.detail.price, amount: e.detail.amount, total: e.detail.total, status: 'Aberta', type: e.detail.type })}
+      />
+      <SellPanel
+        referencePrice={currentPrice}
+        on:confirm={(e) => addOrder({ side: 'Venda', price: e.detail.price, amount: e.detail.amount, total: e.detail.total, status: 'Aberta', type: e.detail.type })}
+      />
+    </div>
+
+<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
+  <Tables title="Ordens de Compra (pendentes)" columns={orderColumns} data={displayPendingBuys} showActions={false} showAdd={false}/>
+  <Tables title="Ordens de Venda (pendentes)" columns={orderColumns} data={displayPendingSells} showActions={false} showAdd={false}/>
+</div>
+  </div>
 </div>

+ 1 - 1
src/routes/users/+page.svelte

@@ -1,6 +1,6 @@
 <script>
   import Header from '$lib/layout/Header.svelte';
-  import Tables from '$lib/components/containers/Tables.svelte';
+  import Tables from '$lib/components/Tables.svelte';
   import UserCreateModal from '$lib/components/users/UserCreateModal.svelte';
   import ConfirmModal from '$lib/components/ui/PopUpDelete.svelte';
   const breadcrumb = [{ label: 'Início' }, { label: 'Usuários', active: true }];