This commit is contained in:
19
frontend/public/manifest.webmanifest
Normal file
19
frontend/public/manifest.webmanifest
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "ERP Generic",
|
||||
"short_name": "ERP",
|
||||
"description": "Offline-capable ERP dashboard for field service teams",
|
||||
"start_url": "/",
|
||||
"scope": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#f9fafb",
|
||||
"theme_color": "#4f46e5",
|
||||
"lang": "hr",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/pwa-icon.svg",
|
||||
"sizes": "any",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any maskable"
|
||||
}
|
||||
]
|
||||
}
|
||||
53
frontend/public/offline.html
Normal file
53
frontend/public/offline.html
Normal file
@@ -0,0 +1,53 @@
|
||||
<!doctype html>
|
||||
<html lang="hr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Offline | ERP Generic</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
background: #f9fafb;
|
||||
color: #111827;
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
.card {
|
||||
max-width: 560px;
|
||||
margin: 24px;
|
||||
background: #fff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
}
|
||||
p {
|
||||
line-height: 1.5;
|
||||
}
|
||||
button {
|
||||
margin-top: 12px;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
background: #4f46e5;
|
||||
color: #fff;
|
||||
padding: 10px 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>Trenutno ste offline</h1>
|
||||
<p>
|
||||
Aplikacija i dalje radi s lokalno spremljenim podacima. Novi zapisi bit će sinkronizirani
|
||||
čim se internetska veza vrati.
|
||||
</p>
|
||||
<button onclick="location.reload()">Pokušaj ponovno</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
9
frontend/public/pwa-icon.svg
Normal file
9
frontend/public/pwa-icon.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<rect width="512" height="512" rx="96" fill="#4f46e5"/>
|
||||
<g fill="#ffffff">
|
||||
<rect x="106" y="126" width="300" height="70" rx="16"/>
|
||||
<rect x="106" y="221" width="300" height="70" rx="16" opacity="0.92"/>
|
||||
<rect x="106" y="316" width="190" height="70" rx="16" opacity="0.85"/>
|
||||
<circle cx="355" cy="351" r="36" fill="#fbbf24"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 420 B |
62
frontend/public/service-worker.js
Normal file
62
frontend/public/service-worker.js
Normal file
@@ -0,0 +1,62 @@
|
||||
const CACHE_NAME = 'erp-shell-v1';
|
||||
const OFFLINE_URL = '/offline.html';
|
||||
const PRECACHE_URLS = ['/', '/index.html', '/manifest.webmanifest', '/pwa-icon.svg', OFFLINE_URL];
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
event.waitUntil(
|
||||
caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_URLS))
|
||||
);
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener('activate', (event) => {
|
||||
event.waitUntil(
|
||||
caches.keys().then((cacheNames) =>
|
||||
Promise.all(
|
||||
cacheNames
|
||||
.filter((name) => name !== CACHE_NAME)
|
||||
.map((name) => caches.delete(name))
|
||||
)
|
||||
)
|
||||
);
|
||||
self.clients.claim();
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', (event) => {
|
||||
const { request } = event;
|
||||
if (request.method !== 'GET') return;
|
||||
|
||||
const url = new URL(request.url);
|
||||
if (url.origin !== self.location.origin) return;
|
||||
|
||||
if (request.mode === 'navigate') {
|
||||
event.respondWith(
|
||||
fetch(request)
|
||||
.then((response) => {
|
||||
const copy = response.clone();
|
||||
caches.open(CACHE_NAME).then((cache) => cache.put(request, copy));
|
||||
return response;
|
||||
})
|
||||
.catch(async () => {
|
||||
const cached = await caches.match(request);
|
||||
return cached || caches.match(OFFLINE_URL);
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (['style', 'script', 'worker', 'font', 'image'].includes(request.destination)) {
|
||||
event.respondWith(
|
||||
caches.match(request).then((cached) => {
|
||||
const networkFetch = fetch(request)
|
||||
.then((response) => {
|
||||
const copy = response.clone();
|
||||
caches.open(CACHE_NAME).then((cache) => cache.put(request, copy));
|
||||
return response;
|
||||
})
|
||||
.catch(() => cached);
|
||||
return cached || networkFetch;
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user