WFDASH-WEBAPP/public/sw-counter.js
Dave 18397bc9be feat(counter): add standalone counter order PWA
This commit adds a full unauthenticated PWA for in-store retail order taking:
- Dedicated `/new-order` route with complete order form, customer/company picker
- Public `/api/counter/*` endpoints for search, order creation, and photo uploads
- Photo capture and gallery selection with 10-photo per order limit
- Offline caching via dedicated service worker and app manifest
- Shortcut redirect at `/counter` for quick installation
- Comprehensive setup and usage documentation in `docs/counter-pwa.md`
- Minor cleanup to root layout metadata and service worker registration
- Small layout fix for collection pool row text truncation

Orders submitted via the PWA appear immediately in the main CRM's New tab, and the app is designed for trusted office kiosk phones without requiring user login.
2026-08-13 09:27:20 +02:00

73 lines
No EOL
2 KiB
JavaScript

const CACHE_NAME = "wfdash-counter-v1";
const OFFLINE_URL = "/offline";
const PRECACHE_ASSETS = [
"/new-order",
"/manifest-counter.json",
"/offline",
"/android-chrome-192x192.png",
"/android-chrome-512x512.png",
];
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_ASSETS))
);
self.skipWaiting();
});
self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(
keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k))
)
)
);
self.clients.claim();
});
self.addEventListener("fetch", (event) => {
const { request } = event;
if (request.method !== "GET") return;
// Never cache API or admin/upload traffic — every submit must hit
// the server live so the order lands on the board immediately.
const url = new URL(request.url);
if (url.pathname.startsWith("/api/")) return;
// Navigation requests: network-first, fall back to cached
// /new-order, then /offline.
if (request.mode === "navigate") {
event.respondWith(
fetch(request)
.then((response) => {
const clone = response.clone();
caches.open(CACHE_NAME).then((cache) => cache.put(request, clone));
return response;
})
.catch(async () => {
const cached = await caches.match(request);
if (cached) return cached;
const newOrder = await caches.match("/new-order");
if (newOrder) return newOrder;
return caches.match(OFFLINE_URL);
})
);
return;
}
// Static assets: stale-while-revalidate.
event.respondWith(
caches.match(request).then((cached) => {
const networkFetch = fetch(request)
.then((response) => {
const clone = response.clone();
caches.open(CACHE_NAME).then((cache) => cache.put(request, clone));
return response;
})
.catch(() => cached);
return cached || networkFetch;
})
);
});