| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- <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}
|