CORS error cleaned

This commit is contained in:
2026-05-22 15:50:35 +00:00
parent bafec754d1
commit 96c7ba3ec1
11 changed files with 372 additions and 241 deletions

View File

@@ -0,0 +1,2 @@
node_modules
.astro

30
001.FRONTEND/Dockerfile Normal file
View File

@@ -0,0 +1,30 @@
# --- 1. FAZA: Izgradnja (Build) ---
FROM node:22-alpine AS builder
WORKDIR /app
# Kopiramo samo datoteke ovisnosti kako bismo iskoristili Docker cache
COPY package*.json ./
RUN npm install
# Kopiramo ostatak izvornog koda i pokrećemo build
COPY . .
RUN npm run build
# --- 2. FAZA: Pokretanje (Run) ---
FROM node:22-alpine AS runner
WORKDIR /app
# Definiramo produkcijsko okruženje
ENV NODE_ENV=production
ENV HOST=0.0.0.0
ENV PORT=4321
# Kopiramo samo izgrađene datoteke iz prve faze (dist) i potrebne module
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package*.json ./
EXPOSE 4321
# Pokretanje aplikacije izravno preko Node-a
CMD ["node", "./dist/server/entry.mjs"]

View File

@@ -1,25 +1,34 @@
// @ts-check
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
import tailwindcss from '@tailwindcss/vite';
// https://astro.build/config
export default defineConfig({
// Ovo omogućuje dinamičke rute bez getStaticPaths
output: 'server',
adapter: node({
mode: 'standalone'
mode: 'standalone',
}),
server: {
host: true,
port: 4321,
},
image: {
// Dodaj domene s kojih Astro smije povlačiti i optimizirati slike
domains: ['localhost', '127.0.0.1'],
domains: ['localhost', '127.0.0.1','v003-backend.captain.mitteworkspace.cloud'],
},
build: {
inlineStylesheets: 'always'
},
vite: {
plugins: [tailwindcss()]
plugins: [tailwindcss()],
// KLJUČNO ZA TAILWIND v4 + NODE standalone SSR:
ssr: {
// Prisiljava Vite da uključi Tailwind v4 stilove u serverski bundle
noExternal: ['tailwindcss', '@tailwindcss/vite']
},
}
});
});

View File

