feat: add AppLayout, BrandLogo, ProfileMenu, and RouteBadge components
- Implemented AppLayout component for application shell with navigation and sidebar. - Created BrandLogo component to display the application logo. - Developed ProfileMenu component for user profile management with dropdown functionality. - Added RouteBadge component to visually represent route numbers with color coding. style: add CSS styles for Dashboard and Routes pages - Introduced Dashboard.css for styling the dashboard layout and components. - Created RoutesPage.css for styling the routes page, including filters and table layout. - Added SchedulesPage.css for styling the schedules layout and route options.
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
const {
|
||||
DEFAULT_FCM_TOPIC,
|
||||
DEFAULT_FCM_ANDROID_CHANNEL_ID,
|
||||
getFirebaseConfig,
|
||||
getFirebaseMessaging
|
||||
} = require('../config/firebase');
|
||||
@@ -91,14 +90,9 @@ function isAlertActive(alert = {}) {
|
||||
function buildAlertCreatedMessage(alert, options = {}) {
|
||||
const alertId = getAlertId(alert);
|
||||
const topic = options.topic || DEFAULT_FCM_TOPIC;
|
||||
const channelId = options.androidChannelId || options.channelId || DEFAULT_FCM_ANDROID_CHANNEL_ID;
|
||||
|
||||
return {
|
||||
topic,
|
||||
notification: {
|
||||
title: getNotificationTitle(alert),
|
||||
body: getNotificationBody(alert)
|
||||
},
|
||||
data: {
|
||||
event: 'alert_created',
|
||||
alertId,
|
||||
@@ -106,14 +100,12 @@ function buildAlertCreatedMessage(alert, options = {}) {
|
||||
routeId: getRouteId(alert),
|
||||
routeNumber: getRouteNumber(alert),
|
||||
startTime: toIsoString(firstDefined(alert.start_time, alert.startTime)),
|
||||
endTime: toIsoString(firstDefined(alert.end_time, alert.endTime))
|
||||
endTime: toIsoString(firstDefined(alert.end_time, alert.endTime)),
|
||||
title: getNotificationTitle(alert),
|
||||
body: getNotificationBody(alert)
|
||||
},
|
||||
android: {
|
||||
priority: 'high',
|
||||
notification: {
|
||||
channelId,
|
||||
tag: `alert_${alertId}`
|
||||
}
|
||||
priority: 'high'
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -131,14 +123,12 @@ class AlertNotificationService {
|
||||
|
||||
this.enabled = options.enabled === undefined ? firebaseConfig.enabled : options.enabled;
|
||||
this.topic = options.topic || firebaseConfig.topic;
|
||||
this.androidChannelId = options.androidChannelId || options.channelId || firebaseConfig.androidChannelId;
|
||||
this.logger = options.logger || getDefaultLogger();
|
||||
this.getMessaging = options.getMessaging || options.messagingFactory || (() => (
|
||||
options.messaging || getFirebaseMessaging({
|
||||
...firebaseConfig,
|
||||
enabled: this.enabled,
|
||||
topic: this.topic,
|
||||
androidChannelId: this.androidChannelId
|
||||
topic: this.topic
|
||||
})
|
||||
));
|
||||
}
|
||||
@@ -160,10 +150,7 @@ class AlertNotificationService {
|
||||
throw new Error('Firebase Messaging is unavailable');
|
||||
}
|
||||
|
||||
const message = buildAlertCreatedMessage(alert, {
|
||||
topic: this.topic,
|
||||
androidChannelId: this.androidChannelId
|
||||
});
|
||||
const message = buildAlertCreatedMessage(alert, { topic: this.topic });
|
||||
const messageId = await messaging.send(message);
|
||||
const normalizedMessageId = toDataString(messageId);
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ function createLogger() {
|
||||
};
|
||||
}
|
||||
|
||||
test('sends an exact alert-created notification and string-only data payload', async () => {
|
||||
test('sends an exact data-only alert-created message with string-only data', async () => {
|
||||
const messages = [];
|
||||
const service = new AlertNotificationService({
|
||||
enabled: true,
|
||||
@@ -52,10 +52,6 @@ test('sends an exact alert-created notification and string-only data payload', a
|
||||
assert.equal(messages.length, 1);
|
||||
assert.deepEqual(messages[0], {
|
||||
topic: 'route_alerts',
|
||||
notification: {
|
||||
title: 'Автобус задерживается',
|
||||
body: 'Задержка около 15 минут'
|
||||
},
|
||||
data: {
|
||||
event: 'alert_created',
|
||||
alertId: '42',
|
||||
@@ -63,16 +59,19 @@ test('sends an exact alert-created notification and string-only data payload', a
|
||||
routeId: '7',
|
||||
routeNumber: '12',
|
||||
startTime: '2026-07-11T08:30:00.000Z',
|
||||
endTime: '2026-07-11T09:30:00.000Z'
|
||||
endTime: '2026-07-11T09:30:00.000Z',
|
||||
title: 'Автобус задерживается',
|
||||
body: 'Задержка около 15 минут'
|
||||
},
|
||||
android: {
|
||||
priority: 'high',
|
||||
notification: {
|
||||
channelId: 'route_alerts',
|
||||
tag: 'alert_42'
|
||||
}
|
||||
priority: 'high'
|
||||
}
|
||||
});
|
||||
assert.equal('notification' in messages[0], false);
|
||||
assert.equal('notification' in messages[0].android, false);
|
||||
assert.equal(messages[0].android.priority, 'high');
|
||||
assert.equal(messages[0].data.title, 'Автобус задерживается');
|
||||
assert.equal(messages[0].data.body, 'Задержка около 15 минут');
|
||||
assert.ok(Object.values(messages[0].data).every((value) => typeof value === 'string'));
|
||||
});
|
||||
|
||||
@@ -92,6 +91,10 @@ test('serializes a null route id and missing optional values as empty strings',
|
||||
assert.equal(message.data.routeNumber, '');
|
||||
assert.equal(message.data.startTime, '');
|
||||
assert.equal(message.data.endTime, '');
|
||||
assert.equal(message.data.title, 'Новость');
|
||||
assert.equal(message.data.body, 'Важная информация');
|
||||
assert.equal('notification' in message, false);
|
||||
assert.equal('notification' in message.android, false);
|
||||
});
|
||||
|
||||
test('does not initialize messaging or send for an inactive alert', async () => {
|
||||
@@ -207,8 +210,8 @@ test('uses the requested Russian title fallbacks and a route-aware body fallback
|
||||
route_number: '5'
|
||||
});
|
||||
|
||||
assert.equal(message.notification.title, expectedTitle);
|
||||
assert.equal(message.notification.body, 'Актуальная информация по маршруту №5');
|
||||
assert.equal(message.data.title, expectedTitle);
|
||||
assert.equal(message.data.body, 'Актуальная информация по маршруту №5');
|
||||
}
|
||||
|
||||
const withoutRoute = buildAlertCreatedMessage({
|
||||
@@ -217,7 +220,7 @@ test('uses the requested Russian title fallbacks and a route-aware body fallback
|
||||
title: '',
|
||||
message: ''
|
||||
});
|
||||
assert.equal(withoutRoute.notification.body, 'Актуальная информация о движении транспорта');
|
||||
assert.equal(withoutRoute.data.body, 'Актуальная информация о движении транспорта');
|
||||
});
|
||||
|
||||
test('reads disabled-by-default Firebase configuration and environment overrides', () => {
|
||||
|
||||
Reference in New Issue
Block a user