feat(121+122): smsplanet conversation, notifications, default footer
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>
This commit is contained in:
81
public/assets/js/modules/notifications.js
Normal file
81
public/assets/js/modules/notifications.js
Normal file
@@ -0,0 +1,81 @@
|
||||
(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);
|
||||
})();
|
||||
Reference in New Issue
Block a user