@@ -8,7 +8,7 @@
"name": "poslovanje",
"version": "0.0.1",
"dependencies": {
"@astrojs/node": "^10.1.1",
"@astrojs/node": "^10.0.6",
"@tailwindcss/vite": "^4.2.4",
"astro": "^6.1.9",
"flowbite": "^4.0.1",
@@ -1186,9 +1186,9 @@
"license": "MIT"
},
"node_modules/@oxc-project/types": {
"version": "0.130.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.130.0.tgz",
"integrity": "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==",
"version": "0.132.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz",
"integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==",
"license": "MIT",
"peer": true,
"funding": {
@@ -1206,9 +1206,9 @@
}
},
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.1.tgz",
"integrity": "sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz",
"integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==",
"cpu": [
"arm64"
],
@@ -1223,9 +1223,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.1.tgz",
"integrity": "sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz",
"integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==",
"cpu": [
"arm64"
],
@@ -1240,9 +1240,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.1.tgz",
"integrity": "sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz",
"integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==",
"cpu": [
"x64"
],
@@ -1257,9 +1257,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.1.tgz",
"integrity": "sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz",
"integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==",
"cpu": [
"x64"
],
@@ -1274,9 +1274,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.1.tgz",
"integrity": "sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz",
"integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==",
"cpu": [
"arm"
],
@@ -1291,9 +1291,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.1.tgz",
"integrity": "sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz",
"integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==",
"cpu": [
"arm64"
],
@@ -1308,9 +1308,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.1.tgz",
"integrity": "sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz",
"integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==",
"cpu": [
"arm64"
],
@@ -1325,9 +1325,9 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.1.tgz",
"integrity": "sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz",
"integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==",
"cpu": [
"ppc64"
],
@@ -1342,9 +1342,9 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.1.tgz",
"integrity": "sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz",
"integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==",
"cpu": [
"s390x"
],
@@ -1359,9 +1359,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.1.tgz",
"integrity": "sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz",
"integrity": "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==",
"cpu": [
"x64"
],
@@ -1376,9 +1376,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.1.tgz",
"integrity": "sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz",
"integrity": "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==",
"cpu": [
"x64"
],
@@ -1393,9 +1393,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.1.tgz",
"integrity": "sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz",
"integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==",
"cpu": [
"arm64"
],
@@ -1410,9 +1410,9 @@
}
},
"node_modules/@rolldown/binding-wasm32-wasi": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.1.tgz",
"integrity": "sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz",
"integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==",
"cpu": [
"wasm32"
],
@@ -1429,9 +1429,9 @@
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.1.tgz",
"integrity": "sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz",
"integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==",
"cpu": [
"arm64"
],
@@ -1446,9 +1446,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.1.tgz",
"integrity": "sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz",
"integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==",
"cpu": [
"x64"
],
@@ -2337,9 +2337,9 @@
}
},
"node_modules/astro": {
"version": "6.3.6",
"resolved": "https://registry.npmjs.org/astro/-/astro-6.3.6.tgz",
"integrity": "sha512-lM30gGI/iASK9Z1WQVnBBYzxVwDv8slkXbJOF7FNJdZQeBrFETpsQvYoLRupM/adt2ObP5hkYAWEeCjofoqlRw==",
"version": "6.3.7",
"resolved": "https://registry.npmjs.org/astro/-/astro-6.3.7.tgz",
"integrity": "sha512-zIeDRrI0qNgN1lcCjNqt6/IVCVej7VwSa326cO8uP9BOk1cg4QuffhLnOn2gCgWQr32/wxpSRFfXiLKHglu1Tw==",
"license": "MIT",
"dependencies": {
"@astrojs/compiler": "^4.0.0",
@@ -2920,9 +2920,9 @@
}
},
"node_modules/enhanced-resolve": {
"version": "5.21.6",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz",
"integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==",
"version": "5.22.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.22.0.tgz",
"integrity": "sha512-xYcDWrpELkFzz9SpZ3PlI6Eu6eD93Yf0WLDRxikGhWJ3MAir2SNZTIVCVZqZ/NUyx8AdMc2gT9C0gPiw18kG+A==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
@@ -5305,13 +5305,13 @@
}
},
"node_modules/rolldown": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.1.tgz",
"integrity": "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.2.tgz",
"integrity": "sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==",
"license": "MIT",
"peer": true,
"dependencies": {
"@oxc-project/types": "=0.130.0",
"@oxc-project/types": "=0.132.0",
"@rolldown/pluginutils": "^1.0.0"
},
"bin": {
@@ -5321,21 +5321,21 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
"@rolldown/binding-android-arm64": "1.0.1",
"@rolldown/binding-darwin-arm64": "1.0.1",
"@rolldown/binding-darwin-x64": "1.0.1",
"@rolldown/binding-freebsd-x64": "1.0.1",
"@rolldown/binding-linux-arm-gnueabihf": "1.0.1",
"@rolldown/binding-linux-arm64-gnu": "1.0.1",
"@rolldown/binding-linux-arm64-musl": "1.0.1",
"@rolldown/binding-linux-ppc64-gnu": "1.0.1",
"@rolldown/binding-linux-s390x-gnu": "1.0.1",
"@rolldown/binding-linux-x64-gnu": "1.0.1",
"@rolldown/binding-linux-x64-musl": "1.0.1",
"@rolldown/binding-openharmony-arm64": "1.0.1",
"@rolldown/binding-wasm32-wasi": "1.0.1",
"@rolldown/binding-win32-arm64-msvc": "1.0.1",
"@rolldown/binding-win32-x64-msvc": "1.0.1"
"@rolldown/binding-android-arm64": "1.0.2",
"@rolldown/binding-darwin-arm64": "1.0.2",
"@rolldown/binding-darwin-x64": "1.0.2",
"@rolldown/binding-freebsd-x64": "1.0.2",
"@rolldown/binding-linux-arm-gnueabihf": "1.0.2",
"@rolldown/binding-linux-arm64-gnu": "1.0.2",
"@rolldown/binding-linux-arm64-musl": "1.0.2",
"@rolldown/binding-linux-ppc64-gnu": "1.0.2",
"@rolldown/binding-linux-s390x-gnu": "1.0.2",
"@rolldown/binding-linux-x64-gnu": "1.0.2",
"@rolldown/binding-linux-x64-musl": "1.0.2",
"@rolldown/binding-openharmony-arm64": "1.0.2",
"@rolldown/binding-wasm32-wasi": "1.0.2",
"@rolldown/binding-win32-arm64-msvc": "1.0.2",
"@rolldown/binding-win32-x64-msvc": "1.0.2"
}
},
"node_modules/rollup": {
@@ -5398,9 +5398,9 @@
}
},
"node_modules/semver": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
"integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -6013,16 +6013,16 @@
}
},
"node_modules/vite": {
"version": "8.0.13",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz",
"integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==",
"version": "8.0.14",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz",
"integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==",
"license": "MIT",
"peer": true,
"dependencies": {
"lightningcss": "^1.32.0",
"picomatch": "^4.0.4",
"postcss": "^8.5.14",
"rolldown": "1.0.1",
"postcss": "^8.5.15",
"rolldown": "1.0.2",
"tinyglobby": "^0.2.16"
},
"bin": {

View File

@@ -10,9 +10,9 @@ interface Props {
description?: string;
}
const {
title = site.title,
description = site.description
const {
title = site.title,
description = site.description
} = Astro.props;
---
@@ -24,16 +24,16 @@ const {
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<meta name="description" content={description} />
<title>{title} | {site.title}</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
</head>
<body class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-white antialiased min-h-screen flex flex-col">
<Nav />
<main class="flex-grow w-full max-w-7xl mx-auto pt-24 pb-12 px-4 sm:px-6 lg:px-8">
@@ -55,13 +55,4 @@ const {
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
font-family: 'Inter', system-ui, sans-serif;
}
/* Osigurava da Dark Mode radi ispravno s Flowbite-om */
.dark {
color-scheme: dark;
}
</style>
</style>

View File

@@ -1,5 +1,8 @@
// src/lib/api.js
const API_BASE = import.meta.env.PUBLIC_API_URL;
// 1. Osiguravamo da API_BASE uvijek završava s točno jednom kosom crtom
const RAW_BASE = import.meta.env.PUBLIC_API_URL;
const API_BASE = RAW_BASE.endsWith('/') ? RAW_BASE : `${RAW_BASE}/`;
/**
* POMOĆNA FUNKCIJA:端 Dohvaća token i postavlja Headere
@@ -66,42 +69,41 @@ async function handleResponse(res) {
// --- RUTE ---
export const routes = {
// Autentifikacija
login: () => '/token/',
trenutniKorisnik: () => '/users/me/',
// Autentifikacija (Maknute početne kose crte!)
login: () => 'token/',
trenutniKorisnik: () => 'users/me/',
// Radni nalozi
radniNalozi: (params = {}) => {
const baseUrl = '/operativa/radni-nalozi/';
// Čistimo null i undefined vrijednosti iz filtera
const baseUrl = 'operativa/radni-nalozi/';
const cleanParams = Object.fromEntries(
Object.entries(params).filter(([_, v]) => v != null)
);
const queryString = new URLSearchParams(cleanParams).toString();
return queryString ? `${baseUrl}?${queryString}` : baseUrl;
},
radniNalogDetalji: (id) => `/operativa/radni-nalozi/${id}/`,
sljedeciBrojNaloga: () => '/operativa/radni-nalozi/sljedeci-broj/',
radniNalogDetalji: (id) => `operativa/radni-nalozi/${id}/`,
sljedeciBrojNaloga: () => 'operativa/radni-nalozi/sljedeci-broj/',
// Putni nalozi i logistika
putniNalozi: () => '/operativa/putni-nalozi/',
putniNalozi: () => 'operativa/putni-nalozi/',
// Vozila i strojevi (Fleet modul)
vozila: () => '/fleet/vozila/', // USKLAĐENO: Prebačeno pod zajednički /fleet/ namespace
vozila: () => 'fleet/vozila/',
strojevi: (vlasnikId = null) => {
const baseUrl = '/fleet/strojevi/';
const baseUrl = 'fleet/strojevi/';
if (vlasnikId) return `${baseUrl}?vlasnik=${vlasnikId}`;
return baseUrl;
},
strojDetalji: (id) => `/fleet/strojevi/${id}/`,
strojDetalji: (id) => `fleet/strojevi/${id}/`,
// Kupci / Klijenti
kupciSvi: () => '/kupci/svi/',
kupacDetalji: (id) => `/kupci/svi/${id}/`,
kupciSvi: () => 'kupci/svi/',
kupacDetalji: (id) => `kupci/svi/${id}/`,
// Kalendar i raspored
mojRaspored: () => '/kalendar/moj-raspored/',
kalendarDogadaji: () => '/kalendar/dogadaji/'
mojRaspored: () => 'kalendar/moj-raspored/',
kalendarDogadaji: () => 'kalendar/dogadaji/'
};
// --- API METODE ---
@@ -418,16 +420,20 @@ export async function fetchPutniNaloziData() {
}
}
/**
* Dohvaća sljedeći slobodni broj radnog naloga s backenda
* Osigurano protiv duplih kosih crta i preflight redirecta
*/
export async function fetchSljedeciBrojNaloga() {
try {
// Koristimo novu rutu iz routes objekta
const url = `${API_BASE}${routes.sljedeciBrojNaloga()}`;
// Pametno spajanje baze i rute (isto kao u ostalim očišćenim metodama)
const base = API_BASE.endsWith('/') ? API_BASE : `${API_BASE}/`;
const ruta = routes.sljedeciBrojNaloga();
const url = `${base}${ruta}`;
const res = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
headers: getAuthHeaders() // Koristi centralizirane headere umjesto sirovog objekta
});
if (res.ok) {

View File

@@ -1,34 +1,35 @@
---
// src/pages/fleet/vozila/[id].astro
import Layout from "../../../layouts/Layout.astro"; // Prilagodi putanju svom glavnom layoutu
import { fetchCalendarEvents } from "../../../lib/api"; // Pretpostavka lokacije tvog centraliziranog API-ja
---
import Layout from "../../../layouts/Layout.astro";
import Button from "../../../components/Button.astro"; // Uvoz tvoje zajedničke komponente za gumbe
import { getStatusColorClass } from "../../../utils/ui";
// 1. Dohvat ID-ja iz URL parametara
const { id } = Astro.params;
// 2. Dohvat podataka s Django API-ja
// Ovdje koristimo tvoj centralizirani API klijent. Ako imaš fetchVoziloDetails(id), iskoristi ga.
// Kao siguran fallback, dohvaćamo sva vozila i filtriramo ono koje nam treba.
const response = await fetch(`${Astro.url.origin}/api/fleet/vozila`); // ili izravno tvoj DRF endpoint ako lib nema pojedinačni fetch
let vozilo = null;
let radniNalozi = [];
// 2. Dohvat podataka s Django API-ja (Backend)
try {
// Simulacija dohvata preko tvog centraliziranog klijenta ili izravnog fetch-a
// Ako tvoj api.js ima metodu npr. fetchVozilo(id), zamijeni ovo s: vozilo = await fetchVozilo(id);
const svaVozilaResponse = await fetch("http://127.0.0.1:8000/api/fleet/vozila/"); // Primjer internog DRF endpointa
const vozila = await svaVozilaResponse.json();
vozilo = vozila.find((v) => v.id === parseInt(id));
const voziloResponse = await fetch(`http://127.0.0.1:8000/api/fleet/vozila/${id}/`);
if (voziloResponse.ok) {
vozilo = await voziloResponse.json();
}
const naloziResponse = await fetch(`http://127.0.0.1:8000/api/operativa/radni-nalozi/`);
if (naloziResponse.ok) {
const sviNalozi = await naloziResponse.json();
radniNalozi = sviNalozi.filter((nalog) => nalog.vozilo === parseInt(id) || nalog.vozilo_id === parseInt(id));
}
} catch (error) {
console.error("Greška pri dohvaćanju podataka o vozilu:", error);
console.error("Greška pri dohvaćanju podataka s Django API-ja:", error);
}
// Ako vozilo ne postoji, preusmjeravamo na 404 ili na listu vozila
if (!vozilo) {
return Astro.redirect("/fleet/vozila?error=not-found");
}
// Pomoćna funkcija za formatiranje statusa lokalno ako zakaže status_prikaz
function formatStatusLocal(status) {
const map = {
'aktivan': 'Aktivan',
@@ -38,21 +39,31 @@ function formatStatusLocal(status) {
return map[status?.toLowerCase()] || status;
}
function getPrioritetClass(prioritet) {
const p = prioritet?.toLowerCase();
if (p === 'hitan' || p === 'visok') return 'bg-red-500/10 text-red-500 border-red-500/20';
if (p === 'srednji') return 'bg-amber-500/10 text-amber-500 border-amber-500/20';
return 'bg-blue-500/10 text-blue-500 border-blue-500/20';
}
const statusKlasa = getStatusColorClass(vozilo.status?.toLowerCase());
---
<Layout title={`Vozilo | ${vozilo.naziv}`}>
<div class="max-w-4xl mx-auto px-4 py-8 font-sans">
<a
href="/fleet/vozila"
class="inline-flex items-center gap-2 text-sm font-black uppercase tracking-widest text-gray-400 hover:text-blue-600 dark:hover:text-blue-400 transition-colors mb-6"
>
<i class="fa-solid fa-arrow-left-long"></i> Povratak na popis
</a>
<div class="mb-6">
<Button
href="/fleet/vozila"
variant="ghost"
class="!px-0 text-sm font-black uppercase tracking-widest text-gray-400 hover:text-blue-600 dark:hover:text-blue-400 transition-colors"
>
<i class="fa-solid fa-arrow-left-long mr-2"></i> Povratak na popis
</Button>
</div>
<div class="w-full bg-white dark:bg-gray-800 text-gray-900 dark:text-white shadow-2xl rounded-[3rem] border border-gray-100 dark:border-gray-700 overflow-hidden transition-all mb-8">
<div class="w-full bg-white dark:bg-gray-800 text-gray-900 dark:text-white shadow-2xl rounded-[3rem] border border-gray-100 dark:border-gray-700 overflow-hidden transition-all">
<div class="flex flex-wrap justify-between items-center bg-gray-50/50 dark:bg-gray-900/50 px-8 py-8 border-b border-gray-100 dark:border-gray-700 gap-4">
<div class="flex items-center gap-5">
<div class="bg-blue-600/10 text-blue-600 dark:text-blue-400 p-5 rounded-2xl shadow-md">
@@ -74,7 +85,6 @@ const statusKlasa = getStatusColorClass(vozilo.status?.toLowerCase());
</div>
<div class="p-8 grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="bg-gray-50 dark:bg-gray-900/40 p-6 rounded-2xl border border-gray-100 dark:border-gray-700/50">
<span class="text-[10px] font-black uppercase tracking-[0.2em] text-gray-400 dark:text-gray-500 block mb-1">
Registracijska oznaka
@@ -113,44 +123,106 @@ const statusKlasa = getStatusColorClass(vozilo.status?.toLowerCase());
Vozilo je mapirano na centralni logistički sustav fleet managementa. Sve izmjene kilometara i servisnih naloga sinkroniziraju se u realnom vremenu s radnim nalozima operativnog tima.
</p>
</div>
</div>
<div class="px-8 py-5 bg-gray-50 dark:bg-gray-900/30 border-t border-gray-100 dark:border-gray-700 flex justify-end gap-3">
<a
<Button
href={`/operativa/radni-nalozi?vozilo=${vozilo.id}`}
class="px-5 py-2.5 bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-200 text-xs font-black uppercase tracking-widest rounded-xl border border-gray-200 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600 transition-all active:scale-95 shadow-sm"
variant="secondary"
class="text-xs font-black uppercase tracking-widest rounded-xl shadow-sm"
>
<i class="fa-solid fa-file-invoice mr-1.5 opacity-70"></i> Radni Nalozi
</a>
<button
<i class="fa-solid fa-file-invoice mr-1.5 opacity-70"></i> Otvori u operativi
</Button>
<Button
id="btn-brzi-servis"
data-id={vozilo.id}
class="px-5 py-2.5 bg-blue-600 text-white text-xs font-black uppercase tracking-widest rounded-xl hover:bg-blue-700 transition-all active:scale-95 shadow-lg shadow-blue-600/20"
variant="primary"
class="text-xs font-black uppercase tracking-widest rounded-xl shadow-lg shadow-blue-600/20"
>
<i class="fa-solid fa-screwdriver-wrench mr-1.5"></i> Otvori Servis
</button>
</Button>
</div>
</div>
<div class="w-full bg-white dark:bg-gray-800 text-gray-900 dark:text-white shadow-2xl rounded-[3rem] border border-gray-100 dark:border-gray-700 overflow-hidden p-8">
<div class="flex items-center justify-between mb-6 border-b border-gray-100 dark:border-gray-700 pb-4">
<div class="flex items-center gap-3">
<i class="fa-solid fa-clipboard-list text-2xl text-blue-600 dark:text-blue-400"></i>
<h2 class="text-xl font-black uppercase tracking-tight italic">Povijest Radnih Naloga</h2>
</div>
<span class="text-xs font-black bg-gray-100 dark:bg-gray-900 px-3 py-1.5 rounded-xl uppercase tracking-widest border border-gray-200 dark:border-gray-700">
Ukupno: {radniNalozi.length}
</span>
</div>
{radniNalozi.length === 0 ? (
<div class="text-center py-12 bg-gray-50 dark:bg-gray-900/20 rounded-2xl border border-dashed border-gray-200 dark:border-gray-700">
<i class="fa-solid fa-folder-open text-4xl text-gray-300 dark:text-gray-600 mb-3"></i>
<p class="text-sm font-bold text-gray-400 uppercase tracking-wider">Nema evidentiranih radnih naloga za ovo vozilo.</p>
</div>
) : (
<div class="space-y-4">
{radniNalozi.map((nalog) => (
<div class="flex flex-wrap items-center justify-between p-5 bg-gray-50 dark:bg-gray-900/30 rounded-2xl border border-gray-100 dark:border-gray-700/50 hover:border-blue-600 dark:hover:border-blue-400 transition-all gap-4">
<div class="flex items-start gap-4">
<div class="bg-gray-200 dark:bg-gray-800 text-gray-700 dark:text-gray-300 p-3 rounded-xl font-black text-xs">
#{nalog.broj_naloga || nalog.id}
</div>
<div>
<h4 class="text-sm font-black uppercase tracking-tight text-gray-800 dark:text-gray-100">
{nalog.opis_kvara || nalog.naslov || "Opis radova nije definiran"}
</h4>
<div class="flex items-center gap-4 mt-1 text-xs text-gray-400 font-medium">
<span>
<i class="fa-solid fa-calendar-days mr-1 text-blue-600/60"></i>
{nalog.datum_otvaranja ? new Date(nalog.datum_otvaranja).toLocaleDateString('hr-HR') : "Nepoznat datum"}
</span>
{nalog.kilometraža_prijave && (
<span>
<i class="fa-solid fa-gauge-high mr-1 text-blue-600/60"></i>
{nalog.kilometraža_prijave.toLocaleString('hr-HR')} KM
</span>
)}
</div>
</div>
</div>
<div class="flex items-center gap-3">
<span class={`text-[10px] font-black px-2.5 py-1 rounded-lg border uppercase tracking-wider ${getPrioritetClass(nalog.prioritet)}`}>
{nalog.prioritet || "Normalno"}
</span>
<Button
href={`/operativa/radni-nalozi/${nalog.id}`}
variant="ghost"
class="!p-2.5 bg-white dark:bg-gray-700 text-gray-500 dark:text-gray-300 rounded-xl border border-gray-200 dark:border-gray-600 hover:text-blue-600 dark:hover:text-blue-400 transition-colors shadow-sm"
title="Pregledaj nalog"
>
<i class="fa-solid fa-chevron-right"></i>
</Button>
</div>
</div>
))}
</div>
)}
</div>
</div>
</Layout>
<script>
// Klijentska skripta za brze akcije na stranici vozila
function initDetaljiVozila() {
const servisBtn = document.getElementById('btn-brzi-servis');
if (servisBtn) {
servisBtn.addEventListener('click', () => {
const voziloId = servisBtn.getAttribute('data-id');
// Primjer brze operativne akcije (npr. otvaranje modala ili preusmjeravanje)
console.log(`Otvaram brzu servisnu prijavu za vozilo ID: ${voziloId}`);
window.location.href = `/operativa/radni-nalozi/novo?vozilo_id=${voziloId}&tip=servis`;
});
}
}
// Podrška za Astro View Transitions i standardno učitavanje
initDetaljiVozila();
document.addEventListener('astro:after-swap', initDetaljiVozila);
</script>
</script>

View File

@@ -1 +1,8 @@
@import "tailwindcss";
/* src/styles/global.css */
@import "tailwindcss";
/* Eksplicitne putanje za Tailwind v4 skener na tvom LXC-u */
@source "../**/*.astro";
@source "../components/**/*.astro";
@source "../layouts/**/*.astro";
@source "../pages/**/*.astro";