|
|
@@ -1,5 +1,6 @@
|
|
|
<script>
|
|
|
import { browser } from '$app/environment';
|
|
|
+ import { goto } from '$app/navigation';
|
|
|
import { onDestroy, onMount, tick } from 'svelte';
|
|
|
import { get } from 'svelte/store';
|
|
|
import Header from '$lib/layout/Header.svelte';
|
|
|
@@ -8,7 +9,7 @@
|
|
|
import ContractCpr from '$lib/components/commodities/cpr/ContractCpr.svelte';
|
|
|
import EmissionCpr from '$lib/components/commodities/cpr/EmissionCpr.svelte';
|
|
|
import CprDetailModal from '$lib/components/commodities/cpr/CprDetailModal.svelte';
|
|
|
- import { authToken, companyId as companyIdStore } from '$lib/utils/stores';
|
|
|
+ import { authToken } from '$lib/utils/stores';
|
|
|
|
|
|
const apiUrl = import.meta.env.VITE_API_URL;
|
|
|
|
|
|
@@ -296,6 +297,9 @@
|
|
|
const breadcrumb = [{ label: 'Início' }, { label: 'CPR', active: true }];
|
|
|
let activeTab = 4;
|
|
|
const tabs = ['Contrato', 'Registro', 'Emissão'];
|
|
|
+ const B3_OFFLINE_START_HOUR = 20;
|
|
|
+ const B3_OFFLINE_END_HOUR = 8;
|
|
|
+ const B3_OFFLINE_POLL_INTERVAL_MS = 60_000;
|
|
|
|
|
|
const historyEndpoint = `${apiUrl}/cpr/history`;
|
|
|
const paymentConfirmEndpoint = `${apiUrl}/b3/payment/confirm`;
|
|
|
@@ -315,6 +319,8 @@
|
|
|
let selectedDetailId = null;
|
|
|
let showDetailModal = false;
|
|
|
let historyInitialized = false;
|
|
|
+ let isB3OfflineWindow = false;
|
|
|
+ let b3OfflineIntervalId = null;
|
|
|
|
|
|
function handleFieldChange(key, value) {
|
|
|
submitError = '';
|
|
|
@@ -380,11 +386,7 @@
|
|
|
if (!token) {
|
|
|
throw new Error('Sessão expirada. Faça login novamente.');
|
|
|
}
|
|
|
- const company = Number(get(companyIdStore));
|
|
|
- if (!company || Number.isNaN(company) || company <= 0) {
|
|
|
- throw new Error('company_id inválido. Refaça o login.');
|
|
|
- }
|
|
|
- return { token, company_id: company };
|
|
|
+ return { token };
|
|
|
}
|
|
|
|
|
|
async function parseJsonResponse(res) {
|
|
|
@@ -569,14 +571,14 @@
|
|
|
if (!paymentId) {
|
|
|
return { state: 'error', message: 'Pagamento inválido. Gere um novo QR Code.' };
|
|
|
}
|
|
|
- const { token, company_id } = ensureAuthContext();
|
|
|
+ const { token } = ensureAuthContext();
|
|
|
const res = await fetch(paymentConfirmEndpoint, {
|
|
|
method: 'POST',
|
|
|
headers: {
|
|
|
'content-type': 'application/json',
|
|
|
Authorization: `Bearer ${token}`
|
|
|
},
|
|
|
- body: JSON.stringify({ payment_id: paymentId, company_id })
|
|
|
+ body: JSON.stringify({ payment_id: paymentId })
|
|
|
});
|
|
|
const raw = await res.text();
|
|
|
let body = null;
|
|
|
@@ -771,14 +773,14 @@
|
|
|
detailError = '';
|
|
|
detailLoading = false;
|
|
|
try {
|
|
|
- const { token, company_id } = ensureAuthContext();
|
|
|
+ const { token } = ensureAuthContext();
|
|
|
const res = await fetch(historyEndpoint, {
|
|
|
method: 'POST',
|
|
|
headers: {
|
|
|
'content-type': 'application/json',
|
|
|
Authorization: `Bearer ${token}`
|
|
|
},
|
|
|
- body: JSON.stringify({ company_id })
|
|
|
+ body: JSON.stringify({})
|
|
|
});
|
|
|
const body = await parseJsonResponse(res);
|
|
|
if (!res.ok) {
|
|
|
@@ -801,14 +803,14 @@
|
|
|
detailLoading = true;
|
|
|
detailError = '';
|
|
|
try {
|
|
|
- const { token, company_id } = ensureAuthContext();
|
|
|
+ const { token } = ensureAuthContext();
|
|
|
const res = await fetch(historyEndpoint, {
|
|
|
method: 'POST',
|
|
|
headers: {
|
|
|
'content-type': 'application/json',
|
|
|
Authorization: `Bearer ${token}`
|
|
|
},
|
|
|
- body: JSON.stringify({ company_id, cpr_id: cprId })
|
|
|
+ body: JSON.stringify({ cpr_id: cprId })
|
|
|
});
|
|
|
const body = await parseJsonResponse(res);
|
|
|
if (!res.ok) {
|
|
|
@@ -852,12 +854,20 @@
|
|
|
onMount(() => {
|
|
|
void fetchHistory();
|
|
|
void restorePersistedPayment();
|
|
|
+ updateB3OfflineState();
|
|
|
+ if (browser) {
|
|
|
+ b3OfflineIntervalId = window.setInterval(updateB3OfflineState, B3_OFFLINE_POLL_INTERVAL_MS);
|
|
|
+ }
|
|
|
});
|
|
|
|
|
|
onDestroy(() => {
|
|
|
stopPaymentPolling();
|
|
|
stopPaymentCountdown();
|
|
|
stopPaymentCopyTimeout();
|
|
|
+ if (b3OfflineIntervalId) {
|
|
|
+ clearInterval(b3OfflineIntervalId);
|
|
|
+ b3OfflineIntervalId = null;
|
|
|
+ }
|
|
|
});
|
|
|
|
|
|
function getMissingRequiredFields() {
|
|
|
@@ -984,8 +994,46 @@
|
|
|
paymentLoadingVisible = false;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ function isWithinB3OfflineWindow(date = new Date()) {
|
|
|
+ const hour = date.getHours();
|
|
|
+ return hour >= B3_OFFLINE_START_HOUR || hour < B3_OFFLINE_END_HOUR;
|
|
|
+ }
|
|
|
+
|
|
|
+ function updateB3OfflineState() {
|
|
|
+ if (!browser) {
|
|
|
+ isB3OfflineWindow = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ isB3OfflineWindow = isWithinB3OfflineWindow(new Date());
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleB3OfflineRedirect() {
|
|
|
+ goto('/dashboard');
|
|
|
+ }
|
|
|
</script>
|
|
|
|
|
|
+{#if isB3OfflineWindow}
|
|
|
+ <div class="fixed inset-0 z-50 flex items-center justify-center px-4 bg-black/70 backdrop-blur-sm">
|
|
|
+ <div class="w-full max-w-xl rounded-2xl bg-white shadow-2xl border border-red-200 dark:bg-gray-900 dark:border-red-800/40 overflow-hidden">
|
|
|
+ <div class="px-6 py-5 space-y-4 text-center">
|
|
|
+ <p class="text-sm uppercase tracking-[0.3em] text-red-500 font-semibold">Manutenção programada</p>
|
|
|
+ <h2 class="text-2xl font-bold text-gray-900 dark:text-gray-100">B3 indisponível entre 20h e 8h</h2>
|
|
|
+ <p class="text-base text-gray-600 dark:text-gray-300">
|
|
|
+ A emissão de novas CPRs fica temporariamente suspensa enquanto a B3 está offline. Retorne ao dashboard e tente novamente após as 08:00.
|
|
|
+ </p>
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ class="mt-2 inline-flex items-center justify-center rounded-lg bg-gray-900 text-white font-semibold px-6 py-3 hover:bg-gray-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-900 dark:bg-white dark:text-gray-900"
|
|
|
+ on:click={handleB3OfflineRedirect}
|
|
|
+ >
|
|
|
+ Voltar para o dashboard
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+{/if}
|
|
|
+
|
|
|
<div>
|
|
|
<Header title="CPR - Cédula de Produto Rural" subtitle="Gestão de contratos, emissão e registro de CPRs" breadcrumb={breadcrumb} />
|
|
|
<div class="p-4">
|