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