+ {tip === 'dashboard' && (
+ <>
+
+ >
+ )}
+
+ {tip === 'radni-nalog' && (
+ <>
+
+
+ >
+ )}
+
+ {tip === 'stroj' && (
+ <>
+
+
+ >
+ )}
+
+ {tip === 'vlasnik' && (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ OIB
+ {podaci.oib}
+
+
+ Adresa
+ {podaci.adresa}, {podaci.grad}
+
+
+ Email
+ {podaci.email}
+
+
+
+ >
+ )}
+
+
+
+
+
+
+
+ {tip === 'radni-nalog' && (
+ <>
+
+ Prioritet
+ Visok
+
+
+ Izradio
+ Admin Sustava
+
+
+ Ažurirano
+ {formatDate(podaci.datum_azuriranja)}
+
+ >
+ )}
+
+ {tip === 'stroj' && (
+ <>
+
+ Zadnji Atest
+ {formatDate(podaci.datum_zadnjeg_atesta)}
+
+
+ Godina Proizv.
+ {podaci.godina_proizvodnje}
+
+
+ Sustav ID
+ #{podaci.id}
+
+ >
+ )}
+
+
+
\ No newline at end of file
diff --git a/001.FRONTEND/src/components/Button.astro b/001.FRONTEND/src/components/Button.astro
new file mode 100644
index 0000000..f645397
--- /dev/null
+++ b/001.FRONTEND/src/components/Button.astro
@@ -0,0 +1,30 @@
+---
+// src/components/Button.astro
+interface Props {
+ type?: "button" | "submit";
+ variant?: "primary" | "danger" | "outline";
+ id?: string;
+ class?: string;
+}
+
+const { type = "button", variant = "primary", id, class: className } = Astro.props;
+
+const variants = {
+ primary: "bg-blue-600 hover:bg-blue-700 text-white shadow-blue-500/20",
+ danger: "bg-red-600 hover:bg-red-700 text-white shadow-red-500/20",
+ outline: "bg-transparent border-2 border-gray-200 text-gray-500 hover:border-blue-600 hover:text-blue-600 shadow-none"
+};
+---
+
+
\ No newline at end of file
diff --git a/001.FRONTEND/src/components/Gallery.astro b/001.FRONTEND/src/components/Gallery.astro
new file mode 100644
index 0000000..ec84d7f
--- /dev/null
+++ b/001.FRONTEND/src/components/Gallery.astro
@@ -0,0 +1,77 @@
+---
+// src/components/Gallery.astro
+import { Image } from 'astro:assets';
+import 'photoswipe/style.css';
+
+interface Props {
+ // Prilagođeno tvom serializeru: 'url' je zapravo 'slika' u JSON-u
+ images: {
+ slika: string;
+ opis?: string;
+ width?: number;
+ height?: number;
+ }[];
+ galleryId: string;
+}
+
+const { images = [], galleryId } = Astro.props;
+
+// Filtriramo važeće slike (pazimo na naziv polja 'slika' iz serializera)
+const validImages = images.filter(img => img && img.slika);
+---
+
+
+
+{validImages.length === 0 && (
+
+
+
Nema priložene foto dokumentacije.
+
+)}
+
+
\ No newline at end of file
diff --git a/001.FRONTEND/src/components/GenericKarticaItem.astro b/001.FRONTEND/src/components/GenericKarticaItem.astro
new file mode 100644
index 0000000..a73853e
--- /dev/null
+++ b/001.FRONTEND/src/components/GenericKarticaItem.astro
@@ -0,0 +1,114 @@
+---
+// src/components/GenericKarticaItem.astro
+import { getStatusColorClass } from "../utils/ui";
+
+interface Props {
+ href: string;
+ status: string;
+ statusBojaClass?: string;
+ ikona: string;
+ naslov: string;
+ subNaslov?: string;
+ metaTekst: string;
+ statusPrikaz: string;
+ bojaTeme?: "blue" | "indigo" | "emerald" | "red"; // Podrška za teme
+}
+
+const {
+ href,
+ status,
+ statusBojaClass,
+ ikona,
+ naslov,
+ subNaslov,
+ metaTekst,
+ statusPrikaz,
+ bojaTeme = "blue"
+} = Astro.props;
+
+// Mapiranje tema na Tailwind klase
+const themes = {
+ blue: {
+ hover: "hover:bg-blue-50/30 dark:hover:bg-blue-900/10",
+ text: "group-hover:text-blue-600 dark:group-hover:text-blue-400",
+ iconBg: "group-hover:bg-blue-600",
+ arrow: "group-hover:text-blue-600"
+ },
+ indigo: {
+ hover: "hover:bg-indigo-50/30 dark:hover:bg-indigo-900/10",
+ text: "group-hover:text-indigo-600 dark:group-hover:text-indigo-400",
+ iconBg: "group-hover:bg-indigo-600",
+ arrow: "group-hover:text-indigo-600"
+ },
+ emerald: {
+ hover: "hover:bg-emerald-50/30 dark:hover:bg-emerald-900/10",
+ text: "group-hover:text-emerald-600 dark:group-hover:text-emerald-400",
+ iconBg: "group-hover:bg-emerald-600",
+ arrow: "group-hover:text-emerald-400"
+ }
+};
+
+const activeTheme = themes[bojaTeme] || themes.blue;
+const resolvedStatusColor = statusBojaClass || getStatusColorClass(status);
+---
+
+
+
+
+
+
+
+
+
+
+
+ {naslov}
+
+ {subNaslov && (
+
+ {subNaslov}
+
+ )}
+
+
+
+ {metaTekst}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/001.FRONTEND/src/components/Kalendar/Kalendar.astro b/001.FRONTEND/src/components/Kalendar/Kalendar.astro
new file mode 100644
index 0000000..789f683
--- /dev/null
+++ b/001.FRONTEND/src/components/Kalendar/Kalendar.astro
@@ -0,0 +1,152 @@
+---
+// src/components/Kalendar.astro
+import { fetchCalendarEvents } from "../../lib/api";
+import { getStatusColorClass } from "../../utils/ui";
+
+// 1. Dohvat podataka s Django API-ja kroz tvoj centralizirani lib
+const apiEvents = await fetchCalendarEvents();
+
+// 2. Grupiranje događaja po datumu (YYYY-MM-DD)
+const eventsByDate = apiEvents.reduce((acc, event) => {
+ const dateKey = new Date(event.start).toISOString().split('T')[0];
+ if (!acc[dateKey]) acc[dateKey] = [];
+ acc[dateKey].push(event);
+ return acc;
+}, {});
+
+// Postavke za trenutni prikaz (Svibanj 2026)
+const daysInMonth = 31;
+const firstDayOffset = 5; // Petak
+const currentYearMonth = "2026-05";
+---
+
+
+
+
+
+
+
Svibanj 2026
+ Raspored servisa
+
+
+
+
+
+
+
Ned
Pon
Uto
Sri
Čet
Pet
Sub
+
+
+
+ {Array.from({ length: firstDayOffset }).map(() => (
+
+ ))}
+
+ {Array.from({ length: daysInMonth }).map((_, i) => {
+ const day = i + 1;
+ const dateKey = `${currentYearMonth}-${day.toString().padStart(2, '0')}`;
+ const dayEvents = eventsByDate[dateKey] || [];
+
+ return (
+
+
0 ? 'text-gray-900 dark:text-white' : 'text-gray-300 dark:text-gray-600'} group-hover:text-blue-600`}>
+ {day.toString().padStart(2, '0')}
+
+
+
+ {dayEvents.map(event => (
+
+ ))}
+
+
+ );
+ })}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/001.FRONTEND/src/components/ListaStrojeva.astro b/001.FRONTEND/src/components/ListaStrojeva.astro
new file mode 100644
index 0000000..4dc314b
--- /dev/null
+++ b/001.FRONTEND/src/components/ListaStrojeva.astro
@@ -0,0 +1,59 @@
+---
+// src/components/ListaStrojeva.astro
+import NaslovList from "./NaslovList.astro";
+import GenericKarticaItem from "./GenericKarticaItem.astro";
+import { getStatusColorClass, formatStatus } from "../utils/ui";
+
+interface Props {
+ strojevi: any[];
+ naslov?: string;
+ prikaziNaslov?: boolean;
+}
+
+const {
+ strojevi = [],
+ naslov = "Tehničke jedinice",
+ prikaziNaslov = true
+} = Astro.props;
+
+// Statistika za NaslovList (npr. koliko ih je aktivno ili na servisu ako imaš te podatke)
+const ukupnoStrojeva = strojevi.length;
+// Pretpostavljamo da strojevi imaju status, ako nemaju, možemo staviti fiksne labele
+const uRadu = strojevi.filter(s => s.status === 'u_radu').length;
+---
+
+
+
+ {prikaziNaslov && (
+
+ )}
+
+
+
+ {strojevi.length > 0 ? strojevi.map((s) => (
+
+ )) : (
+
+
+ Nema dostupnih tehničkih jedinica
+
+ )}
+
+
+
\ No newline at end of file
diff --git a/001.FRONTEND/src/components/LoginForm.astro b/001.FRONTEND/src/components/LoginForm.astro
new file mode 100644
index 0000000..518f8b9
--- /dev/null
+++ b/001.FRONTEND/src/components/LoginForm.astro
@@ -0,0 +1,103 @@
+---
+// src/components/LoginForm.astro
+import Button from "./Button.astro";
+---
+
+
+
+
+
+
+
Prijava
+
+ ServisLog / Terminal
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/001.FRONTEND/src/components/NaslovList.astro b/001.FRONTEND/src/components/NaslovList.astro
new file mode 100644
index 0000000..ce2e092
--- /dev/null
+++ b/001.FRONTEND/src/components/NaslovList.astro
@@ -0,0 +1,59 @@
+---
+// src/components/NaslovList.astro
+interface Props {
+ naslov: string;
+ ukupno: number;
+ label1: string;
+ count1: number;
+ filter1: string;
+ label2: string;
+ count2: number;
+ filter2: string;
+ label3?: string;
+ count3?: number;
+ filter3?: string;
+}
+
+const { naslov, ukupno, label1, count1, filter1, label2, count2, filter2, label3, count3, filter3 } = Astro.props;
+---
+
+
+
+
+ {naslov}
+
+
+
+
+
+
+ {label1}: {count1}
+
+
+
+
+
+
+ {label2}: {count2}
+
+
+
+ {label3 && count3 !== undefined && filter3 && (
+
+
+
+ {label3}: {count3}
+
+
+ )}
+
+
\ No newline at end of file
diff --git a/001.FRONTEND/src/components/Navbar.astro b/001.FRONTEND/src/components/Navbar.astro
new file mode 100644
index 0000000..d2007b1
--- /dev/null
+++ b/001.FRONTEND/src/components/Navbar.astro
@@ -0,0 +1,107 @@
+---
+// src/components/Navbar.astro
+import site from "../data/site.json";
+
+const currentPath = Astro.url.pathname;
+---
+
+
+
+
\ No newline at end of file
diff --git a/001.FRONTEND/src/components/RadniNalogLista.astro b/001.FRONTEND/src/components/RadniNalogLista.astro
new file mode 100644
index 0000000..29bd91a
--- /dev/null
+++ b/001.FRONTEND/src/components/RadniNalogLista.astro
@@ -0,0 +1,94 @@
+---
+// src/components/RadniNalogLista.astro
+import { fetchDashboardData } from "../lib/api";
+import { getStatusColorClass, formatStatus } from "../utils/ui";
+import NaslovList from "./NaslovList.astro";
+import GenericKarticaItem from "./GenericKarticaItem.astro";
+
+interface Props {
+ limit?: number;
+ naslov?: string;
+ prikaziNaslov?: boolean;
+}
+
+const {
+ limit = 0,
+ naslov = "Zadnje aktivnosti",
+ prikaziNaslov = true,
+} = Astro.props;
+
+// 1. Dohvat filtera iz URL-a (npr. ?status=u_radu)
+const statusFilter = Astro.url.searchParams.get('status');
+
+// 2. Dohvat podataka s API-ja
+const { nalozi = [] } = await fetchDashboardData();
+
+// 3. Logika filtriranja (Serverska strana)
+let filtriraniNalozi = nalozi;
+
+if (statusFilter) {
+ filtriraniNalozi = nalozi.filter(n => n.status === statusFilter);
+}
+
+// 4. Izračun brojeva za NaslovList (uvijek se računa iz originalne liste nalozi)
+const planiranoCount = nalozi.filter(n => n.status === 'planirano').length;
+const uRaduCount = nalozi.filter(n => n.status === 'u_radu').length;
+
+// 5. Primjena limita na prikaz (npr. top 5 nalozi)
+const prikazaniNalozi = limit > 0 ? filtriraniNalozi.slice(0, limit) : filtriraniNalozi;
+
+// Dinamički naslov ovisno o filteru
+const prikazaniNaslov = statusFilter
+ ? `Filtrirano: ${formatStatus(statusFilter)}`
+ : naslov;
+---
+
+
+ {prikaziNaslov && (
+
+ )}
+
+
+
+ {prikazaniNalozi.length > 0 ? (
+ prikazaniNalozi.map((n) => (
+
+ ))
+ ) : (
+
+ Nema zapisa za odabrani status
+
+ )}
+
+
+
+
+
\ No newline at end of file
diff --git a/001.FRONTEND/src/components/StatsGrid.astro b/001.FRONTEND/src/components/StatsGrid.astro
new file mode 100644
index 0000000..1e1a493
--- /dev/null
+++ b/001.FRONTEND/src/components/StatsGrid.astro
@@ -0,0 +1,48 @@
+---
+// src/components/StatsGrid.astro
+interface Props {
+ label: string;
+ value: string | number;
+ icon: string;
+ variant?: 'blue' | 'yellow' | 'emerald' | 'gray';
+ animate?: boolean;
+ filterValue?: string; // NOVO: npr. 'u_radu', 'planirano'...
+ class?: string;
+}
+
+const {
+ label,
+ value,
+ icon,
+ variant = 'blue',
+ animate = false,
+ filterValue = "",
+ class: className = ""
+} = Astro.props;
+
+const themes = {
+ blue: { container: "border-gray-100 dark:border-gray-700", iconBg: "bg-blue-50 dark:bg-blue-900/30", iconColor: "text-blue-600 dark:text-blue-400", labelColor: "text-gray-400" },
+ yellow: { container: "border-yellow-500/20 border-2", iconBg: "bg-yellow-500", iconColor: "text-white", labelColor: "text-yellow-600" },
+ emerald: { container: "border-gray-100 dark:border-gray-700", iconBg: "bg-emerald-50 dark:bg-emerald-900/30", iconColor: "text-emerald-600 dark:text-emerald-400", labelColor: "text-gray-400" },
+ gray: { container: "border-gray-100 dark:border-gray-700", iconBg: "bg-gray-100 dark:bg-gray-800", iconColor: "text-gray-500", labelColor: "text-gray-400" }
+};
+
+const theme = themes[variant];
+---
+
+
+
+
+
+
+
+
+ {value}
+
+
+ {label}
+
+
+
\ No newline at end of file
diff --git a/001.FRONTEND/src/components/Toast.astro b/001.FRONTEND/src/components/Toast.astro
new file mode 100644
index 0000000..8361e25
--- /dev/null
+++ b/001.FRONTEND/src/components/Toast.astro
@@ -0,0 +1,73 @@
+---
+// src/components/Toast.astro
+---
+
+
+
\ No newline at end of file
diff --git a/001.FRONTEND/src/components/WelcomeHeader.astro b/001.FRONTEND/src/components/WelcomeHeader.astro
new file mode 100644
index 0000000..8f3791c
--- /dev/null
+++ b/001.FRONTEND/src/components/WelcomeHeader.astro
@@ -0,0 +1,36 @@
+---
+// src/components/WelcomeHeader.astro
+import site from "../data/site.json";
+
+const currentPath = Astro.url.pathname.replace(/\/$/, "") || "/";
+const currentPageData = site.navigation.find(item => item.url === currentPath);
+
+console.log("Current Path:", currentPath);
+
+const {
+ welcomeHeaderTextH1 = "Dobrodošli",
+ welcomeHeaderTextH1dodatno = "",
+ welcomeHeaderPodnaslov = "Pregled sustava",
+ welcomeHeaderPovratniURL = "",
+ welcomeHeaderDisplay = true
+} = currentPageData || {};
+---
+
+{welcomeHeaderDisplay && (
+
+)}
\ No newline at end of file
diff --git a/001.FRONTEND/src/data/site.json b/001.FRONTEND/src/data/site.json
new file mode 100644
index 0000000..92462a3
--- /dev/null
+++ b/001.FRONTEND/src/data/site.json
@@ -0,0 +1,97 @@
+{
+ "title": "ServisLog",
+ "description": "Profesionalni sustav za upravljanje servisnim operacijama",
+ "url": "https://servislog.hr",
+ "author": "Servis tim",
+ "theme": {
+ "mode": "dark",
+ "color": "blue"
+ },
+ "navigation": [
+ {
+ "name": "Dashboard",
+ "url": "/",
+ "icon": "fa-chart-line",
+ "welcomeHeaderDisplay": true,
+ "welcomeHeaderTextH1": "Kontrolna",
+ "welcomeHeaderTextH1dodatno": " ploča",
+ "welcomeHeaderPodnaslov": "Pregled ključnih informacija u stvarnom vremenu",
+ "welcomeHeaderPovratniURL": ""
+ },
+ {
+ "name": "Radni nalozi",
+ "url": "/operativa/radni-nalozi",
+ "icon": "fa-file-signature",
+ "welcomeHeaderDisplay": true,
+ "welcomeHeaderTextH1": "Radni",
+ "welcomeHeaderTextH1dodatno": " nalozi",
+ "welcomeHeaderPodnaslov": "Pregled ključnih informacija u stvarnom vremenu",
+ "welcomeHeaderPovratniURL": "/"
+ },
+ {
+ "name": "Vozni Park",
+ "url": "/fleet/vozila",
+ "icon": "fa-truck-pickup",
+ "welcomeHeaderDisplay": true,
+ "welcomeHeaderTextH1": "Vozni",
+ "welcomeHeaderTextH1dodatno": " park",
+ "welcomeHeaderPodnaslov": "Sustavna evidencija i nadzor mobilnih resursa",
+ "welcomeHeaderPovratniURL": "/"
+ },
+ {
+ "name": "Registrirani strojevi",
+ "url": "/fleet/strojevi",
+ "icon": "fa-truck-pickup",
+ "welcomeHeaderDisplay": true,
+ "welcomeHeaderTextH1": "Vozni",
+ "welcomeHeaderTextH1dodatno": " park",
+ "welcomeHeaderPodnaslov": "Sustavna evidencija i nadzor mobilnih resursa",
+ "welcomeHeaderPovratniURL": "/"
+ },
+ {
+ "name": "Klijenti",
+ "url": "/kupci/svi",
+ "icon": "fa-address-book",
+ "welcomeHeaderDisplay": true,
+ "welcomeHeaderTextH1": "Pregled",
+ "welcomeHeaderTextH1dodatno": "klijenata",
+ "welcomeHeaderPodnaslov": "Upravljanje bazom korisnika i partnera",
+ "welcomeHeaderPovratniURL": "/"
+ },
+ {
+ "name": "Kalendar",
+ "url": "/kalendar-dogadaja",
+ "icon": "fa-calendar-days",
+ "welcomeHeaderDisplay": true,
+ "welcomeHeaderTextH1": "Kalendar",
+ "welcomeHeaderTextH1dodatno": "događaja",
+ "welcomeHeaderPodnaslov": "Pregled operacija u stvarnom vremenu",
+ "welcomeHeaderPovratniURL": "/"
+ },
+ {
+ "name": "Login",
+ "url": "/login",
+ "icon": "fa-calendar-days",
+ "welcomeHeaderDisplay": false,
+ "welcomeHeaderTextH1": "Prijava",
+ "welcomeHeaderTextH1dodatno": " korisnika",
+ "welcomeHeaderPodnaslov": "Pristup sustavu za ovlaštene korisnike",
+ "welcomeHeaderPovratniURL": ""
+ },
+ {
+ "name": "Novi Radni Nalog",
+ "url": "/operativa/radni-nalozi/novi",
+ "icon": "fa-calendar-days",
+ "welcomeHeaderDisplay": true,
+ "welcomeHeaderTextH1": "Novi",
+ "welcomeHeaderTextH1dodatno": " Radni nalog",
+ "welcomeHeaderPodnaslov": "Otvaranje novog servisnog ili radnog naloga u sustavu",
+ "welcomeHeaderPovratniURL": "/operativa/radni-nalozi"
+ }
+ ],
+ "api_endpoints": {
+ "base": "PUBLIC_API_URL",
+ "auth": "/token/",
+ "me": "/users/me/"
+ }
+}
\ No newline at end of file
diff --git a/001.FRONTEND/src/layouts/Layout.astro b/001.FRONTEND/src/layouts/Layout.astro
new file mode 100644
index 0000000..c795762
--- /dev/null
+++ b/001.FRONTEND/src/layouts/Layout.astro
@@ -0,0 +1,67 @@
+---
+// src/layouts/Layout.astro
+import Nav from "../components/Navbar.astro";
+import Toast from '../components/Toast.astro';
+import site from "../data/site.json";
+import "../styles/global.css";
+
+interface Props {
+ title?: string;
+ description?: string;
+}
+
+const {
+ title = site.title,
+ description = site.description
+} = Astro.props;
+---
+
+
+
+
+
+
+
+
+
+
+
{title} | {site.title}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/001.FRONTEND/src/pages/fleet/strojevi/[id].astro b/001.FRONTEND/src/pages/fleet/strojevi/[id].astro
new file mode 100644
index 0000000..56cd45b
--- /dev/null
+++ b/001.FRONTEND/src/pages/fleet/strojevi/[id].astro
@@ -0,0 +1,163 @@
+---
+// src/pages/fleet/strojevi/[id].astro
+import Layout from "../../../layouts/Layout.astro";
+import Button from "../../../components/Button.astro";
+import AkcijePanel from "../../../components/AkcijePanel.astro";
+
+// Importiranje utilitija
+const { id } = Astro.params;
+const API_BASE = import.meta.env.PUBLIC_API_URL;
+
+let stroj = null;
+
+try {
+ // Dohvaćamo točno određeni stroj
+ const res = await fetch(`${API_BASE}/fleet/strojevi/${id}/?t=${Date.now()}`);
+ if (res.ok) stroj = await res.json();
+} catch (e) {
+ console.error("Greška pri dohvatu detalja stroja:", e);
+}
+
+if (!stroj) return Astro.redirect("/404");
+
+// Logika za Atest status (npr. ako je unutar 30 dana ili prošao)
+const atestDate = stroj.datum_zadnjeg_atesta ? new Date(stroj.datum_zadnjeg_atesta) : null;
+const isAtestExpired = atestDate && atestDate < new Date();
+---
+
+
+
+
+
+
+
+
+
+ Godište
+ {stroj.godina_proizvodnje || 'N/A'}
+
+
+
+
+
Radni sati
+
+ {parseFloat(stroj.radni_sati).toLocaleString('hr-HR')}
+ H
+
+
+
+
+
+ Zadnji atest
+
+ {stroj.datum_zadnjeg_atesta ? new Date(stroj.datum_zadnjeg_atesta).toLocaleDateString('hr-HR') : 'Nema podataka'}
+
+
+
+
+
+ Proizvođač
+ {stroj.marka}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Model
+ {stroj.model_stroja}
+
+
+ Tip jedinice
+ {stroj.tip_human_readable}
+
+
+ Registracija
+ {stroj.registracija || 'NIJE REGISTRIRAN'}
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
Status održavanja
+
+ Ovaj stroj ({stroj.naziv}) je zadnji put atestiran {stroj.datum_zadnjeg_atesta}. Prema servisnom planu, toranjske dizalice zahtijevaju periodični pregled svakih 12 mjeseci. Trenutni radni sati ({stroj.radni_sati} h) sugeriraju potrebu za redovnim podmazivanjem sklopova.
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/001.FRONTEND/src/pages/fleet/strojevi/index.astro b/001.FRONTEND/src/pages/fleet/strojevi/index.astro
new file mode 100644
index 0000000..631c43b
--- /dev/null
+++ b/001.FRONTEND/src/pages/fleet/strojevi/index.astro
@@ -0,0 +1,101 @@
+---
+// src/pages/fleet/strojevi/index.astro
+import Layout from "../../../layouts/Layout.astro";
+import AkcijePanel from "../../../components/AkcijePanel.astro";
+import WelcomeHeader from "../../../components/WelcomeHeader.astro";
+import NaslovList from "../../../components/NaslovList.astro";
+import GenericKarticaItem from "../../../components/GenericKarticaItem.astro";
+
+// Importiranje utilitija
+import { fetchStrojeviData, fetchCurrentUser } from "../../../lib/api";
+import { getStatusColorClass, formatStatus } from "../../../utils/ui";
+
+// 1. DOHVAT FILTERA IZ URL-a
+const url = new URL(Astro.request.url);
+const vlasnikId = url.searchParams.get('vlasnik');
+
+// 2. DOHVAT PODATAKA (Paralelno)
+const [{ strojevi }, user] = await Promise.all([
+ fetchStrojeviData(vlasnikId),
+ fetchCurrentUser()
+]);
+
+// 3. LOGIKA ZA DINAMIČKE INFORMACIJE
+const imeKupca = strojevi.length > 0 && vlasnikId ? strojevi[0].vlasnik_naziv : null;
+const imeKorisnika = user?.first_name || "Serviser";
+const ulogaKorisnika = user?.is_serviser ? "Tehnička Služba" : "Logistika";
+
+let dinamickiPodnaslov = "Pregled kompletne strojne flote i dizalica";
+if (vlasnikId && imeKupca) {
+ dinamickiPodnaslov = `Prikaz strojeva dodijeljenih klijentu: ${imeKupca}`;
+}
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {vlasnikId && (
+
+ )}
+
+
+
+
+
+ {strojevi.length > 0 ? strojevi.map((s) => (
+
+ )) : (
+
+
+
Nema strojeva u bazi podataka
+
+ )}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/001.FRONTEND/src/pages/fleet/vozila/index.astro b/001.FRONTEND/src/pages/fleet/vozila/index.astro
new file mode 100644
index 0000000..d434eee
--- /dev/null
+++ b/001.FRONTEND/src/pages/fleet/vozila/index.astro
@@ -0,0 +1,95 @@
+---
+// src/pages/fleet/vozila/index.astro
+import Layout from "../../../layouts/Layout.astro";
+import Button from "../../../components/Button.astro";
+import AkcijePanel from "../../../components/AkcijePanel.astro";
+import WelcomeHeader from "../../../components/WelcomeHeader.astro"; // Importamo novi header
+import NaslovList from "../../../components/NaslovList.astro";
+import GenericKarticaItem from "../../../components/GenericKarticaItem.astro";
+
+// Importiranje utilitija
+import { fetchDashboardData, fetchCurrentUser } from "../../../lib/api";
+import { getStatusColorClass } from "../../../utils/ui";
+
+// 1. DOHVAT PODATAKA
+// Paralelno dohvaćamo podatke o vozilima i trenutnom korisniku za Header
+const [dashboardData, user] = await Promise.all([
+ fetchDashboardData(),
+ fetchCurrentUser()
+]);
+
+const vozila = dashboardData?.vozila || [];
+
+// 2. LOGIKA STATISTIKE
+const aktivnaVozila = vozila.filter(v => v.status === 'aktivan').length;
+const naServisu = vozila.filter(v => v.status === 'servis').length;
+const neaktivnaVozila = vozila.filter(v => v.status === 'neaktivan').length;
+
+// Podaci za WelcomeHeader
+const imeKorisnika = user?.first_name || "Serviser";
+// const ulogaKorisnika = user?.is_serviser ? "Terenski Tehničar" : "Logistika";
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {vozila.length > 0 ? vozila.map((v) => (
+
+ )) : (
+
+
+
Baza podataka je prazna
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/001.FRONTEND/src/pages/index.astro b/001.FRONTEND/src/pages/index.astro
new file mode 100644
index 0000000..ede5839
--- /dev/null
+++ b/001.FRONTEND/src/pages/index.astro
@@ -0,0 +1,90 @@
+---
+// src/pages/index.astro
+import Layout from "../layouts/Layout.astro";
+import WelcomeHeader from "../components/WelcomeHeader.astro";
+import AkcijePanel from "../components/AkcijePanel.astro";
+import RadniNalogLista from "../components/RadniNalogLista.astro";
+import StatsGrid from "../components/StatsGrid.astro";
+import Kalendar from "../components/Kalendar/Kalendar.astro";
+
+// API i Utils
+import { fetchDashboardData, fetchCurrentUser } from "../lib/api";
+
+// 1. Dohvat podataka (paralelno radi brzine)
+const [data, user] = await Promise.all([
+ fetchDashboardData(),
+ fetchCurrentUser()
+]);
+
+const nalozi = data?.nalozi || [];
+const imeKorisnika = user?.first_name || "Kolega";
+
+// 2. Kalkulacija statistike samo za StatsGrid (ostalo ide u RadniNalogLista)
+const naloziURadu = nalozi.filter(n => n.status === 'u_radu').length;
+const planiraniNalozi = nalozi.filter(n => n.status === 'planirano').length;
+const zavrseniNalozi = nalozi.filter(n => n.status === 'zavrseno' || n.status === 'naplaceno').length;
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/001.FRONTEND/src/pages/kalendar-dogadaja.astro b/001.FRONTEND/src/pages/kalendar-dogadaja.astro
new file mode 100644
index 0000000..16a0247
--- /dev/null
+++ b/001.FRONTEND/src/pages/kalendar-dogadaja.astro
@@ -0,0 +1,25 @@
+---
+// src/pages/kalendar-dogadaja.astro
+import Layout from "../layouts/Layout.astro";
+import WelcomeHeader from "../components/WelcomeHeader.astro";
+import Kalendar from "../components/Kalendar/Kalendar.astro";
+
+// API i Utils
+import { fetchCurrentUser } from "../lib/api";
+
+// 1. Dohvaćamo samo korisnika za pozdrav u headeru
+const user = await fetchCurrentUser();
+const imeKorisnika = user?.first_name || "Kolega";
+---
+
+
+
+
\ No newline at end of file
diff --git a/001.FRONTEND/src/pages/kupci/[id].astro b/001.FRONTEND/src/pages/kupci/[id].astro
new file mode 100644
index 0000000..f9f73cf
--- /dev/null
+++ b/001.FRONTEND/src/pages/kupci/[id].astro
@@ -0,0 +1,131 @@
+---
+// src/pages/kupci/[id].astro
+import Layout from "../../layouts/Layout.astro";
+import AkcijePanel from "../../components/AkcijePanel.astro";
+import ListaStrojeva from "../../components/ListaStrojeva.astro";
+import { fetchStrojeviData, fetchRadniNalozi, fetchKupacDetalji, routes } from "../../lib/api";
+
+const { id } = Astro.params;
+
+// 1. Paralelno dohvaćanje podataka (brže renderiranje)
+// Koristimo status 'u_radu' jer smo utvrdili da Django ne prepoznaje 'otvoreno'
+const [strojeviData, kupac, nalozi] = await Promise.all([
+ fetchStrojeviData(id),
+ fetchKupacDetalji(id),
+ fetchRadniNalozi({ klijent: id, status: 'u_radu' })
+]);
+
+const strojevi = strojeviData?.strojevi || [];
+
+// 2. Sigurnosni redirect ako kupac ne postoji
+if (!kupac) return Astro.redirect("/404");
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Napredna analitika voznog parka
+
Pristupite detaljnim izvještajima i povijesti strojeva.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Naplata i Ugovori
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/001.FRONTEND/src/pages/kupci/svi.astro b/001.FRONTEND/src/pages/kupci/svi.astro
new file mode 100644
index 0000000..a6d5e32
--- /dev/null
+++ b/001.FRONTEND/src/pages/kupci/svi.astro
@@ -0,0 +1,97 @@
+---
+// src/pages/kupci/svi.astro
+import Layout from "../../layouts/Layout.astro";
+import AkcijePanel from "../../components/AkcijePanel.astro";
+import WelcomeHeader from "../../components/WelcomeHeader.astro";
+
+// Importiranje nove funkcije
+import { fetchKupciData } from "../../lib/api";
+import { getStatusColorClass } from "../../utils/ui";
+
+// 1. DOHVAT PODATAKA (Koristimo novu funkciju)
+const { kupci } = await fetchKupciData();
+
+// 2. LOGIKA STATISTIKE
+const aktivniKupci = kupci.filter(k => k.aktivno === true).length;
+const brojPravnih = kupci.filter(k => k.tip === 'pravno').length;
+---
+
+
+
+
+
+
+
+
+
+
+
Popis klijenata
+
+ Aktivni: {aktivniKupci}
+ Tvrtke: {brojPravnih}
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/001.FRONTEND/src/pages/login.astro b/001.FRONTEND/src/pages/login.astro
new file mode 100644
index 0000000..c85329c
--- /dev/null
+++ b/001.FRONTEND/src/pages/login.astro
@@ -0,0 +1,18 @@
+---
+// src/pages/login.astro
+import Layout from "../layouts/Layout.astro";
+import LoginForm from "../components/LoginForm.astro";
+import WelcomeHeader from "../components/WelcomeHeader.astro";
+---
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/001.FRONTEND/src/pages/operativa/radni-nalozi/[id].astro b/001.FRONTEND/src/pages/operativa/radni-nalozi/[id].astro
new file mode 100644
index 0000000..98508ec
--- /dev/null
+++ b/001.FRONTEND/src/pages/operativa/radni-nalozi/[id].astro
@@ -0,0 +1,181 @@
+---
+// src/pages/operativa/radni-nalozi/[id].astro
+import Layout from "../../../layouts/Layout.astro";
+import AkcijePanel from "../../../components/AkcijePanel.astro";
+import Gallery from "../../../components/Gallery.astro";
+
+// Utiliti za vizualni prikaz
+import { getStatusColorClass } from "../../../utils/ui";
+
+const { id } = Astro.params;
+const API_BASE = import.meta.env.PUBLIC_API_URL;
+
+let nalog = null;
+
+try {
+ // Dohvaćamo detaljne podatke (RadniNalogDetaljiSerializer)
+ const res = await fetch(`${API_BASE}/operativa/radni-nalozi/${id}/?t=${Date.now()}`);
+ if (res.ok) nalog = await res.json();
+} catch (e) {
+ console.error("Greška pri dohvatu detalja naloga:", e);
+}
+
+if (!nalog) return Astro.redirect("/404");
+
+// Priprema datuma za prikaz
+const datumKreiranja = new Date(nalog.datum_kreiranja).toLocaleDateString('hr-HR', {
+ day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit'
+});
+---
+
+
+
+
+
+
+
+
+ Povratak na dashboard
+
+
+ Radni nalog #{nalog.broj_naloga}
+
+
+
+ Otvoreno: {datumKreiranja}h
+
+
|
+
+ {nalog.izvrsitelj_ime || 'Nedodijeljeno'}
+
+
+
+
+
+
+
+
+ {nalog.status_display}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ "{nalog.opis_kvara}"
+
+
+
+
+
+
+
+
+
+
{nalog.stroj.naziv}
+
+ SN: {nalog.stroj?serijski_broj}
+
+
+
Radni sati: {nalog.stroj.radni_sati} h
+
Lokacija: {nalog.stroj.lokacija || 'Teren'}
+
+
+
+
+
+
+
{nalog.klijent.naziv}
+
{nalog.klijent.grad} • OIB: {nalog.klijent.oib}
+
+
+
+
+
+
+ {nalog.vozilo ? (
+
+
+
+
{nalog.vozilo.naziv}
+ {nalog.vozilo.registracija}
+
+
+
Početni KM
+
+ {nalog.vozilo.trenutni_kilometri?.toLocaleString() || '0'} km
+
+
+
+ ) : (
+
+
+
Radni nalog bez zaduženog vozila
+
+ )}
+
+
+
+
+
+
+ {nalog.slike?.length || 0} SLIKA
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Zadnja aktivnost
+
+
+
+
+
+
Ažurirano
+
+ {new Date(nalog.datum_azuriranja).toLocaleDateString('hr-HR')} u {new Date(nalog.datum_azuriranja).toLocaleTimeString('hr-HR', {hour:'2-digit', minute:'2-digit'})}h
+
+
+
+
+
+
+
+
Sistemski ID: {nalog.id}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/001.FRONTEND/src/pages/operativa/radni-nalozi/index.astro b/001.FRONTEND/src/pages/operativa/radni-nalozi/index.astro
new file mode 100644
index 0000000..1e0331a
--- /dev/null
+++ b/001.FRONTEND/src/pages/operativa/radni-nalozi/index.astro
@@ -0,0 +1,66 @@
+---
+// src/pages/operativa/radni-nalozi/index.astro
+import Layout from "../../../layouts/Layout.astro";
+import Button from "../../../components/Button.astro";
+import AkcijePanel from "../../../components/AkcijePanel.astro";
+import RadniNalogLista from "../../../components/RadniNalogLista.astro";
+import WelcomeHeader from "../../../components/WelcomeHeader.astro";
+import NaslovList from "../../../components/NaslovList.astro";
+
+// 1. DOHVAT PODATAKA
+import { fetchRadniNalozi, fetchCurrentUser } from "../../../lib/api";
+import { formatStatus, getStatusColorClass } from "../../../utils/ui";
+import site from "../../../data/site.json";
+
+// Paralelno dohvaćamo naloge i podatke o trenutnom korisniku
+const klijentId = Astro.url.searchParams.get('klijent');
+const statusParam = Astro.url.searchParams.get('status');
+
+const [nalozi, user] = await Promise.all([
+ fetchRadniNalozi({ klijent: klijentId, status: statusParam }),
+ fetchCurrentUser()
+]);
+
+// 2. LOGIKA ZA HEADER I BROJAČE
+const uRadu = nalozi.filter(n => n.status === 'u_radu').length;
+const planirano = nalozi.filter(n => n.status === 'planirano').length;
+const siteWelcomeHeader = site.navigation.find(({name}) => name === "Kalendar");
+
+// Podaci za WelcomeHeader
+const imeKorisnika = user?.first_name || "Serviser";
+---
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/001.FRONTEND/src/pages/operativa/radni-nalozi/novi.astro b/001.FRONTEND/src/pages/operativa/radni-nalozi/novi.astro
new file mode 100644
index 0000000..0214057
--- /dev/null
+++ b/001.FRONTEND/src/pages/operativa/radni-nalozi/novi.astro
@@ -0,0 +1,213 @@
+---
+// src/pages/operativa/radni-nalozi/novi.astro
+import Layout from "../../../layouts/Layout.astro";
+import Button from "../../../components/Button.astro";
+import { fetchKupciData, fetchStrojeviData } from "../../../lib/api";
+import WelcomeHeader from "../../../components/WelcomeHeader.astro";
+
+// 1. DOHVAT PODATAKA NA SERVERU
+const { kupci } = await fetchKupciData();
+const { strojevi } = await fetchStrojeviData();
+---
+
+
+
+
+
+
+
+
+
+
+
+ AUTO-GENERIRANO
+
+
* Broj će biti dodijeljen nakon spremanja
+
+
+
+
+
+
+ {new Date().toLocaleDateString('hr-HR')}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/001.FRONTEND/src/scripts/filters.js b/001.FRONTEND/src/scripts/filters.js
new file mode 100644
index 0000000..dfef65f
--- /dev/null
+++ b/001.FRONTEND/src/scripts/filters.js
@@ -0,0 +1,70 @@
+// // src/scripts/filters.js
+
+// Upravlja sa item.style.display na osnovu aktivnog filtera i data-status atributa
+// Također upravlja sa stilovima kartica (ring, border) i naslovom filtera
+// export function initFilters() {
+// const cards = document.querySelectorAll('.stat-card');
+// const items = document.querySelectorAll('.nalog-item');
+// const title = document.getElementById('filter-title');
+// let activeFilter = null;
+
+// cards.forEach(card => {
+// card.addEventListener('click', () => {
+// const filter = card.getAttribute('data-filter');
+
+// if (activeFilter === filter) {
+// activeFilter = null;
+// items.forEach(item => item.style.display = 'flex');
+// cards.forEach(c => c.classList.remove('ring-4', 'ring-blue-600/20', 'border-blue-600'));
+// if (title) title.innerText = "Zadnje aktivnosti";
+// } else {
+// activeFilter = filter;
+// cards.forEach(c => c.classList.remove('ring-4', 'ring-blue-600/20', 'border-blue-600'));
+// card.classList.add('ring-4', 'ring-blue-600/20', 'border-blue-600');
+
+// if (title) title.innerText = `Filtrirano: ${filter.replace('_', ' ')}`;
+
+// items.forEach(item => {
+// const status = item.getAttribute('data-status');
+// item.style.display = (status === filter) ? 'flex' : 'none';
+// });
+// }
+// });
+// });
+// }
+
+
+
+// src/scripts/filters.js
+
+export function initFilters() {
+ const cards = document.querySelectorAll('.stat-card');
+
+ // Dohvaćamo trenutni aktivni filter iz URL-a
+ const urlParams = new URLSearchParams(window.location.search);
+ const currentActiveFilter = urlParams.get('status');
+
+ cards.forEach(card => {
+ const filterValue = card.getAttribute('data-filter');
+
+ // Dodaj vizualno aktivno stanje ako se URL podudara s gumbom
+ if (currentActiveFilter === filterValue) {
+ card.classList.add('ring-4', 'ring-blue-600/20', 'border-blue-600');
+ }
+
+ card.addEventListener('click', () => {
+ const newParams = new URLSearchParams(window.location.search);
+
+ if (currentActiveFilter === filterValue) {
+ // Ako kliknemo na već aktivni filter, poništavamo ga
+ newParams.delete('status');
+ } else {
+ // Inače postavljamo novi filter
+ newParams.set('status', filterValue);
+ }
+
+ // Osvježi stranicu s novim parametrom - to okida novi API request na serveru
+ window.location.search = newParams.toString();
+ });
+ });
+}
\ No newline at end of file
diff --git a/001.FRONTEND/src/styles/global.css b/001.FRONTEND/src/styles/global.css
new file mode 100644
index 0000000..a461c50
--- /dev/null
+++ b/001.FRONTEND/src/styles/global.css
@@ -0,0 +1 @@
+@import "tailwindcss";
\ No newline at end of file
diff --git a/001.FRONTEND/src/utils/ui.js b/001.FRONTEND/src/utils/ui.js
new file mode 100644
index 0000000..ef72139
--- /dev/null
+++ b/001.FRONTEND/src/utils/ui.js
@@ -0,0 +1,111 @@
+// src/utils/ui.js
+
+/**
+ * Vraća set Tailwind klasa za specifičnu temu
+ * @param {string} tema - 'indigo', 'blue', 'emerald', 'amber', 'red'
+ */
+export function getThemeClasses(tema = "indigo") {
+ const themes = {
+ indigo: {
+ bg: "bg-indigo-50",
+ bgDark: "dark:bg-indigo-900/20",
+ text: "text-indigo-600",
+ textDark: "dark:text-indigo-400",
+ border: "border-indigo-100",
+ borderDark: "dark:border-indigo-900/30",
+ shadow: "shadow-indigo-500/5",
+ hover: "hover:bg-indigo-50/30 dark:hover:bg-indigo-900/10",
+ icon: "group-hover:bg-indigo-600 group-hover:text-white"
+ },
+ blue: {
+ bg: "bg-blue-50",
+ bgDark: "dark:bg-blue-900/20",
+ text: "text-blue-600",
+ textDark: "dark:text-blue-400",
+ border: "border-blue-100",
+ borderDark: "dark:border-blue-900/30",
+ shadow: "shadow-blue-500/5",
+ hover: "hover:bg-blue-50/30 dark:hover:bg-blue-900/10",
+ icon: "group-hover:bg-blue-600 group-hover:text-white"
+ },
+ emerald: {
+ bg: "bg-emerald-50",
+ bgDark: "dark:bg-emerald-900/20",
+ text: "text-emerald-600",
+ textDark: "dark:text-emerald-400",
+ border: "border-emerald-100",
+ borderDark: "dark:border-emerald-900/30",
+ shadow: "shadow-emerald-500/5",
+ hover: "hover:bg-emerald-50/30 dark:hover:bg-emerald-900/10",
+ icon: "group-hover:bg-emerald-600 group-hover:text-white"
+ },
+ amber: {
+ bg: "bg-amber-50",
+ bgDark: "dark:bg-amber-900/20",
+ text: "text-amber-600",
+ textDark: "dark:text-amber-400",
+ border: "border-amber-100",
+ borderDark: "dark:border-amber-900/30",
+ shadow: "shadow-amber-500/5",
+ hover: "hover:bg-amber-50/30 dark:hover:bg-amber-900/10",
+ icon: "group-hover:bg-amber-600 group-hover:text-white"
+ },
+ red: {
+ bg: "bg-red-50",
+ bgDark: "dark:bg-red-900/20",
+ text: "text-red-600",
+ textDark: "dark:text-red-400",
+ border: "border-red-100",
+ borderDark: "dark:border-red-900/30",
+ shadow: "shadow-red-500/5",
+ hover: "hover:bg-red-50/30 dark:hover:bg-red-900/10",
+ icon: "group-hover:bg-red-600 group-hover:text-white"
+ }
+ };
+
+ return themes[tema] || themes.indigo;
+}
+
+/**
+ * Određuje boju kružića i animaciju na temelju statusa (Vozila + Radni Nalozi)
+ */
+export function getStatusColorClass(status) {
+ // 1. Provjera postoji li status. Ako ne, vrati sivu boju.
+ if (!status) return 'bg-gray-400';
+
+ // 2. Siguran poziv toLowerCase()
+ const s = String(status).toLowerCase();
+
+ switch (s) {
+ case 'u_radu': return 'bg-yellow-500 animate-pulse';
+ case 'planirano':
+ case 'aktivan': return 'bg-blue-500';
+ case 'zavrseno': return 'bg-emerald-500';
+ case 'naplaceno': return 'bg-gray-500 opacity-50';
+ case 'servis':
+ case 'hitno': return 'bg-red-500 animate-pulse';
+ default: return 'bg-gray-400';
+ }
+}
+
+/**
+ * Pretvara tehnički status u ljudima čitljiv format
+ * Dodana provjera da spriječi toLowerCase error
+ */
+export function formatStatus(status) {
+ // Provjera ako je status prazan
+ if (!status) return 'Nepoznato';
+
+ const labels = {
+ 'planirano': 'Planirano',
+ 'u_radu': 'U radu',
+ 'zavrseno': 'Završeno',
+ 'naplaceno': 'Naplaćeno',
+ 'aktivan': 'Aktivan',
+ 'servis': 'Na servisu',
+ 'hitno': 'HITNO'
+ };
+
+ const s = String(status).toLowerCase();
+ return labels[s] || s;
+}
\ No newline at end of file
diff --git a/001.FRONTEND/tailwind.config.mjs b/001.FRONTEND/tailwind.config.mjs
new file mode 100644
index 0000000..8f1fae7
--- /dev/null
+++ b/001.FRONTEND/tailwind.config.mjs
@@ -0,0 +1,13 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: [
+ './src/**/*.{astro,html,js,jsx,md,mdx,ts,tsx}',
+ './node_modules/flowbite/**/*.js' // Ovo omogućuje Flowbite stilove
+ ],
+ theme: {
+ extend: {},
+ },
+ plugins: [
+ require('flowbite/plugin') // Registrira Flowbite kao plugin
+ ],
+}
\ No newline at end of file
diff --git a/001.FRONTEND/tsconfig.json b/001.FRONTEND/tsconfig.json
new file mode 100644
index 0000000..8bf91d3
--- /dev/null
+++ b/001.FRONTEND/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "extends": "astro/tsconfigs/strict",
+ "include": [".astro/types.d.ts", "**/*"],
+ "exclude": ["dist"]
+}