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:
2026-09-15 22:11:10 +03:00
parent ddd67f438b
commit 6804ca5cdb
23 changed files with 999 additions and 1349 deletions

View File

@@ -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', () => {