ソースを参照

ADD: Add the new screen settings, new screen Mananger users and complete the screen CPR and Commoditys with the edit and delete modal

gdias 2 ヶ月 前
コミット
1d2ecc051b

+ 0 - 0
static/icons/edit-dark.svg → src/lib/assets/icons/edit-dark.svg


+ 0 - 0
static/icons/edit-light.svg → src/lib/assets/icons/edit-light.svg


+ 0 - 0
static/icons/edit.svg → src/lib/assets/icons/edit.svg


+ 0 - 0
static/icons/plus-dark.svg → src/lib/assets/icons/plus-dark.svg


+ 0 - 0
static/icons/plus-light.svg → src/lib/assets/icons/plus-light.svg


+ 0 - 0
static/icons/plus-white.svg → src/lib/assets/icons/plus-white.svg


+ 0 - 0
static/icons/plus.svg → src/lib/assets/icons/plus.svg


+ 0 - 0
static/icons/trash-dark.svg → src/lib/assets/icons/trash-dark.svg


+ 0 - 0
static/icons/trash-light.svg → src/lib/assets/icons/trash-light.svg


+ 0 - 0
static/icons/trash.svg → src/lib/assets/icons/trash.svg


BIN
src/lib/assets/logo.png


+ 39 - 27
src/lib/components/containers/Tables.svelte

@@ -1,5 +1,10 @@
 <script>
   import { createEventDispatcher } from 'svelte';
+  import plusWhite from '$lib/assets/icons/plus-white.svg';
+  import editLight from '$lib/assets/icons/edit-light.svg';
+  import editDark from '$lib/assets/icons/edit-dark.svg';
+  import trashLight from '$lib/assets/icons/trash-light.svg';
+  import trashDark from '$lib/assets/icons/trash-dark.svg';
 
   // Título do bloco da tabela
   export let title = 'Lista';
@@ -11,6 +16,9 @@
 
   // Controla exibição da coluna de ações
   export let showActions = true;
+  // Controla botões individuais de ação
+  export let showEdit = true;
+  export let showDelete = true;
 
   const dispatch = createEventDispatcher();
 
@@ -18,12 +26,12 @@
     dispatch('addTop');
   }
 
