Phase 121 — SMSPLANET Conversation + Notifications:
- migration 20260512_000110 adds smsplanet conversation + notifications tables
- src/Modules/Sms (SmsConversationService, SmsMessageRepository, SmsplanetWebhookController)
- src/Modules/Notifications (Repository, Controller, ApiController)
- order SMS tab, notification center, sender mode, inbound webhook
- public notifications.js + layouts/app.php integration
Phase 122 — SMSPLANET Default SMS Footer:
- migration 20260512_000111 adds smsplanet_integration_settings.default_footer
- footer appended to test SMS and order SMS, validated against 918 char limit
- settings textarea + compact order SMS note when footer configured
Bundled (could not split per-phase without hunk staging):
- routes/web.php (also carries Phase 118 fakturownia redirects)
- DOCS/{ARCHITECTURE,DB_SCHEMA,TECH_CHANGELOG}.md (118 + 121 + 122 entries)
- .paul/codebase/{architecture,db_schema,tech_changelog}.md (118 + 121 + 122)
- .paul/STATE.md, ROADMAP.md, changelog/2026-05-12.md (UNIFY closure)
Co-Authored-By: Claude <noreply@anthropic.com>
82 lines
2.4 KiB
JavaScript
82 lines
2.4 KiB
JavaScript
(function () {
|
|
var badge = document.getElementById('js-notification-badge');
|
|
var button = document.getElementById('js-notification-button');
|
|
if (!badge || !button || typeof fetch !== 'function') return;
|
|
|
|
var seenKey = 'orderproSeenNotificationIds';
|
|
var seenIds = loadSeenIds();
|
|
var permissionAsked = false;
|
|
|
|
function loadSeenIds() {
|
|
try {
|
|
return JSON.parse(localStorage.getItem(seenKey) || '[]').map(String);
|
|
} catch (e) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function saveSeenIds() {
|
|
try {
|
|
localStorage.setItem(seenKey, JSON.stringify(seenIds.slice(-100)));
|
|
} catch (e) {}
|
|
}
|
|
|
|
function updateBadge(count) {
|
|
var value = Math.max(0, Number(count) || 0);
|
|
badge.textContent = value > 99 ? '99+' : String(value);
|
|
badge.hidden = value === 0;
|
|
}
|
|
|
|
function requestPermission() {
|
|
if (permissionAsked || !('Notification' in window) || Notification.permission !== 'default') {
|
|
return Promise.resolve();
|
|
}
|
|
permissionAsked = true;
|
|
return Notification.requestPermission().catch(function () {});
|
|
}
|
|
|
|
function showBrowserNotification(item) {
|
|
var id = String(item.id || '');
|
|
if (id === '' || seenIds.indexOf(id) !== -1) return;
|
|
|
|
seenIds.push(id);
|
|
saveSeenIds();
|
|
if (!('Notification' in window) || Notification.permission !== 'granted') return;
|
|
|
|
var nativeNotification = new Notification(item.title || 'orderPRO', {
|
|
body: item.body || '',
|
|
tag: 'orderpro-notification-' + id
|
|
});
|
|
nativeNotification.onclick = function () {
|
|
window.focus();
|
|
if (item.target_url) {
|
|
window.location.href = item.target_url;
|
|
}
|
|
};
|
|
}
|
|
|
|
function poll() {
|
|
fetch('/api/notifications/unread', { credentials: 'same-origin' })
|
|
.then(function (response) { return response.ok ? response.json() : null; })
|
|
.then(function (data) {
|
|
if (!data || !data.ok) return;
|
|
updateBadge(data.count);
|
|
if (Array.isArray(data.items)) {
|
|
data.items.slice().reverse().forEach(showBrowserNotification);
|
|
}
|
|
})
|
|
.catch(function () {});
|
|
}
|
|
|
|
button.addEventListener('click', function (event) {
|
|
if ('Notification' in window && Notification.permission === 'default') {
|
|
event.preventDefault();
|
|
requestPermission().finally(function () {
|
|
window.location.href = button.href;
|
|
});
|
|
}
|
|
});
|
|
poll();
|
|
window.setInterval(poll, 30000);
|
|
})();
|