diff --git a/backend/core/urls.py b/backend/core/urls.py index 0fd2475..6ca0f64 100644 --- a/backend/core/urls.py +++ b/backend/core/urls.py @@ -2,14 +2,19 @@ from django.conf import settings from django.conf.urls.static import static from django.contrib import admin +from django.http import JsonResponse from django.urls import path, include from rest_framework_simplejwt.views import ( TokenObtainPairView, TokenRefreshView, ) +def api_health(_request): + return JsonResponse({'status': 'ok'}) + urlpatterns = [ path('admin/', admin.site.urls), + path('api/health/', api_health, name='api_health'), # Uključujemo rute iz modula (svaki modul ima svoj urls.py) path('api/crm/', include('modules.crm.urls')), diff --git a/frontend/src/stores/clientStore.js b/frontend/src/stores/clientStore.js index cbe03e7..523c929 100644 --- a/frontend/src/stores/clientStore.js +++ b/frontend/src/stores/clientStore.js @@ -8,12 +8,25 @@ export const $clients = atom([]); export const $clientsLoading = atom(false); export const $clientsError = atom(null); -export async function fetchClients(signal) { +let _lastClientsFetchAt = 0; +const CLIENTS_FETCH_TTL_MS = 30_000; // 30 sekundi + +export async function fetchClients(arg) { + const options = (arg && typeof arg === 'object' && !('aborted' in arg)) + ? arg + : { signal: arg }; + const { signal, force = false } = options; + + if (!force && $clients.get().length > 0 && Date.now() - _lastClientsFetchAt < CLIENTS_FETCH_TTL_MS) { + return; + } + $clientsLoading.set(true); $clientsError.set(null); try { const data = await api.get('crm/clients/', { signal }); $clients.set(Array.isArray(data) ? data : (data.results ?? [])); + _lastClientsFetchAt = Date.now(); } catch (err) { if (err?.name !== 'AbortError') $clientsError.set(err.message ?? 'Greška'); } finally { @@ -24,16 +37,19 @@ export async function fetchClients(signal) { export async function createClient(payload) { const data = await api.post('crm/clients/', payload); $clients.set([...$clients.get(), data]); + _lastClientsFetchAt = Date.now(); return data; } export async function updateClient(id, payload) { const data = await api.patch(`crm/clients/${id}/`, payload); $clients.set($clients.get().map((c) => (c.id === id ? data : c))); + _lastClientsFetchAt = Date.now(); return data; } export async function deleteClient(id) { await api.delete(`crm/clients/${id}/`); $clients.set($clients.get().filter((c) => c.id !== id)); + _lastClientsFetchAt = Date.now(); } diff --git a/frontend/src/stores/networkStore.js b/frontend/src/stores/networkStore.js index 5194772..bd27c91 100644 --- a/frontend/src/stores/networkStore.js +++ b/frontend/src/stores/networkStore.js @@ -4,14 +4,14 @@ export const $isOffline = atom(false); const API_BASE = import.meta.env.PUBLIC_API_URL || 'http://localhost:8001/api/'; -// Heartbeat endpoint: OPTIONS /api/token/ -// DRF odgovara bez 405 greške i dovoljno je za provjeru dostupnosti backenda. -const HEARTBEAT_URL = new URL('token/', API_BASE).toString(); +// Heartbeat endpoint: GET /api/health/ +// Time izbjegavamo periodično gađanje auth/token endpointa. +const HEARTBEAT_URL = new URL('health/', API_BASE).toString(); async function provjeriStvarnuVezu() { try { const response = await fetch(HEARTBEAT_URL, { - method: 'OPTIONS', + method: 'GET', cache: 'no-store', });