|
|
@@ -1,10 +1,14 @@
|
|
|
<script>
|
|
|
+ import { onMount } from 'svelte';
|
|
|
import Header from '$lib/layout/Header.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';
|
|
|
+ import { authToken } from '$lib/utils/stores';
|
|
|
+
|
|
|
+ const apiUrl = import.meta.env.VITE_API_URL;
|
|
|
|
|
|
const breadcrumb = [{ label: 'Início' }, { label: 'Commodities', active: true }];
|
|
|
|
|
|
@@ -12,13 +16,19 @@
|
|
|
|
|
|
let columns = [
|
|
|
{ key: "nome", label: "Nome" },
|
|
|
- { key: "status", label: "Status" }
|
|
|
+ { key: "flag", label: "Flag" }
|
|
|
];
|
|
|
|
|
|
- let data = [
|
|
|
- { nome: "Commodity A", status: "Disponível" },
|
|
|
- { nome: "Commodity B", status: "Indisponível" }
|
|
|
- ];
|
|
|
+ let data = [];
|
|
|
+ let loading = false;
|
|
|
+ let error = '';
|
|
|
+ let serverResponse = '';
|
|
|
+ let createLoading = false;
|
|
|
+ let createError = '';
|
|
|
+ let createSuccess = '';
|
|
|
+ let registerResetToken = 0;
|
|
|
+ let updateLoading = false;
|
|
|
+ let updateError = '';
|
|
|
|
|
|
const tabs = ["Registro"];
|
|
|
|
|
|
@@ -28,6 +38,50 @@
|
|
|
let showDeleteConfirm = false;
|
|
|
let rowToDelete = null;
|
|
|
|
|
|
+ onMount(fetchCommodities);
|
|
|
+
|
|
|
+ async function fetchCommodities() {
|
|
|
+ loading = true;
|
|
|
+ error = '';
|
|
|
+ try {
|
|
|
+ const token = $authToken;
|
|
|
+ if (!token) {
|
|
|
+ throw new Error('Sessão expirada. Faça login novamente.');
|
|
|
+ }
|
|
|
+ const res = await fetch(`${apiUrl}/commodities?flag=all`, {
|
|
|
+ headers: {
|
|
|
+ 'content-type': 'application/json',
|
|
|
+ Authorization: `Bearer ${token}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+ const raw = await res.text();
|
|
|
+ serverResponse = raw?.trim() ?? '';
|
|
|
+ console.log('Resposta do backend /commodities?flag=all:', raw);
|
|
|
+ let payload = null;
|
|
|
+ if (raw) {
|
|
|
+ try {
|
|
|
+ payload = JSON.parse(raw);
|
|
|
+ } catch (parseErr) {
|
|
|
+ console.error('Resposta inválida ao buscar commodities:', parseErr, raw);
|
|
|
+ throw new Error('Resposta inválida do servidor ao buscar commodities.');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!res.ok || payload?.status !== 'ok' || payload?.code !== 'S_OK') {
|
|
|
+ throw new Error(payload?.msg ?? 'Falha ao buscar commodities.');
|
|
|
+ }
|
|
|
+ data = (payload?.data ?? []).map((item) => ({
|
|
|
+ id: item.commodities_id,
|
|
|
+ nome: item.commodities_name ?? item.name ?? '-',
|
|
|
+ flag: item.commodities_flag ?? item.flag ?? '-'
|
|
|
+ }));
|
|
|
+ } catch (err) {
|
|
|
+ error = err?.message ?? 'Falha ao buscar commodities.';
|
|
|
+ data = [];
|
|
|
+ } finally {
|
|
|
+ loading = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
function handleAddTop() {
|
|
|
activeModal = 1;
|
|
|
}
|
|
|
@@ -36,16 +90,57 @@
|
|
|
activeModal = 0;
|
|
|
}
|
|
|
|
|
|
- function handleRegisterSubmit(event) {
|
|
|
+ async 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' }
|
|
|
- ];
|
|
|
+ if (!payload?.nome) {
|
|
|
+ createError = 'Informe o nome da commodity.';
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ createError = '';
|
|
|
+ createSuccess = '';
|
|
|
+ createLoading = true;
|
|
|
+ try {
|
|
|
+ const body = {
|
|
|
+ name: payload.nome,
|
|
|
+ flag: (payload.flag ?? 'a')
|
|
|
+ };
|
|
|
+
|
|
|
+ const token = $authToken;
|
|
|
+ const res = await fetch(`${apiUrl}/commodity/create`, {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ 'content-type': 'application/json',
|
|
|
+ ...(token ? { Authorization: `Bearer ${token}` } : {})
|
|
|
+ },
|
|
|
+ body: JSON.stringify(body)
|
|
|
+ });
|
|
|
+
|
|
|
+ const raw = await res.text();
|
|
|
+ let response = null;
|
|
|
+ if (raw) {
|
|
|
+ try {
|
|
|
+ response = JSON.parse(raw);
|
|
|
+ } catch (parseErr) {
|
|
|
+ console.error('Resposta inválida ao criar commodity:', parseErr, raw);
|
|
|
+ throw new Error('Resposta inválida do servidor ao criar commodity.');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!res.ok || response?.status !== 'ok' || response?.code !== 'S_CREATED') {
|
|
|
+ throw new Error(response?.msg ?? 'Falha ao criar commodity.');
|
|
|
+ }
|
|
|
+
|
|
|
+ const backendMsg = response?.msg;
|
|
|
+ createSuccess = backendMsg && backendMsg !== '[100] Request ok.' ? backendMsg : 'Commodity criada com sucesso.';
|
|
|
+ registerResetToken = Date.now();
|
|
|
+ activeModal = 0;
|
|
|
+ await fetchCommodities();
|
|
|
+ } catch (err) {
|
|
|
+ createError = err?.message ?? 'Falha ao criar commodity.';
|
|
|
+ } finally {
|
|
|
+ createLoading = false;
|
|
|
}
|
|
|
- activeModal = 0;
|
|
|
}
|
|
|
|
|
|
function handleEditRow(e) {
|
|
|
@@ -53,15 +148,11 @@
|
|
|
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 ?? []
|
|
|
+ id: row.id,
|
|
|
+ nome: row.nome ?? '',
|
|
|
+ flag: row.flag ?? 'a'
|
|
|
};
|
|
|
+ updateError = '';
|
|
|
showEdit = true;
|
|
|
}
|
|
|
|
|
|
@@ -72,38 +163,120 @@
|
|
|
showDeleteConfirm = true;
|
|
|
}
|
|
|
|
|
|
- function confirmDelete() {
|
|
|
- data = data.filter((r) => r !== rowToDelete);
|
|
|
- showDeleteConfirm = false;
|
|
|
- rowToDelete = null;
|
|
|
+ let deleteLoading = false;
|
|
|
+ let deleteError = '';
|
|
|
+
|
|
|
+ async function confirmDelete() {
|
|
|
+ if (!rowToDelete?.id) {
|
|
|
+ showDeleteConfirm = false;
|
|
|
+ rowToDelete = null;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ deleteError = '';
|
|
|
+ deleteLoading = true;
|
|
|
+ try {
|
|
|
+ const token = $authToken;
|
|
|
+ const res = await fetch(`${apiUrl}/commodity/delete`, {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ 'content-type': 'application/json',
|
|
|
+ ...(token ? { Authorization: `Bearer ${token}` } : {})
|
|
|
+ },
|
|
|
+ body: JSON.stringify({ commodities_id: rowToDelete.id })
|
|
|
+ });
|
|
|
+
|
|
|
+ const raw = await res.text();
|
|
|
+ let response = null;
|
|
|
+ if (raw) {
|
|
|
+ try {
|
|
|
+ response = JSON.parse(raw);
|
|
|
+ } catch (parseErr) {
|
|
|
+ console.error('Resposta inválida ao excluir commodity:', parseErr, raw);
|
|
|
+ throw new Error('Resposta inválida do servidor ao excluir commodity.');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!res.ok || response?.status !== 'ok' || response?.code !== 'S_DELETED') {
|
|
|
+ throw new Error(response?.msg ?? 'Falha ao excluir commodity.');
|
|
|
+ }
|
|
|
+
|
|
|
+ const backendMsg = response?.msg;
|
|
|
+ createSuccess = backendMsg && backendMsg !== '[100] Request ok.' ? backendMsg : 'Commodity excluída com sucesso.';
|
|
|
+ await fetchCommodities();
|
|
|
+ showDeleteConfirm = false;
|
|
|
+ rowToDelete = null;
|
|
|
+ } catch (err) {
|
|
|
+ deleteError = err?.message ?? 'Falha ao excluir commodity.';
|
|
|
+ } finally {
|
|
|
+ deleteLoading = false;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
function cancelDelete() {
|
|
|
showDeleteConfirm = false;
|
|
|
rowToDelete = null;
|
|
|
+ deleteError = '';
|
|
|
+ deleteLoading = false;
|
|
|
}
|
|
|
|
|
|
- function handleCommoditySave(e) {
|
|
|
+ async 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
|
|
|
- );
|
|
|
+ if (!selectedRow?.id || !value) return;
|
|
|
+
|
|
|
+ updateError = '';
|
|
|
+ updateLoading = true;
|
|
|
+ try {
|
|
|
+ const body = {
|
|
|
+ commodities_id: selectedRow.id,
|
|
|
+ name: value.nome,
|
|
|
+ flag: value.flag
|
|
|
+ };
|
|
|
+
|
|
|
+ const token = $authToken;
|
|
|
+ const res = await fetch(`${apiUrl}/commodity/update`, {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ 'content-type': 'application/json',
|
|
|
+ ...(token ? { Authorization: `Bearer ${token}` } : {})
|
|
|
+ },
|
|
|
+ body: JSON.stringify(body)
|
|
|
+ });
|
|
|
+
|
|
|
+ const raw = await res.text();
|
|
|
+ let response = null;
|
|
|
+ if (raw) {
|
|
|
+ try {
|
|
|
+ response = JSON.parse(raw);
|
|
|
+ } catch (parseErr) {
|
|
|
+ console.error('Resposta inválida ao atualizar commodity:', parseErr, raw);
|
|
|
+ throw new Error('Resposta inválida do servidor ao atualizar commodity.');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!res.ok || response?.status !== 'ok' || response?.code !== 'S_UPDATED') {
|
|
|
+ throw new Error(response?.msg ?? 'Falha ao atualizar commodity.');
|
|
|
+ }
|
|
|
+
|
|
|
+ const backendMsg = response?.msg;
|
|
|
+ createSuccess = backendMsg && backendMsg !== '[100] Request ok.' ? backendMsg : 'Commodity atualizada com sucesso.';
|
|
|
+ showEdit = false;
|
|
|
+ selectedRow = null;
|
|
|
+ editValue = {};
|
|
|
+ await fetchCommodities();
|
|
|
+ } catch (err) {
|
|
|
+ updateError = err?.message ?? 'Falha ao atualizar commodity.';
|
|
|
+ } finally {
|
|
|
+ updateLoading = false;
|
|
|
}
|
|
|
- handleCommodityCancel();
|
|
|
}
|
|
|
|
|
|
function handleCommodityCancel() {
|
|
|
showEdit = false;
|
|
|
selectedRow = null;
|
|
|
editValue = {};
|
|
|
+ updateError = '';
|
|
|
+ updateLoading = false;
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
@@ -111,11 +284,23 @@
|
|
|
<Header title="Commodities" subtitle="Gestão de commodities" breadcrumb={breadcrumb} />
|
|
|
<div class="p-4">
|
|
|
<div class="max-w-6xl mx-auto mt-4">
|
|
|
+ {#if createError}
|
|
|
+ <div class="mb-4 rounded border border-red-300 bg-red-50 text-red-700 px-3 py-2 text-sm">{createError}</div>
|
|
|
+ {/if}
|
|
|
+ {#if createSuccess}
|
|
|
+ <div class="mb-4 rounded border border-green-300 bg-green-50 text-green-700 px-3 py-2 text-sm">{createSuccess}</div>
|
|
|
+ {/if}
|
|
|
{#if activeModal === 0}
|
|
|
+ {#if error && serverResponse}
|
|
|
+ <div class="mb-4 rounded border border-red-300 bg-red-50 text-red-700 px-3 py-2 text-sm">
|
|
|
+ <pre class="text-xs bg-white/80 dark:bg-gray-900/30 text-gray-800 dark:text-gray-100 rounded p-2 overflow-auto max-h-40">{serverResponse}</pre>
|
|
|
+ </div>
|
|
|
+ {/if}
|
|
|
<Tables
|
|
|
title="Produtos"
|
|
|
columns={columns}
|
|
|
data={data}
|
|
|
+ loading={loading}
|
|
|
on:addTop={handleAddTop}
|
|
|
on:editRow={handleEditRow}
|
|
|
on:deleteRow={handleDeleteRow}
|
|
|
@@ -134,12 +319,14 @@
|
|
|
visible={showEdit}
|
|
|
title="Editar Commodity"
|
|
|
value={editValue}
|
|
|
+ loading={updateLoading}
|
|
|
+ error={updateError}
|
|
|
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} />
|
|
|
+ <RegisterCommodity on:submit={handleRegisterSubmit} loading={createLoading} resetToken={registerResetToken} />
|
|
|
{/if}
|
|
|
</div>
|
|
|
</div>
|