feat: Add notification services for Email, Telegram, and WhatsApp

- Implement EmailNotifier for sending batch and test notifications via email.
- Implement TelegramNotifier for sending batch and test notifications via Telegram.
- Implement WahaNotifier for sending batch and test notifications via WhatsApp using WAHA API.
- Create migration for client_categories table and update clients table to include category_id.
- Create migration for notification_settings table to manage notification configurations.
- Add ClientCategorySeeder to populate default client categories.
- Create manifest.json for PWA support.
- Implement service worker (sw.js) for caching static assets and offline support.
- Add edit event view for modifying scheduled events.
- Create search results view for displaying search queries.
- Develop notification settings view for configuring notification channels and triggers.
This commit is contained in:
I-Fun
2026-04-01 12:46:58 +07:00
parent 1126877ef5
commit d3c35ad730
29 changed files with 1849 additions and 96 deletions

61
public/sw.js Normal file
View File

@@ -0,0 +1,61 @@
// KOL Manager Service Worker
// Provides basic offline support and caches static assets
const CACHE_NAME = 'kol-manager-v1';
const STATIC_ASSETS = [
'/',
'/manifest.json',
];
// Install — pre-cache shell
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => cache.addAll(STATIC_ASSETS))
);
self.skipWaiting();
});
// Activate — clean up old caches
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();
});
// Fetch — network-first for HTML/API, cache-first for static assets
self.addEventListener('fetch', event => {
const { request } = event;
const url = new URL(request.url);
// Skip non-GET and cross-origin requests
if (request.method !== 'GET' || url.origin !== location.origin) return;
// Cache-first for Vite build assets (hashed filenames)
if (url.pathname.startsWith('/build/')) {
event.respondWith(
caches.match(request).then(cached => cached || fetch(request).then(response => {
const clone = response.clone();
caches.open(CACHE_NAME).then(cache => cache.put(request, clone));
return response;
}))
);
return;
}
// Network-first for everything else (app routes)
event.respondWith(
fetch(request)
.then(response => {
// Cache successful HTML responses
if (response.ok && request.headers.get('Accept')?.includes('text/html')) {
const clone = response.clone();
caches.open(CACHE_NAME).then(cache => cache.put(request, clone));
}
return response;
})
.catch(() => caches.match(request))
);
});