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

@@ -155,45 +155,48 @@ GOOGLE_APPLICATION_CREDENTIALS=/absolute/private/path/firebase-service-account.j
После этого запустите `npm run dev`. Не копируйте JSON в `backend/`, не
добавляйте его в `.env` в виде текста и не коммитьте файл.
### Локальный запуск в Docker Compose
### Запуск в Docker Compose на Proxmox
Значение `GOOGLE_APPLICATION_CREDENTIALS` внутри контейнера должно указывать на
путь внутри контейнера, поэтому одного host-пути в корневом `.env` недостаточно.
Создайте локальный, не коммитящийся файл
`docker-compose.firebase.override.yml`:
```yaml
services:
backend:
environment:
FCM_ENABLED: "true"
FIREBASE_PROJECT_ID: ridebus-development
GOOGLE_APPLICATION_CREDENTIALS: /run/secrets/firebase-service-account.json
volumes:
- /absolute/private/path/firebase-service-account.json:/run/secrets/firebase-service-account.json:ro
```
Запуск:
Docker должен работать внутри VM или LXC-контейнера Proxmox. На этом Docker-
хосте создайте закрытый каталог и положите в него service account JSON:
```bash
docker compose \
-f docker-compose.yml \
-f docker-compose.firebase.override.yml \
up -d --build backend
sudo install -d -m 700 /opt/polotsk-transit/secrets
sudo install -m 600 /private/path/firebase-service-account.json \
/opt/polotsk-transit/secrets/firebase-service-account.json
```
Основной `docker-compose.yml` передаёт все FCM-переменные, но не требует и не
монтирует credential-файл. Поэтому обычный запуск с `FCM_ENABLED=false`
остается рабочим без Firebase.
В корневом `.env` проекта задайте:
```env
FCM_ENABLED=true
FCM_TOPIC=route_alerts
FCM_ANDROID_CHANNEL_ID=route_alerts
FIREBASE_PROJECT_ID=ridebus-production
FIREBASE_SECRETS_DIR=/opt/polotsk-transit/secrets
GOOGLE_APPLICATION_CREDENTIALS=/run/secrets/firebase-service-account.json
```
Основной `docker-compose.yml` монтирует `FIREBASE_SECRETS_DIR` в backend как
read-only `/run/secrets`. Запуск отдельного override-файла больше не требуется:
```bash
docker compose up -d --build backend
docker compose logs -f backend
```
При `FCM_ENABLED=false` Firebase не инициализируется, поэтому отсутствие JSON
не мешает работе приложения.
## Формирование сообщения
FCM получает одновременно видимый `notification` payload и строковый `data`
payload. Android priority всегда `high`. Все значения в `data`, включая ID и
даты, преобразуются в строки; отсутствующие значения передаются как пустые
строки.
FCM получает только строковый `data` payload. Верхнеуровневого `notification`
и `android.notification` нет, поэтому Android-клиент сам создаёт уведомление и
может добавить локальные действия, включая кнопку «Прочитано». Android
priority всегда `high`. Все значения в `data`, включая ID, даты, заголовок и
текст, являются строками; отсутствующие значения передаются как пустые строки.
`notification.title` берётся из `alert.title`. Если заголовок отсутствует или
`data.title` берётся из `alert.title`. Если заголовок отсутствует или
состоит только из пробелов, используется fallback по типу:
| `alert_type` | Fallback title |
@@ -203,7 +206,7 @@ payload. Android priority всегда `high`. Все значения в `data`
| `detour` | `Изменение маршрута` |
| `info` | `Информация о маршруте` |
`notification.body` содержит `alert.message`. Для пустого сообщения
`data.body` содержит `alert.message`. Для пустого сообщения
используется безопасный текст с номером маршрута, а если маршрут не указан —
общий текст об обновлении движения.
@@ -212,10 +215,6 @@ payload. Android priority всегда `high`. Все значения в `data`
```json
{
"topic": "route_alerts",
"notification": {
"title": "Задержка движения",
"body": "Автобусы задерживаются примерно на 15 минут"
},
"data": {
"event": "alert_created",
"alertId": "42",
@@ -223,14 +222,12 @@ payload. Android priority всегда `high`. Все значения в `data`
"routeId": "7",
"routeNumber": "5",
"startTime": "2026-07-11T09:00:00.000Z",
"endTime": "2026-07-11T11:00:00.000Z"
"endTime": "2026-07-11T11:00:00.000Z",
"title": "Задержка движения",
"body": "Автобусы задерживаются примерно на 15 минут"
},
"android": {
"priority": "high",
"notification": {
"channelId": "route_alerts",
"tag": "alert_42"
}
"priority": "high"
}
}
```
@@ -295,5 +292,5 @@ curl -i -X POST http://localhost:3000/api/v1/alerts \
вернуть `201`, а лог должен содержать `alertId`, Firebase code и причину без
credentials или токенов.
Поведение Android при foreground/background зависит от клиентской реализации,
но серверный контракт всегда содержит и `notification`, и `data` payload.
Поведение Android при foreground/background полностью определяется клиентской
обработкой data-only сообщения.