-  function handleEditRow(row) {
-    dispatch('editRow', { row });
+  function handleEditRow(row, index) {
+    dispatch('editRow', { row, index });
   }
 
-  function handleDeleteRow(row) {
-    dispatch('deleteRow', { row });
+  function handleDeleteRow(row, index) {
+    dispatch('deleteRow', { row, index });
   }
 </script>
 
@@ -36,7 +44,7 @@
       title="Adicionar"
       on:click={handleAddTop}
     >
-      <img src="/icons/plus-white.svg" alt="Adicionar" class="w-6 h-6" />
+      <img src={plusWhite} alt="Adicionar" class="w-6 h-6" />
       <span class="sr-only">Adicionar</span>
     </button>
   </div>
@@ -57,8 +65,8 @@
       </thead>
       <tbody class="divide-y divide-gray-200 dark:divide-gray-700 bg-white dark:bg-gray-800">
         {#if data.length > 0}
-          {#each data as row}
-            <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/60">
+          {#each data as row, index}
+            <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/60" on:dblclick={() => handleEditRow(row, index)}>
               {#each columns as col}
                 <td class="px-6 py-3 text-base text-gray-700 dark:text-gray-300">
                   {row[col.key]}
@@ -67,26 +75,30 @@
               {#if showActions}
                 <td class="px-6 py-3 text-sm text-right whitespace-nowrap">
                   <div class="inline-flex items-center gap-2">
-                    <button
-                      class="inline-flex items-center justify-center w-9 h-9 rounded-md border border-gray-200 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700"
-                      type="button"
-                      title="Editar"
-                      on:click={() => handleEditRow(row)}
-                    >
-                      <img src="/icons/edit-light.svg" alt="Editar" class="w-5 h-5 block dark:hidden" />
-                      <img src="/icons/edit-dark.svg" alt="Editar" class="w-5 h-5 hidden dark:block" />
-                      <span class="sr-only">Editar</span>
-                    </button>
-                    <button
-                      class="inline-flex items-center justify-center w-9 h-9 rounded-md border border-gray-200 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700"
-                      type="button"
-                      title="Excluir"
-                      on:click={() => handleDeleteRow(row)}
-                    >
-                      <img src="/icons/trash-light.svg" alt="Excluir" class="w-5 h-5 block dark:hidden" />
-                      <img src="/icons/trash-dark.svg" alt="Excluir" class="w-5 h-5 hidden dark:block" />
-                      <span class="sr-only">Excluir</span>
-                    </button>
+                    {#if showEdit}
+                      <button
+                        class="inline-flex items-center justify-center w-9 h-9 rounded-md border border-gray-200 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700"
+                        type="button"
+                        title="Editar"
+                        on:click={() => handleEditRow(row, index)}
+                      >
+                        <img src={editLight} alt="Editar" class="w-5 h-5 block dark:hidden" />
+                        <img src={editDark} alt="Editar" class="w-5 h-5 hidden dark:block" />
+                        <span class="sr-only">Editar</span>
+                      </button>
+                    {/if}
+                    {#if showDelete}
+                      <button
+                        class="inline-flex items-center justify-center w-9 h-9 rounded-md border border-gray-200 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700"
+                        type="button"
+                        title="Excluir"
+                        on:click={() => handleDeleteRow(row, index)}
+                      >
+                        <img src={trashLight} alt="Excluir" class="w-5 h-5 block dark:hidden" />
+                        <img src={trashDark} alt="Excluir" class="w-5 h-5 hidden dark:block" />
+                        <span class="sr-only">Excluir</span>
+                      </button>
+                    {/if}
                   </div>
                 </td>
               {/if}

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


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

@@ -0,0 +1,244 @@
+<script>
+  import { createEventDispatcher, onDestroy } from 'svelte';
+
+  // Control props
+  export let visible = false;
+  export let title = 'Editar Commodity';
+  export let saveText = 'Salvar';
+  export let cancelText = 'Cancelar';
+
+  // Initial value
+  // Expected shape:
+  // {
+  //   tipo: 'graos_60kg',
+  //   descricao: '',
+  //   quantidade: number|string,
+  //   preco: number|string,
+  //   vencimentoPagamento: 'YYYY-MM-DD',
+  //   dataLimiteEntrega: 'YYYY-MM-DD',
+  //   cpr: boolean,
+  //   arquivos: File[] | Array<{ name: string, url?: string, type?: string, size?: number }>
+  // }
+  export let value = {};
+
+  const dispatch = createEventDispatcher();
+
+  const defaults = {
+    tipo: 'graos_60kg',
+    descricao: '',
+    quantidade: '',
+    preco: '',
+    vencimentoPagamento: '',
+    dataLimiteEntrega: '',
+    cpr: false,
+    arquivos: []
+  };
+
+  let local = { ...defaults };
+
+  // Manage object URLs for previews
+  let objectUrls = [];
+  onDestroy(() => {
+    objectUrls.forEach((u) => URL.revokeObjectURL(u));
+    objectUrls = [];
+  });
+
+  // Sync when modal opens
+  $: if (visible) {
+    objectUrls.forEach((u) => URL.revokeObjectURL(u));
+    objectUrls = [];
+    local = {
+      ...defaults,
+      ...value,
+      arquivos: value?.arquivos ? [...value.arquivos] : []
+    };
+  }
+
+  function isFileLike(item) {
+    return typeof File !== 'undefined' && item instanceof File;
+  }
+
+  function filePreview(item) {
+    try {
+      if (isFileLike(item)) {
+        const url = URL.createObjectURL(item);
+        objectUrls.push(url);
+        return url;
+      }
+      if (item && item.url) return item.url;
+      return null;
+    } catch {
+      return null;
+    }
+  }
+
+  function formatSize(bytes) {
+    if (!bytes && bytes !== 0) return '';
+    const units = ['B', 'KB', 'MB', 'GB'];
+    let i = 0;
+    let size = bytes;
+    while (size >= 1024 && i < units.length - 1) {
+      size /= 1024;
+      i++;
+    }
+    return `${size.toFixed(1)} ${units[i]}`;
+  }
+
+  function handleFilesChange(event) {
+    const files = Array.from(event.currentTarget.files || []);
+    local.arquivos = files;
+  }
+
+  function handleSave() {
+    dispatch('save', { value: local });
+  }
+
+  function handleCancel() {
+    dispatch('cancel');
+  }
+</script>
+
+{#if visible}
+  <div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
+    <div class="bg-white dark:bg-gray-800 w-full max-w-3xl rounded-lg shadow-lg">
+      <div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center">
+        <h3 class="text-lg font-semibold text-gray-800 dark:text-gray-100">{title}</h3>
+        <button
+          class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
+          on:click={handleCancel}
+          aria-label="Fechar"
+        >
+          ✕
+        </button>
+      </div>
+
+      <div class="p-6 space-y-5 max-h-[75vh] overflow-y-auto">
+        <!-- Tipo -->
+        <div>
+          <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="tipo">Tipo</label>
+          <select
+            id="tipo"
+            class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
+            bind:value={local.tipo}
+          >
+            <option value="graos_60kg">Grãos em sacas de 60kg</option>
+          </select>
+        </div>
+
+        <!-- Descrição, Quantidade, Preço -->
+        <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
+          <div>
+            <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="descricao">Descrição</label>
+            <input
+              id="descricao"
+              class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
+              bind:value={local.descricao}
+              placeholder="Descreva a commodity"
+            />
+          </div>
+          <div>
+            <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="quantidade">Quantidade</label>
+            <input
+              id="quantidade"
+              type="number"
+              class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
+              bind:value={local.quantidade}
+              min="0"
+              step="1"
+              placeholder="0"
+            />
+          </div>
+          <div>
+            <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="preco">Preço (R$)</label>
+            <input
+              id="preco"
+              type="number"
+              class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
+              bind:value={local.preco}
+              min="0"
+              step="0.01"
+              placeholder="0,00"
+            />
+          </div>
+        </div>
+
+        <!-- Datas -->
+        <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
+          <div>
+            <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="vencimentoPagamento">Vencimento do Pagamento</label>
+            <input
+              id="vencimentoPagamento"
+              type="date"
+              class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
+              bind:value={local.vencimentoPagamento}
+            />
+          </div>
+          <div>
+            <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="dataLimiteEntrega">Data Limite da Entrega</label>
+            <input
+              id="dataLimiteEntrega"
+              type="date"
+              class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
+              bind:value={local.dataLimiteEntrega}
+            />
+          </div>
+        </div>
+
+        <!-- CPR Checkbox -->
+        <div class="flex items-center gap-2">
+          <input
+            id="cpr"
+            type="checkbox"
+            class="h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
+            bind:checked={local.cpr}
+          />
+          <label for="cpr" class="text-sm text-gray-700 dark:text-gray-300">CPR</label>
+        </div>
+
+        <!-- Arquivos -->
+        <div>
+          <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="arquivos">Anexos</label>
+          <input
+            id="arquivos"
+            type="file"
+            class="block w-full text-sm text-gray-700 dark:text-gray-200 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100"
+            multiple
+            on:change={handleFilesChange}
+          />
+          {#if local.arquivos && local.arquivos.length}
+            <ul class="mt-3 space-y-2">
+              {#each local.arquivos as item}
+                <li class="flex items-center gap-3 p-2 rounded-md border border-gray-200 dark:border-gray-700">
+                  {#if (isFileLike(item) && item.type?.startsWith('image')) || (!isFileLike(item) && item.type?.startsWith?.('image'))}
+                    {#if filePreview(item)}
+                      <img src={filePreview(item)} alt={isFileLike(item) ? item.name : item.name} class="w-10 h-10 object-cover rounded" />
+                    {/if}
+                  {/if}
+                  <div class="flex-1 min-w-0">
+                    <div class="text-sm text-gray-800 dark:text-gray-100 truncate">{isFileLike(item) ? item.name : item.name}</div>
+                    <div class="text-xs text-gray-500 dark:text-gray-400">{isFileLike(item) ? formatSize(item.size) : (item.size ? formatSize(item.size) : (item.type || ''))}</div>
+                  </div>
+                </li>
+              {/each}
+            </ul>
+          {/if}
+        </div>
+      </div>
+
+      <div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700 flex justify-end gap-2">
+        <button
+          class="px-4 py-2 rounded-md border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700"
+          on:click={handleCancel}
+        >
+          {cancelText}
+        </button>
+        <button
+          class="px-4 py-2 rounded-md bg-blue-600 hover:bg-blue-700 text-white"
+          on:click={handleSave}
+        >
+          {saveText}
+        </button>
+      </div>
+    </div>
+  </div>
+{/if}

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

@@ -0,0 +1,184 @@
+<script>
+    import { createEventDispatcher } from 'svelte';
+    let tipo = "";
+    let descricao = "";
+    let quantidade = "";
+    let preco = "";
+    let vencimentoPagamento = "";
+    let dataLimiteEntrega = "";
+    let cpr = false;
+    let arquivos = [];
+    let fileInput;
+    const dispatch = createEventDispatcher();
+
+    function addFiles(files) {
+      if (!files || !files.length) return;
+      arquivos = [...arquivos, ...Array.from(files)];
+    }
+
+    function handleFiles(event) {
+      addFiles(event?.target?.files);
+    }
+
+    function handleDrop(event) {
+      event.preventDefault();
+      addFiles(event?.dataTransfer?.files);
+    }
+
+    function handleDragOver(event) {
+      event.preventDefault();
+    }
+
+    function removeFile(index) {
+      arquivos = arquivos.filter((_, i) => i !== index);
+    }
+
+    function submitForm() {
+      const payload = {
+        tipo,
+        descricao,
+        quantidade,
+        preco,
+        vencimentoPagamento,
+        dataLimiteEntrega,
+        cpr,
+        arquivos
+      };
+      dispatch('submit', payload);
+    }
+  </script>
+  
+  <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm">
+  <form class="space-y-4 p-4" on:submit|preventDefault={submitForm}>
+    
+
+    <!-- Tipo -->
+    <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
+      <div>
+        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Tipo *</label>
+        <select
+          bind:value={tipo}
+          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"
+        >
+          <option value="">Selecione...</option>
+          <option value="graos_60kg">Grãos em sacas de 60kg</option>
+        </select>
+      </div>
+    </div>
+
+    <!-- Descrição, Quantidade, Preço -->
+    <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
+      <div>
+        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Descrição</label>
+        <input
+          bind:value={descricao}
+          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 placeholder-gray-400 dark:placeholder-gray-400 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
+          placeholder="Descreva a commodity"
+        />
+      </div>
+      <div>
+        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Quantidade</label>
+        <input
+          type="number"
+          min="0"
+          step="1"
+          bind:value={quantidade}
+          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 placeholder-gray-400 dark:placeholder-gray-400 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
+          placeholder="0"
+        />
+      </div>
+      <div>
+        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Preço (R$)</label>
+        <input
+          type="number"
+          min="0"
+          step="0.01"
+          bind:value={preco}
+          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 placeholder-gray-400 dark:placeholder-gray-400 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
+          placeholder="0,00"
+        />
+      </div>
+    </div>
+
+    <!-- Vencimento do Pagamento, Data Limite da Entrega, CPR -->
+    <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
+      <div>
+        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Vencimento do Pagamento</label>
+        <input
+          type="date"
+          bind:value={vencimentoPagamento}
+          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 placeholder-gray-400 dark:placeholder-gray-400 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">Data Limite da Entrega</label>
+        <input
+          type="date"
+          bind:value={dataLimiteEntrega}
+          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 placeholder-gray-400 dark:placeholder-gray-400 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
+        />
+      </div>
+      <div class="flex items-end">
+        <label class="flex items-center gap-2 text-gray-700 dark:text-gray-300">
+          <input type="checkbox" bind:checked={cpr} class="accent-blue-600" />
+          CPR
+        </label>
+      </div>
+    </div>
+
+    <!-- Anexos / Arquivos -->
+    <div>
+      <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Anexos</label>
+      <div
+        class="mt-1 rounded border-2 border-dashed border-gray-300 dark:border-gray-600 p-6 text-center hover:border-blue-400 transition-colors"
+        on:drop={handleDrop}
+        on:dragover={handleDragOver}
+      >
+        <p class="text-sm text-gray-600 dark:text-gray-400">Arraste e solte os ficheiros aqui</p>
+        <p class="text-sm text-gray-600 dark:text-gray-400">ou</p>
+        <button
+          type="button"
+          class="mt-2 inline-flex items-center gap-2 rounded bg-blue-600 px-3 py-2 text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
+          on:click={() => fileInput.click()}
+        >
+          Escolher ficheiros
+        </button>
+        <input
+          id="file-input"
+          bind:this={fileInput}
+          type="file"
+          multiple
+          accept=".pdf,.jpg,.jpeg,.png,.doc,.docx,.xls,.xlsx,.csv"
+          on:change={handleFiles}
+          class="hidden"
+        />
+      </div>
+      {#if arquivos.length}
+        <ul class="mt-3 divide-y divide-gray-200 dark:divide-gray-700 rounded border border-gray-200 dark:border-gray-700">
+          {#each arquivos as file, i}
+            <li class="flex items-center justify-between px-3 py-2 text-sm">
+              <span class="text-gray-700 dark:text-gray-300 truncate">{file.name} ({Math.round(file.size / 1024)} KB)</span>
+              <button
+                type="button"
+                class="ml-4 text-red-600 hover:text-red-700"
+                on:click={() => removeFile(i)}
+              >
+                Remover
+              </button>
+            </li>
+          {/each}
+        </ul>
+      {/if}
+    </div>
+
+    <div class="mt-6 flex justify-end">
+      <button
+        type="submit"
+        class="inline-flex items-center rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
+      >
+        Salvar
+      </button>
+    </div>
+
+  </form>
+  </div>

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

@@ -0,0 +1,249 @@
+<script>
+  import { createEventDispatcher, onDestroy } from 'svelte';
+
+  // Control props
+  export let visible = false;
+  export let title = 'Editar CPR';
+  export let saveText = 'Salvar';
+  export let cancelText = 'Cancelar';
+
+  // Initial CPR value
+  // Expected shape:
+  // {
+  //   tipo: 'graos_60kg',
+  //   descricao: '',
+  //   quantidade: number,
+  //   preco: number,
+  //   vencimentoPagamento: 'YYYY-MM-DD',
+  //   dataLimiteEntrega: 'YYYY-MM-DD',
+  //   cpr: boolean,
+  //   anexos: File[] | Array<{ name: string, url?: string, type?: string, size?: number }>
+  // }
+  export let value = {};
+
+  const dispatch = createEventDispatcher();
+
+  const defaults = {
+    tipo: 'graos_60kg',
+    descricao: '',
+    quantidade: '',
+    preco: '',
+    vencimentoPagamento: '',
+    dataLimiteEntrega: '',
+    cpr: false,
+    anexos: []
+  };
+
+  let local = { ...defaults };
+
+  // Revoke created object URLs on destroy
+  let objectUrls = [];
+  onDestroy(() => {
+    objectUrls.forEach((u) => URL.revokeObjectURL(u));
+    objectUrls = [];
+  });
+
+  // Sync when modal opens
+  $: if (visible) {
+    // Reset previews URLs
+    objectUrls.forEach((u) => URL.revokeObjectURL(u));
+    objectUrls = [];
+    // Merge defaults with provided value
+    local = {
+      ...defaults,
+      ...value,
+      anexos: value?.anexos ? [...value.anexos] : []
+    };
+  }
+
+  function handleFilesChange(event) {
+    const files = Array.from(event.currentTarget.files || []);
+    local.anexos = files;
+  }
+
+  function isFileLike(item) {
+    return typeof File !== 'undefined' && item instanceof File;
+  }
+
+  function filePreview(item) {
+    try {
+      if (isFileLike(item)) {
+        const url = URL.createObjectURL(item);
+        objectUrls.push(url);
+        return url;
+      }
+      if (item && item.url) return item.url;
+      return null;
+    } catch {
+      return null;
+    }
+  }
+
+  function formatSize(bytes) {
+    if (!bytes && bytes !== 0) return '';
+    const units = ['B', 'KB', 'MB', 'GB'];
+    let i = 0;
+    let size = bytes;
+    while (size >= 1024 && i < units.length - 1) {
+      size /= 1024;
+      i++;
+    }
+    return `${size.toFixed(1)} ${units[i]}`;
+  }
+
+  function handleSave() {
+    dispatch('save', { value: local });
+  }
+
+  function handleCancel() {
+    dispatch('cancel');
+  }
+</script>
+
+{#if visible}
+  <div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
+    <div class="bg-white dark:bg-gray-800 w-full max-w-3xl rounded-lg shadow-lg">
+      <div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center">
+        <h3 class="text-lg font-semibold text-gray-800 dark:text-gray-100">{title}</h3>
+        <button
+          class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
+          on:click={handleCancel}
+          aria-label="Fechar"
+        >
+          ✕
+        </button>
+      </div>
+
+      <div class="p-6 space-y-5 max-h-[75vh] overflow-y-auto">
+        <!-- Tipo -->
+        <div>
+          <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="tipo">Tipo</label>
+          <select
+            id="tipo"
+            class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
+            bind:value={local.tipo}
+          >
+            <option value="graos_60kg">Grãos em sacas de 60kg</option>
+          </select>
+        </div>
+
+        <!-- Descrição -->
+        <div>
+          <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="descricao">Descrição</label>
+          <textarea
+            id="descricao"
+            class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
+            rows="4"
+            bind:value={local.descricao}
+            placeholder="Descreva a CPR"
+          />
+        </div>
+
+        <!-- Quantidade e Preço -->
+        <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
+          <div>
+            <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="quantidade">Quantidade</label>
+            <input
+              id="quantidade"
+              type="number"
+              class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
+              bind:value={local.quantidade}
+              min="0"
+              step="1"
+              placeholder="0"
+            />
+          </div>
+          <div>
+            <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="preco">Preço (R$)</label>
+            <input
+              id="preco"
+              type="number"
+              class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
+              bind:value={local.preco}
+              min="0"
+              step="0.01"
+              placeholder="0,00"
+            />
+          </div>
+        </div>
+
+        <!-- Datas -->
+        <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
+          <div>
+            <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="vencimentoPagamento">Vencimento do Pagamento</label>
+            <input
+              id="vencimentoPagamento"
+              type="date"
+              class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
+              bind:value={local.vencimentoPagamento}
+            />
+          </div>
+          <div>
+            <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="dataLimiteEntrega">Data Limite da Entrega</label>
+            <input
+              id="dataLimiteEntrega"
+              type="date"
+              class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
+              bind:value={local.dataLimiteEntrega}
+            />
+          </div>
+        </div>
+
+        <!-- CPR Checkbox -->
+        <div class="flex items-center gap-2">
+          <input
+            id="cpr"
+            type="checkbox"
+            class="h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
+            bind:checked={local.cpr}
+          />
+          <label for="cpr" class="text-sm text-gray-700 dark:text-gray-300">CPR</label>
+        </div>
+
+        <!-- Anexos -->
+        <div>
+          <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="anexos">Anexos</label>
+          <input
+            id="anexos"
+            type="file"
+            class="block w-full text-sm text-gray-700 dark:text-gray-200 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100"
+            multiple
+            on:change={handleFilesChange}
+          />
+          {#if local.anexos && local.anexos.length}
+            <ul class="mt-3 space-y-2">
+              {#each local.anexos as item, i}
+                <li class="flex items-center gap-3 p-2 rounded-md border border-gray-200 dark:border-gray-700">
+                  {#if (isFileLike(item) && item.type?.startsWith('image')) || (!isFileLike(item) && item.type?.startsWith?.('image'))}
+                    {#if filePreview(item)}
+                      <img src={filePreview(item)} alt={isFileLike(item) ? item.name : item.name} class="w-10 h-10 object-cover rounded" />
+                    {/if}
+                  {/if}
+                  <div class="flex-1 min-w-0">
+                    <div class="text-sm text-gray-800 dark:text-gray-100 truncate">{isFileLike(item) ? item.name : item.name}</div>
+                    <div class="text-xs text-gray-500 dark:text-gray-400">{isFileLike(item) ? formatSize(item.size) : (item.size ? formatSize(item.size) : (item.type || ''))}</div>
+                  </div>
+                </li>
+              {/each}
+            </ul>
+          {/if}
+        </div>
+      </div>
+
+      <div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700 flex justify-end gap-2">
+        <button
+          class="px-4 py-2 rounded-md border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700"
+          on:click={handleCancel}
+        >
+          {cancelText}
+        </button>
+        <button
+          class="px-4 py-2 rounded-md bg-blue-600 hover:bg-blue-700 text-white"
+          on:click={handleSave}
+        >
+          {saveText}
+        </button>
+      </div>
+    </div>
+  </div>
+{/if}

+ 62 - 0
src/lib/components/settings/ChangeEmail.svelte

@@ -0,0 +1,62 @@
+<script>
+  import { createEventDispatcher } from 'svelte';
+  const dispatch = createEventDispatcher();
+
+  let email = '';
+  let currentPassword = '';
+  let errors = { email: '', currentPassword: '' };
+
+  function validate() {
+    errors = { email: '', currentPassword: '' };
+    const emailRegex = /[^@\s]+@[^@\s]+\.[^@\s]+/;
+    if (!email || !emailRegex.test(email)) {
+      errors.email = 'Informe um e-mail válido';
+    }
+    if (!currentPassword || currentPassword.length < 6) {
+      errors.currentPassword = 'Informe sua senha atual (mín. 6 caracteres)';
+    }
+    return !errors.email && !errors.currentPassword;
+  }
+
+  function handleSubmit() {
+    if (!validate()) return;
+    dispatch('submit', { email, currentPassword });
+  }
+</script>
+
+<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm">
+  <form class="space-y-4 p-4" on:submit|preventDefault={handleSubmit}>
+    <h3 class="text-base font-semibold text-gray-800 dark:text-gray-100">Alterar e-mail</h3>
+
+    <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
+      <div>
+        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Novo e-mail</label>
+        <input
+          bind:value={email}
+          type="email"
+          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"
+          placeholder="seuemail@exemplo.com"
+        />
+        {#if errors.email}
+          <p class="mt-1 text-sm text-red-600">{errors.email}</p>
+        {/if}
+      </div>
+      <div>
+        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Senha atual</label>
+        <input
+          bind:value={currentPassword}
+          type="password"
+          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"
+          placeholder="********"
+        />
+        {#if errors.currentPassword}
+          <p class="mt-1 text-sm text-red-600">{errors.currentPassword}</p>
+        {/if}
+      </div>
+    </div>
+
+    <div class="flex justify-end">
+      <button type="submit" class="px-4 py-2 rounded bg-blue-600 hover:bg-blue-700 text-white">Salvar e-mail</button>
+    </div>
+  </form>
+</div>

+ 77 - 0
src/lib/components/settings/ChangePassword.svelte

@@ -0,0 +1,77 @@
+<script>
+  import { createEventDispatcher } from 'svelte';
+  const dispatch = createEventDispatcher();
+
+  let currentPassword = '';
+  let newPassword = '';
+  let confirmPassword = '';
+  let errors = { currentPassword: '', newPassword: '', confirmPassword: '' };
+
+  function validate() {
+    errors = { currentPassword: '', newPassword: '', confirmPassword: '' };
+    if (!currentPassword || currentPassword.length < 6) {
+      errors.currentPassword = 'Informe sua senha atual (mín. 6 caracteres)';
+    }
+    if (!newPassword || newPassword.length < 6) {
+      errors.newPassword = 'Nova senha deve ter no mínimo 6 caracteres';
+    }
+    if (newPassword && confirmPassword !== newPassword) {
+      errors.confirmPassword = 'Confirmação diferente da nova senha';
+    }
+    return !errors.currentPassword && !errors.newPassword && !errors.confirmPassword;
+  }
+
+  function handleSubmit() {
+    if (!validate()) return;
+    dispatch('submit', { currentPassword, newPassword });
+  }
+</script>
+
+<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm">
+  <form class="space-y-4 p-4" on:submit|preventDefault={handleSubmit}>
+    <h3 class="text-base font-semibold text-gray-800 dark:text-gray-100">Alterar senha</h3>
+
+    <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
+      <div>
+        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Senha atual</label>
+        <input
+          bind:value={currentPassword}
+          type="password"
+          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"
+          placeholder="********"
+        />
+        {#if errors.currentPassword}
+          <p class="mt-1 text-sm text-red-600">{errors.currentPassword}</p>
+        {/if}
+      </div>
+      <div>
+        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Nova senha</label>
+        <input
+          bind:value={newPassword}
+          type="password"
+          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"
+          placeholder="********"
+        />
+        {#if errors.newPassword}
+          <p class="mt-1 text-sm text-red-600">{errors.newPassword}</p>
+        {/if}
+      </div>
+      <div>
+        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Confirmar nova senha</label>
+        <input
+          bind:value={confirmPassword}
+          type="password"
+          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"
+          placeholder="********"
+        />
+        {#if errors.confirmPassword}
+          <p class="mt-1 text-sm text-red-600">{errors.confirmPassword}</p>
+        {/if}
+      </div>
+    </div>
+
+    <div class="flex justify-end">
+      <button type="submit" class="px-4 py-2 rounded bg-blue-600 hover:bg-blue-700 text-white">Salvar senha</button>
+    </div>
+  </form>
+</div>

+ 46 - 0
src/lib/components/ui/PopUpDelete.svelte

@@ -0,0 +1,46 @@
+<script>
+  import { createEventDispatcher } from 'svelte';
+
+  export let visible = false;
+  export let title = 'Confirmar ação';
+  export let confirmText = 'Confirmar';
+  export let cancelText = 'Cancelar';
+  export let disableBackdropClose = false;
+
+  const dispatch = createEventDispatcher();
+
+  function onConfirm() {
+    dispatch('confirm');
+  }
+  function onCancel() {
+    dispatch('cancel');
+  }
+
+  function backdropClick(e) {
+    if (disableBackdropClose) return;
+    // close only if clicked directly on backdrop
+    if (e.currentTarget === e.target) onCancel();
+  }
+</script>
+
+{#if visible}
+  <div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50" on:click={backdropClick}>
+    <div class="bg-white dark:bg-gray-800 w-full max-w-sm rounded-lg shadow-lg">
+      <div class="px-6 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">{title}</h3>
+        <button class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" aria-label="Fechar" on:click={onCancel}>✕</button>
+      </div>
+
+      <div class="p-6 text-sm text-gray-700 dark:text-gray-300">
+        <slot>
+          Tem certeza que deseja continuar?
+        </slot>
+      </div>
+
+      <div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700 flex justify-end gap-3">
+        <button class="px-4 py-2 rounded-md border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700" on:click={onCancel}>{cancelText}</button>
+        <button class="px-4 py-2 rounded-md bg-red-600 hover:bg-red-700 text-white" on:click={onConfirm}>{confirmText}</button>
+      </div>
+    </div>
+  </div>
+{/if}

+ 114 - 0
src/lib/components/users/UserCreateModal.svelte

@@ -0,0 +1,114 @@
+<script>
+  import { createEventDispatcher } from 'svelte';
+  const dispatch = createEventDispatcher();
+
+  export let visible = false;
+  export let title = 'Novo Usuário';
+  export let saveText = 'Criar usuário';
+  export let cancelText = 'Cancelar';
+
+  let nome = '';
+  let email = '';
+  let role = 'user';
+  let password = '';
+  let confirmPassword = '';
+
+  let errors = { nome: '', email: '', password: '', confirmPassword: '' };
+
+  function validate() {
+    errors = { nome: '', email: '', password: '', confirmPassword: '' };
+    const emailRegex = /[^@\s]+@[^@\s]+\.[^@\s]+/;
+    if (!nome.trim()) errors.nome = 'Informe o nome';
+    if (!email || !emailRegex.test(email)) errors.email = 'Informe um e-mail válido';
+    if (!password || password.length < 6) errors.password = 'Senha mínima de 6 caracteres';
+    if (confirmPassword !== password) errors.confirmPassword = 'Confirmação diferente da senha';
+    return !errors.nome && !errors.email && !errors.password && !errors.confirmPassword;
+  }
+
+  function handleSave() {
+    if (!validate()) return;
+    dispatch('submit', { nome, email, role, password });
+  }
+
+  function handleCancel() {
+    dispatch('cancel');
+  }
+</script>
+
+{#if visible}
+  <div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
+    <div class="bg-white dark:bg-gray-800 w-full max-w-xl rounded-lg shadow-lg">
+      <div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center">
+        <h3 class="text-lg font-semibold text-gray-800 dark:text-gray-100">{title}</h3>
+        <button class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" on:click={handleCancel} aria-label="Fechar">✕</button>
+      </div>
+
+      <div class="p-6 space-y-4">
+        <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
+          <div>
+            <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Nome</label>
+            <input
+              bind:value={nome}
+              class="mt-1 block w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
+              placeholder="Nome completo"
+            />
+            {#if errors.nome}
+              <p class="mt-1 text-sm text-red-600">{errors.nome}</p>
+            {/if}
+          </div>
+          <div>
+            <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">E-mail</label>
+            <input
+              bind:value={email}
+              type="email"
+              class="mt-1 block w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
+              placeholder="usuario@empresa.com"
+            />
+            {#if errors.email}
+              <p class="mt-1 text-sm text-red-600">{errors.email}</p>
+            {/if}
+          </div>
+        </div>
+
+        <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
+          <div>
+            <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Perfil</label>
+            <select bind:value={role} class="mt-1 block w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
+              <option value="user">Usuário</option>
+              <option value="admin">Administrador</option>
+            </select>
+          </div>
+          <div>
+            <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Senha</label>
+            <input
+              bind:value={password}
+              type="password"
+              class="mt-1 block w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
+              placeholder="********"
+            />
+            {#if errors.password}
+              <p class="mt-1 text-sm text-red-600">{errors.password}</p>
+            {/if}
+          </div>
+          <div>
+            <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Confirmar senha</label>
+            <input
+              bind:value={confirmPassword}
+              type="password"
+              class="mt-1 block w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
+              placeholder="********"
+            />
+            {#if errors.confirmPassword}
+              <p class="mt-1 text-sm text-red-600">{errors.confirmPassword}</p>
+            {/if}
+          </div>
+        </div>
+      </div>
+
+      <div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700 flex justify-end gap-2">
+        <button class="px-4 py-2 rounded-md border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700" on:click={handleCancel}>{cancelText}</button>
+        <button class="px-4 py-2 rounded-md bg-blue-600 hover:bg-blue-700 text-white" on:click={handleSave}>{saveText}</button>
+      </div>
+    </div>
+  </div>
+{/if}

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

@@ -1,6 +1,7 @@
 <script>
 	import '../app.css';
 	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 { browser } from '$app/environment';
@@ -16,7 +17,7 @@
   </script>
   
   <svelte:head>
-	<link rel="icon" href={favicon} />
+	<link rel="icon" href={logo} />
   </svelte:head>
   
   <div class="flex min-h-screen">

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

@@ -6,5 +6,5 @@
 
 <div>
   <Header title="Bank" subtitle="Serviços bancários" breadcrumb={breadcrumb} />
-  <EmptyState title="Área em Desenvolvimento" subtitle="Esta funcionalidade estará disponível em breve." />
+  <EmptyState title="Área em Desenvolvimento" description="Esta funcionalidade estará disponível em breve." />
 </div>

+ 138 - 2
src/routes/commodities/+page.svelte

@@ -1,10 +1,146 @@
 <script>
   import Header from '$lib/layout/Header.svelte';
-  import EmptyState from '$lib/components/ui/EmptyState.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 ConfirmModal from '$lib/components/ui/PopUpDelete.svelte';
+
   const breadcrumb = [{ label: 'Início' }, { label: 'Commodities', active: true }];
+
+  let activeModal = 0;
+
+  let columns = [
+    { key: "nome", label: "Nome" },
+    { key: "status", label: "Status" }
+  ];
+
+  let data = [
+    { nome: "Commodity A", status: "Disponível" },
+    { nome: "Commodity B", status: "Indisponível" }
+  ];
+
+  const tabs = ["Registro"];
+
+  let showEdit = false;
+  let selectedRow = null;
+  let editValue = {};
+  let showDeleteConfirm = false;
+  let rowToDelete = null;
+
+  function handleAddTop() {
+    activeModal = 1;
+  }
+
+  function handleCancel() {
+    activeModal = 0;
+  }
+
+  function handleRegisterSubmit(event) {
+    const payload = event?.detail;
+    // TODO: substituir por chamada de API/ação do SvelteKit
+    if (payload?.descricao) {
+      data = [
+        ...data,
+        { nome: payload.descricao, status: payload.cpr ? 'Com CPR' : 'Sem CPR' }
+      ];
+    }
+    activeModal = 0;
+  }
+
+  function handleEditRow(e) {
+    const { row } = e?.detail || {};
+    if (!row) return;
+    selectedRow = row;
+    editValue = {
+      tipo: row.tipo ?? 'graos_60kg',
+      descricao: row.descricao ?? row.nome ?? '',
+      quantidade: row.quantidade ?? '',
+      preco: row.preco ?? '',
+      vencimentoPagamento: row.vencimentoPagamento ?? '',
+      dataLimiteEntrega: row.dataLimiteEntrega ?? '',
+      cpr: row.cpr ?? (row.status === 'Com CPR'),
+      arquivos: row.arquivos ?? []
+    };
+    showEdit = true;
+  }
+
+  function handleDeleteRow(e) {
+    const { row } = e?.detail || {};
+    if (!row) return;
+    rowToDelete = row;
+    showDeleteConfirm = true;
+  }
+
+  function confirmDelete() {
+    data = data.filter((r) => r !== rowToDelete);
+    showDeleteConfirm = false;
+    rowToDelete = null;
+  }
+
+  function cancelDelete() {
+    showDeleteConfirm = false;
+    rowToDelete = null;
+  }
+
+  function handleCommoditySave(e) {
+    const { value } = e.detail || {};
+    if (selectedRow && value) {
+      data = data.map((r) =>
+        r === selectedRow
+          ? {
+              ...r,
+              ...value,
+              nome: value.descricao && value.descricao.trim() ? value.descricao : r.nome,
+              status: value.cpr ? 'Com CPR' : 'Sem CPR'
+            }
+          : r
+      );
+    }
+    handleCommodityCancel();
+  }
+
+  function handleCommodityCancel() {
+    showEdit = false;
+    selectedRow = null;
+    editValue = {};
+  }
 </script>
 
 <div>
   <Header title="Commodities" subtitle="Gestão de commodities" breadcrumb={breadcrumb} />
-  <EmptyState title="Área em Desenvolvimento" subtitle="Esta funcionalidade estará disponível em breve." />
+  <div class="p-4">
+    <div class="max-w-6xl mx-auto mt-4">
+      {#if activeModal === 0}
+      <Tables
+        title="Produtos"
+        columns={columns}
+        data={data}
+        on:addTop={handleAddTop}
+        on:editRow={handleEditRow}
+        on:deleteRow={handleDeleteRow}
+      />
+      <ConfirmModal
+        visible={showDeleteConfirm}
+        title="Confirmar exclusão"
+        confirmText="Excluir"
+        cancelText="Cancelar"
+        on:confirm={confirmDelete}
+        on:cancel={cancelDelete}
+      >
+        <p>Tem certeza que deseja excluir a commodity "{rowToDelete?.nome}"?</p>
+      </ConfirmModal>
+      <CommodityEditModal
+        visible={showEdit}
+        title="Editar Commodity"
+        value={editValue}
+        on:save={handleCommoditySave}
+        on:cancel={handleCommodityCancel}
+      />
+      {:else if activeModal === 1}
+      <div class="mb-4"><Tabs {tabs} showCloseIcon={true} on:close={handleCancel} /></div>
+      <RegisterCommodity on:submit={handleRegisterSubmit} />
+      {/if}
+    </div>
+  </div>
 </div>

+ 66 - 9
src/routes/cpr/+page.svelte

@@ -1,10 +1,21 @@
 <script>
   import Header from '$lib/layout/Header.svelte';
-  import Tabs from '$lib/components/containers/cpr/Tabs.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 ConfirmModal from '$lib/components/ui/PopUpDelete.svelte';
+
+  // EditModal
+  let showCprEdit = false;
+  let selectedRow = null;
+  let editValue = {};
+
+  // Delete confirmation
+  let showDeleteConfirm = false;
+  let rowToDelete = null;
 
   const breadcrumb = [{ label: 'Início' }, { label: 'CPR', active: true }];
   let activeTab = 4;
@@ -21,22 +32,36 @@
   ];
 
   function handleAddTop() {
-    // Ao clicar em "+", mudar para a aba de Registro (index 1) e exibir Tabs + Form
+    // ao clicar em "+", mudar para a aba de Registro (index 1) e exibir Tabs + Form
     activeTab = 0;
   }
 
   function handleDeleteRow(e) {
     const { row } = e.detail;
-    // Remove pelo objeto (referência) para manter simples no exemplo
-    data = data.filter((r) => r !== row);
+    rowToDelete = row;
+    showDeleteConfirm = true;
   }
 
   function handleEditRow(e) {
     const { row } = e.detail;
-    // Exemplo simples: alterna status ao editar
-    data = data.map((r) =>
-      r === row ? { ...r, status: r.status === 'Disponível' ? 'Indisponível' : 'Disponível' } : r
-    );
+    selectedRow = row;
+    editValue = { ...row }; 
+    showCprEdit = true;
+  }
+
+  function handleCprSave(e) {
+    const { value } = e.detail;
+    if (selectedRow) {
+      // Atualiza a linha com os dados editados
+      data = data.map((r) => (r === selectedRow ? { ...r, ...value } : r));
+    }
+    handleCprCancel();
+  }
+
+  function handleCprCancel() {
+    showCprEdit = false;
+    selectedRow = null;
+    editValue = {};
   }
 
   function handleFinalize() {
@@ -50,6 +75,18 @@
     // Volta para a tabela principal cancelando o processo
     activeTab = 4;
   }
+
+  function confirmDelete() {
+    // Remove pelo objeto (referência) para manter simples no exemplo
+    data = data.filter((r) => r !== rowToDelete);
+    showDeleteConfirm = false;
+    rowToDelete = null;
+  }
+
+  function cancelDelete() {
+    showDeleteConfirm = false;
+    rowToDelete = null;
+  }
 </script>
 
 <div>
@@ -59,13 +96,21 @@
     
       {#if activeTab === 4}
         <Tables
-          title="Produtos"
+          title="CPRs"
           columns={columns}
           data={data}
           on:addTop={handleAddTop}
           on:editRow={handleEditRow}
           on:deleteRow={handleDeleteRow}
+          
         />
+        <CprEditModal
+        visible={showCprEdit}
+        title="Editar CPR"
+        value={editValue}
+        on:save={handleCprSave}
+        on:cancel={handleCprCancel}
+      />
       {:else if activeTab === 0}
       <Tabs {tabs} bind:active={activeTab} showCloseIcon={true} on:close={handleCancel} />
       <div class="mt-4">
@@ -126,7 +171,19 @@
           Finalizar CPR
         </button>
       </div>
+
       {/if}
     </div>
   </div>
 </div>
+
+<ConfirmModal
+  visible={showDeleteConfirm}
+  title="Confirmar exclusão"
+  confirmText="Excluir"
+  cancelText="Cancelar"
+  on:confirm={confirmDelete}
+  on:cancel={cancelDelete}
+>
+  <p>Tem certeza que deseja excluir a CPR "{rowToDelete?.nome}"?</p>
+</ConfirmModal>

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

@@ -6,5 +6,5 @@
 
 <div>
   <Header title="Marketplace" subtitle="Catálogo e negociação" breadcrumb={breadcrumb} />
-  <EmptyState title="Área em Desenvolvimento" subtitle="Esta funcionalidade estará disponível em breve." />
+  <EmptyState title="Área em Desenvolvimento" description="Esta funcionalidade estará disponível em breve." />
 </div>

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

@@ -6,5 +6,5 @@
 
 <div>
   <Header title="Operação" subtitle="Operações do sistema" breadcrumb={breadcrumb} />
-  <EmptyState title="Área em Desenvolvimento" subtitle="Esta funcionalidade estará disponível em breve." />
+  <EmptyState title="Área em Desenvolvimento" description="Esta funcionalidade estará disponível em breve." />
 </div>

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

@@ -6,6 +6,6 @@
 
 <div>
   <Header title="Relatórios" subtitle="Indicadores e relatórios" breadcrumb={breadcrumb} />
-  <EmptyState title="Área em Desenvolvimento" subtitle="Esta funcionalidade estará disponível em breve." />
+  <EmptyState title="Área em Desenvolvimento" description="Esta funcionalidade estará disponível em breve." />
 </div>
   

+ 19 - 3
src/routes/settings/+page.svelte

@@ -1,10 +1,26 @@
 <script>
   import Header from '$lib/layout/Header.svelte';
-  import EmptyState from '$lib/components/ui/EmptyState.svelte';
+  import ChangeEmail from '$lib/components/settings/ChangeEmail.svelte';
+  import ChangePassword from '$lib/components/settings/ChangePassword.svelte';
+
   const breadcrumb = [{ label: 'Início' }, { label: 'Configurações', active: true }];
+
+  function handleEmailSubmit(e) {
+    const { email } = e.detail || {};
+    alert(`E-mail atualizado para: ${email}`);
+  }
+
+  function handlePasswordSubmit(e) {
+    alert('Senha alterada com sucesso');
+  }
 </script>
 
 <div>
-  <Header title="Configurações" subtitle="Preferências do sistema" breadcrumb={breadcrumb} />
-  <EmptyState title="Área em Desenvolvimento" subtitle="Esta funcionalidade estará disponível em breve." />
+  <Header title="Configurações" subtitle="Gerencie sua conta" breadcrumb={breadcrumb} />
+  <div class="p-4">
+    <div class="max-w-4xl mx-auto mt-4 space-y-6">
+      <ChangeEmail on:submit={handleEmailSubmit} />
+      <ChangePassword on:submit={handlePasswordSubmit} />
+    </div>
+  </div>
 </div>

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

@@ -6,5 +6,5 @@
 
 <div>
   <Header title="Tokens" subtitle="Gestão de tokens" breadcrumb={breadcrumb} />
-  <EmptyState title="Área em Desenvolvimento" subtitle="Esta funcionalidade estará disponível em breve." />
+  <EmptyState title="Área em Desenvolvimento" description="Esta funcionalidade estará disponível em breve." />
 </div>

+ 85 - 2
src/routes/users/+page.svelte

@@ -1,10 +1,93 @@
 <script>
   import Header from '$lib/layout/Header.svelte';
-  import EmptyState from '$lib/components/ui/EmptyState.svelte';
+  import Tables from '$lib/components/containers/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 }];
+
+  // Tabela
+  let columns = [
+    { key: 'nome', label: 'Nome' },
+    { key: 'email', label: 'E-mail' },
+    { key: 'role', label: 'Perfil' }
+  ];
+
+  let data = [
+    { nome: 'Lucas Lumps', email: 'lucas@empresa.com', role: 'admin' },
+    { nome: 'Jorginho', email: 'jorginho@empresa.com', role: 'user' }
+  ];
+
+  // Modal criar usuário
+  let showCreate = false;
+
+  // Confirmação de delete
+  let showDeleteConfirm = false;
+  let rowToDelete = null;
+
+  function handleAddTop() {
+    showCreate = true;
+  }
+
+  function handleCreateSubmit(e) {
+    const payload = e?.detail;
+    if (payload?.nome && payload?.email) {
+      data = [...data, { nome: payload.nome, email: payload.email, role: payload.role || 'user' }];
+    }
+    showCreate = false;
+  }
+
+  function handleCreateCancel() {
+    showCreate = false;
+  }
+
+  function handleDeleteRow(e) {
+    const { row } = e?.detail || {};
+    if (!row) return;
+    rowToDelete = row;
+    showDeleteConfirm = true;
+  }
+
+  function confirmDelete() {
+    data = data.filter((r) => r !== rowToDelete);
+    showDeleteConfirm = false;
+    rowToDelete = null;
+  }
+
+  function cancelDelete() {
+    showDeleteConfirm = false;
+    rowToDelete = null;
+  }
 </script>
 
 <div>
   <Header title="Usuários" subtitle="Gestão de usuários" breadcrumb={breadcrumb} />
-  <EmptyState title="Área em Desenvolvimento" subtitle="Esta funcionalidade estará disponível em breve." />
+  <div class="p-4">
+    <div class="max-w-6xl mx-auto mt-4">
+      <Tables
+        title="Usuários"
+        {columns}
+        {data}
+        on:addTop={handleAddTop}
+        on:deleteRow={handleDeleteRow}
+        showEdit={false}
+      />
+
+      <UserCreateModal
+        visible={showCreate}
+        on:submit={handleCreateSubmit}
+        on:cancel={handleCreateCancel}
+      />
+
+      <ConfirmModal
+        visible={showDeleteConfirm}
+        title="Confirmar exclusão"
+        confirmText="Excluir"
+        cancelText="Cancelar"
+        on:confirm={confirmDelete}
+        on:cancel={cancelDelete}
+      >
+        <p>Tem certeza que deseja excluir o usuário "{rowToDelete?.nome}"?</p>
+      </ConfirmModal>
+    </div>
+  </div>
 </div>