prvi kod za live uporabu
Some checks failed
ERP CI Pipeline / test (push) Has been cancelled

This commit is contained in:
mariomitte
2026-07-09 21:21:17 +02:00
parent 857bb65d52
commit 5d39e048ba
239 changed files with 25151 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
import { useEffect } from 'preact/hooks';
import { useSignal } from '@preact/signals';
import { fetchJson } from '../../lib/api';
import { getCachedData, setCachedData } from '../../lib/db';
import { useToast } from '../../hooks/useToast';
type LoadState = 'idle' | 'loading' | 'success' | 'error';
export interface DataViewProps {
endpoint: string;
cacheKey: string;
initialData: unknown[] | null;
initialError?: string | null;
}
export default function DataView({
endpoint,
cacheKey,
initialData,
initialError = null,
}: DataViewProps) {
const toast = useToast();
const itemsSignal = useSignal<unknown[]>([]);
const stateSignal = useSignal<LoadState>('idle');
const sourceSignal = useSignal<'ssr' | 'network' | 'cache' | null>(null);
const errorSignal = useSignal<string | null>(null);
useEffect(() => {
if (Array.isArray(initialData) && initialData.length) {
itemsSignal.value = initialData;
stateSignal.value = 'success';
sourceSignal.value = 'ssr';
} else if (initialError) {
errorSignal.value = initialError;
stateSignal.value = 'error';
} else {
stateSignal.value = 'loading';
}
const controller = new AbortController();
// SSR gives first paint; hydration then refreshes client data and persists it offline.
async function refreshData() {
try {
stateSignal.value = 'loading';
const payload = await fetchJson<unknown[]>(endpoint, {
method: 'GET',
signal: controller.signal,
timeoutMs: 12000,
});
itemsSignal.value = Array.isArray(payload) ? payload : [];
await setCachedData(cacheKey, itemsSignal.value);
sourceSignal.value = 'network';
errorSignal.value = null;
stateSignal.value = 'success';
toast.success('Podaci su uspješno osvježeni.');
} catch (error) {
if (controller.signal.aborted) {
return;
}
const cached = await getCachedData<unknown[]>(cacheKey);
if (cached?.payload) {
itemsSignal.value = Array.isArray(cached.payload) ? cached.payload : [];
sourceSignal.value = 'cache';
errorSignal.value = null;
stateSignal.value = 'success';
toast.warning('Prikazani su lokalno spremljeni podaci (offline fallback).');
return;
}
errorSignal.value = error instanceof Error ? error.message : 'Data loading failed.';
stateSignal.value = 'error';
toast.error('Neuspješno učitavanje podataka i nema lokalnog cachea.');
}
}
void refreshData();
return () => controller.abort();
}, [endpoint, cacheKey, initialData, initialError]);
return (
<section className="rounded-xl border border-border-hairline bg-canvas-elevated p-5 shadow-sm">
<header className="mb-4 flex items-center justify-between">
<h2 className="text-lg font-semibold text-text-main">DataView</h2>
<span className="text-xs text-text-muted">
Source: {sourceSignal.value || 'unknown'} State: {stateSignal.value}
</span>
</header>
{stateSignal.value === 'loading' && (
<p className="text-sm text-text-muted">Učitavanje podataka...</p>
)}
{stateSignal.value === 'error' && (
<p className="rounded-lg border border-red-300 bg-red-50 px-3 py-2 text-sm text-red-700">
{errorSignal.value || 'Greška pri učitavanju.'}
</p>
)}
{stateSignal.value === 'success' && (
<div className="space-y-2">
<p className="text-sm text-text-muted">
Ukupno zapisa: <strong className="text-text-main">{itemsSignal.value.length}</strong>
</p>
<pre className="max-h-96 overflow-auto rounded-lg border border-border-hairline bg-canvas-base p-3 text-xs text-text-main">
{JSON.stringify(itemsSignal.value.slice(0, 20), null, 2)}
</pre>
</div>
)}
</section>
);
}