diff --git a/backend-transit/.env.example b/backend-transit/.env.example new file mode 100644 index 0000000..1e06154 --- /dev/null +++ b/backend-transit/.env.example @@ -0,0 +1,14 @@ +# ----------------------------------------------------------------------------- +# Tripz Transit Microservice Configuration +# ----------------------------------------------------------------------------- + +# --- Server Port --- +PORT=4020 + +# --- Database Credentials (Transit) --- +# These must match what's in docker-compose.yml under transit-db +DB_HOST=transit-db +DB_PORT=5432 +DB_USER=transit_user +DB_PASSWORD=26ab87247a55f86032c943b7e361c475cd3ca8b3b772d82e2fe6a9b5ee0839a3 +DB_NAME=tripz_transit diff --git a/backend-transit/.gitignore b/backend-transit/.gitignore new file mode 100644 index 0000000..d5a500e --- /dev/null +++ b/backend-transit/.gitignore @@ -0,0 +1,39 @@ +# compiled output +/dist +/node_modules + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and Editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# Environment +.env +.env.test +.env.production diff --git a/backend-transit/docker-compose.yml b/backend-transit/docker-compose.yml new file mode 100644 index 0000000..ff580d0 --- /dev/null +++ b/backend-transit/docker-compose.yml @@ -0,0 +1,27 @@ +version: '3.8' + +services: + transit-db: + image: postgres:15-alpine + container_name: tripz-transit-db + env_file: + - .env + ports: + - "5433:5432" # Exposed on 5433 to avoid conflict with main DB + volumes: + - transit_pgdata:/var/lib/postgresql/data + + transit-api: + build: + context: . + dockerfile: Dockerfile + container_name: tripz-transit-api + ports: + - "4020:4020" + env_file: + - .env + depends_on: + - transit-db + +volumes: + transit_pgdata: diff --git a/backend-transit/package.json b/backend-transit/package.json new file mode 100644 index 0000000..0098605 --- /dev/null +++ b/backend-transit/package.json @@ -0,0 +1,27 @@ +{ + "name": "backend-transit", + "version": "1.0.0", + "description": "Tripz Transit Microservice", + "scripts": { + "build": "nest build", + "start": "nest start", + "start:dev": "nest start --watch", + "start:prod": "node dist/main" + }, + "dependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/config": "^3.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "@nestjs/typeorm": "^10.0.0", + "pg": "^8.11.0", + "reflect-metadata": "^0.1.13", + "rxjs": "^7.8.1", + "typeorm": "^0.3.17" + }, + "devDependencies": { + "@nestjs/cli": "^10.0.0", + "@types/node": "^20.3.1", + "typescript": "^5.1.3" + } +} diff --git a/backend-transit/src/app.module.ts b/backend-transit/src/app.module.ts new file mode 100644 index 0000000..e4e83cf --- /dev/null +++ b/backend-transit/src/app.module.ts @@ -0,0 +1,25 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { TransitModule } from './transit/transit.module'; + +@Module({ + imports: [ + ConfigModule.forRoot({ isGlobal: true }), + TypeOrmModule.forRootAsync({ + inject: [ConfigService], + useFactory: (cfg: ConfigService) => ({ + type: 'postgres', + host: cfg.get('DB_HOST'), + port: cfg.get('DB_PORT'), + database: cfg.get('DB_NAME'), + username: cfg.get('DB_USER'), + password: cfg.get('DB_PASSWORD'), + synchronize: true, // Should be false in production + autoLoadEntities: true, + }), + }), + TransitModule, + ], +}) +export class AppModule {} diff --git a/backend-transit/src/main.ts b/backend-transit/src/main.ts new file mode 100644 index 0000000..b996096 --- /dev/null +++ b/backend-transit/src/main.ts @@ -0,0 +1,18 @@ +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; +import { ValidationPipe } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + const cfg = app.get(ConfigService); + + app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true })); + app.enableCors(); + + // Start the standalone transit API + const port = cfg.get('PORT') || 4020; + await app.listen(port); + console.log(`Transit Microservice running on port ${port}`); +} +bootstrap(); diff --git a/backend-transit/src/transit/dto/transit.dto.ts b/backend-transit/src/transit/dto/transit.dto.ts new file mode 100644 index 0000000..25c265f --- /dev/null +++ b/backend-transit/src/transit/dto/transit.dto.ts @@ -0,0 +1,32 @@ +export class CreateRouteDto { + name: string; + tenant_id: string; + polyline?: Record; + is_active?: boolean; +} + +export class CreateStationDto { + route_id: string; + name: string; + latitude: number; + longitude: number; + order_index: number; +} + +export class CreateSupervisorDto { + tenant_id: string; + user_id: string; + route_id?: string; + institution_name?: string; +} + +export class StartTripDto { + route_id: string; + driver_id: string; +} + +export class BuyTicketDto { + passenger_id: string; + bus_trip_id: string; + price: number; +} diff --git a/backend-transit/src/transit/entities/transit-bus-trip.entity.ts b/backend-transit/src/transit/entities/transit-bus-trip.entity.ts new file mode 100644 index 0000000..aa0e80d --- /dev/null +++ b/backend-transit/src/transit/entities/transit-bus-trip.entity.ts @@ -0,0 +1,33 @@ +import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from 'typeorm'; +import { TransitRoute } from './transit-route.entity'; + +export type BusTripStatus = 'active' | 'completed' | 'cancelled'; + +@Entity('transit_bus_trips') +export class TransitBusTrip { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column() + route_id: string; + + // External reference to the main DB user + @Column() + driver_id: string; + + @Column({ type: 'varchar', length: 50, default: 'active' }) + status: BusTripStatus; + + @Column({ nullable: true }) + current_station_id: string; + + @CreateDateColumn() + created_at: Date; + + @UpdateDateColumn() + updated_at: Date; + + @ManyToOne(() => TransitRoute) + @JoinColumn({ name: 'route_id' }) + route: TransitRoute; +} diff --git a/backend-transit/src/transit/entities/transit-route.entity.ts b/backend-transit/src/transit/entities/transit-route.entity.ts new file mode 100644 index 0000000..f26db9a --- /dev/null +++ b/backend-transit/src/transit/entities/transit-route.entity.ts @@ -0,0 +1,25 @@ +import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm'; + +@Entity('transit_routes') +export class TransitRoute { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column() + tenant_id: string; + + @Column({ length: 150 }) + name: string; + + @Column({ type: 'jsonb', nullable: true }) + polyline: Record; + + @Column({ default: true }) + is_active: boolean; + + @CreateDateColumn() + created_at: Date; + + @UpdateDateColumn() + updated_at: Date; +} diff --git a/backend-transit/src/transit/entities/transit-station.entity.ts b/backend-transit/src/transit/entities/transit-station.entity.ts new file mode 100644 index 0000000..2628685 --- /dev/null +++ b/backend-transit/src/transit/entities/transit-station.entity.ts @@ -0,0 +1,30 @@ +import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne, JoinColumn } from 'typeorm'; +import { TransitRoute } from './transit-route.entity'; + +@Entity('transit_stations') +export class TransitStation { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column() + route_id: string; + + @Column({ length: 150 }) + name: string; + + @Column('decimal', { precision: 10, scale: 7 }) + latitude: number; + + @Column('decimal', { precision: 10, scale: 7 }) + longitude: number; + + @Column('int') + order_index: number; + + @CreateDateColumn() + created_at: Date; + + @ManyToOne(() => TransitRoute) + @JoinColumn({ name: 'route_id' }) + route: TransitRoute; +} diff --git a/backend-transit/src/transit/entities/transit-supervisor.entity.ts b/backend-transit/src/transit/entities/transit-supervisor.entity.ts new file mode 100644 index 0000000..208a726 --- /dev/null +++ b/backend-transit/src/transit/entities/transit-supervisor.entity.ts @@ -0,0 +1,28 @@ +import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne, JoinColumn } from 'typeorm'; +import { TransitRoute } from './transit-route.entity'; + +@Entity('transit_supervisors') +export class TransitSupervisor { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column() + tenant_id: string; + + // External reference to the main DB user + @Column() + user_id: string; + + @Column({ nullable: true }) + route_id: string; + + @Column({ length: 150, nullable: true }) + institution_name: string; + + @CreateDateColumn() + created_at: Date; + + @ManyToOne(() => TransitRoute) + @JoinColumn({ name: 'route_id' }) + route: TransitRoute; +} diff --git a/backend-transit/src/transit/entities/transit-ticket.entity.ts b/backend-transit/src/transit/entities/transit-ticket.entity.ts new file mode 100644 index 0000000..313703e --- /dev/null +++ b/backend-transit/src/transit/entities/transit-ticket.entity.ts @@ -0,0 +1,33 @@ +import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne, JoinColumn } from 'typeorm'; +import { TransitBusTrip } from './transit-bus-trip.entity'; + +export type TicketStatus = 'active' | 'used' | 'refunded'; + +@Entity('transit_tickets') +export class TransitTicket { + @PrimaryGeneratedColumn('uuid') + id: string; + + // External reference to the main DB user + @Column() + passenger_id: string; + + @Column() + bus_trip_id: string; + + @Column('decimal', { precision: 10, scale: 3 }) + price: number; + + @Column({ type: 'varchar', length: 50, default: 'active' }) + status: TicketStatus; + + @Column({ nullable: true }) + qr_code: string; + + @CreateDateColumn() + created_at: Date; + + @ManyToOne(() => TransitBusTrip) + @JoinColumn({ name: 'bus_trip_id' }) + bus_trip: TransitBusTrip; +} diff --git a/backend-transit/src/transit/transit.controller.ts b/backend-transit/src/transit/transit.controller.ts new file mode 100644 index 0000000..3663370 --- /dev/null +++ b/backend-transit/src/transit/transit.controller.ts @@ -0,0 +1,53 @@ +import { Controller, Post, Get, Body, Param, Query } from '@nestjs/common'; +import { TransitService } from './transit.service'; +import { CreateRouteDto, CreateStationDto, CreateSupervisorDto, StartTripDto, BuyTicketDto } from './dto/transit.dto'; + +@Controller('transit') +export class TransitController { + constructor(private readonly transitService: TransitService) {} + + @Post('routes') + createRoute(@Body() dto: CreateRouteDto) { + return this.transitService.createRoute(dto); + } + + @Get('routes') + getRoutes(@Query('tenant_id') tenant_id: string) { + return this.transitService.getRoutes(tenant_id); + } + + @Post('stations') + createStation(@Body() dto: CreateStationDto) { + return this.transitService.createStation(dto); + } + + @Get('routes/:routeId/stations') + getStations(@Param('routeId') routeId: string) { + return this.transitService.getStations(routeId); + } + + @Post('supervisors') + assignSupervisor(@Body() dto: CreateSupervisorDto) { + return this.transitService.assignSupervisor(dto); + } + + @Get('reports/supervisor/:userId') + getSupervisorReports(@Param('userId') userId: string) { + return this.transitService.getSupervisorReports(userId); + } + + @Post('trips/start') + startTrip(@Body() dto: StartTripDto) { + return this.transitService.startTrip(dto); + } + + @Post('trips/:id/end') + endTrip(@Param('id') id: string) { + return this.transitService.endTrip(id); + } + + @Post('tickets/buy') + buyTicket(@Body() dto: BuyTicketDto) { + return this.transitService.buyTicket(dto); + } +} diff --git a/backend-transit/src/transit/transit.module.ts b/backend-transit/src/transit/transit.module.ts new file mode 100644 index 0000000..24c7568 --- /dev/null +++ b/backend-transit/src/transit/transit.module.ts @@ -0,0 +1,25 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { TransitRoute } from './entities/transit-route.entity'; +import { TransitStation } from './entities/transit-station.entity'; +import { TransitSupervisor } from './entities/transit-supervisor.entity'; +import { TransitBusTrip } from './entities/transit-bus-trip.entity'; +import { TransitTicket } from './entities/transit-ticket.entity'; +import { TransitController } from './transit.controller'; +import { TransitService } from './transit.service'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ + TransitRoute, + TransitStation, + TransitSupervisor, + TransitBusTrip, + TransitTicket, + ]), + ], + controllers: [TransitController], + providers: [TransitService], + exports: [TypeOrmModule, TransitService], +}) +export class TransitModule {} diff --git a/backend-transit/src/transit/transit.service.ts b/backend-transit/src/transit/transit.service.ts new file mode 100644 index 0000000..a0e0320 --- /dev/null +++ b/backend-transit/src/transit/transit.service.ts @@ -0,0 +1,74 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { TransitRoute } from './entities/transit-route.entity'; +import { TransitStation } from './entities/transit-station.entity'; +import { TransitSupervisor } from './entities/transit-supervisor.entity'; +import { TransitBusTrip } from './entities/transit-bus-trip.entity'; +import { TransitTicket } from './entities/transit-ticket.entity'; +import { CreateRouteDto, CreateStationDto, CreateSupervisorDto, StartTripDto, BuyTicketDto } from './dto/transit.dto'; + +@Injectable() +export class TransitService { + constructor( + @InjectRepository(TransitRoute) private routesRepo: Repository, + @InjectRepository(TransitStation) private stationsRepo: Repository, + @InjectRepository(TransitSupervisor) private supervisorsRepo: Repository, + @InjectRepository(TransitBusTrip) private tripsRepo: Repository, + @InjectRepository(TransitTicket) private ticketsRepo: Repository, + ) {} + + // Routes + async createRoute(dto: CreateRouteDto) { + const route = this.routesRepo.create(dto); + return this.routesRepo.save(route); + } + + async getRoutes(tenant_id: string) { + return this.routesRepo.find({ where: { tenant_id, is_active: true } }); + } + + // Stations + async createStation(dto: CreateStationDto) { + const station = this.stationsRepo.create(dto); + return this.stationsRepo.save(station); + } + + async getStations(route_id: string) { + return this.stationsRepo.find({ where: { route_id }, order: { order_index: 'ASC' } }); + } + + // Supervisors + async assignSupervisor(dto: CreateSupervisorDto) { + const supervisor = this.supervisorsRepo.create(dto); + return this.supervisorsRepo.save(supervisor); + } + + async getSupervisorReports(user_id: string) { + // Basic implementation: find trips and tickets for the supervisor's route + const supervisor = await this.supervisorsRepo.findOne({ where: { user_id } }); + if (!supervisor || !supervisor.route_id) throw new NotFoundException('Supervisor or Route not found'); + + const trips = await this.tripsRepo.count({ where: { route_id: supervisor.route_id } }); + // This requires a more complex query in production, but simplified for scaffolding + return { supervisor_id: supervisor.id, route_id: supervisor.route_id, total_trips: trips }; + } + + // Trips + async startTrip(dto: StartTripDto) { + const trip = this.tripsRepo.create({ ...dto, status: 'active' }); + return this.tripsRepo.save(trip); + } + + async endTrip(id: string) { + await this.tripsRepo.update(id, { status: 'completed' }); + return this.tripsRepo.findOne({ where: { id } }); + } + + // Tickets + async buyTicket(dto: BuyTicketDto) { + // Future: Call main API to deduct from wallet + const ticket = this.ticketsRepo.create({ ...dto, status: 'active' }); + return this.ticketsRepo.save(ticket); + } +} diff --git a/backend-transit/tsconfig.json b/backend-transit/tsconfig.json new file mode 100644 index 0000000..95f5641 --- /dev/null +++ b/backend-transit/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": false, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false + } +} diff --git a/dashboards/transit-web/.env.example b/dashboards/transit-web/.env.example new file mode 100644 index 0000000..8a6a632 --- /dev/null +++ b/dashboards/transit-web/.env.example @@ -0,0 +1,3 @@ +# رابط الباك اند — يشير إلى backend/transit عبر connect_admin.php +# VITE_API_BASE=https://api.siromove.com/transit +VITE_API_BASE=https://jordan-siro.intaleqapp.com diff --git a/dashboards/transit-web/.gitignore b/dashboards/transit-web/.gitignore new file mode 100644 index 0000000..01473fa --- /dev/null +++ b/dashboards/transit-web/.gitignore @@ -0,0 +1,4 @@ +node_modules +dist +.env +*.local diff --git a/dashboards/transit-web/README.md b/dashboards/transit-web/README.md new file mode 100644 index 0000000..964fb4a --- /dev/null +++ b/dashboards/transit-web/README.md @@ -0,0 +1,39 @@ +# لوحة مشرف المؤسسة — مواصلاتي + +واجهة ويب (Vue 3 + Vite) يستخدمها مشرف الجامعة/الفندق/الشركة لإدارة أسطول +الباصات، الخطوط، الجداول، الطلاب، والإعلانات. تتصل حصراً بـ +`backend/transit/*` عبر بوابة `connect_admin.php` (مصادقة هاتف + OTP، جلسة +Bearer صالحة 24 ساعة). + +## التشغيل محلياً + +```bash +cp .env.example .env # اضبط VITE_API_BASE على رابط الباك اند +npm install +npm run dev # http://localhost:5183 +``` + +## البناء للإنتاج + +```bash +npm run build # يُنتج dist/ +``` + +انشر محتوى `dist/` على `transit.siromove.com` (أو `admin.siromove.com` — كلا +الأصلين مضبوطان في CORS ضمن `backend/transit/connect_admin.php` عبر متغير +البيئة `TRANSIT_ADMIN_ORIGINS`). + +## البنية + +- `src/api/` — عميل HTTP (`client.js`) وكل نداءات `transit/*` (`transit.js`) +- `src/stores/auth.js` — جلسة المشرف (Pinia) وتخزين التوكن في `localStorage` +- `src/components/MapRouteBuilder.vue` — منشئ خطوط بخريطة Leaflet (نقر لإضافة + محطة بالترتيب، سحب لتعديل الموقع) — لا يتطلب مفتاح API (OpenStreetMap) +- `src/views/RouteEditorView.vue` — محرر خط كامل (بيانات + محطات + جداول) +- الأيام في الجداول الزمنية: قناع بت (`bit0=أحد … bit6=سبت`، 62=الأحد–الخميس) + +## ملاحظات أمنية + +- لا يوجد أي اتصال مباشر بقواعد main/ride/tracking — فقط `siroTransitDb` عبر + الباك اند. +- التوكن يُحذف تلقائياً من `localStorage` عند استجابة 401 من الخادم. diff --git a/dashboards/transit-web/index.html b/dashboards/transit-web/index.html new file mode 100644 index 0000000..d7debea --- /dev/null +++ b/dashboards/transit-web/index.html @@ -0,0 +1,19 @@ + + + + + + مواصلاتي — لوحة المؤسسة + + + + + + +
+ + + diff --git a/dashboards/transit-web/package-lock.json b/dashboards/transit-web/package-lock.json new file mode 100644 index 0000000..b42f1bc --- /dev/null +++ b/dashboards/transit-web/package-lock.json @@ -0,0 +1,1203 @@ +{ + "name": "transit-dashboard", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "transit-dashboard", + "version": "1.0.0", + "dependencies": { + "leaflet": "^1.9.4", + "pinia": "^2.1.7", + "vue": "^3.4.21", + "vue-router": "^4.3.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.4", + "vite": "^5.2.8" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.39.tgz", + "integrity": "sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw==", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/shared": "3.5.39", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.39.tgz", + "integrity": "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg==", + "dependencies": { + "@vue/compiler-core": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.39.tgz", + "integrity": "sha512-d0ki86iOyN8LoZPBmk5SJWNwHP19CnDDCfuo//+2WJa2g5Ke0Jay983PIBIcSSzldC68I8DrD5GrHV3OSDfodg==", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/compiler-core": "3.5.39", + "@vue/compiler-dom": "3.5.39", + "@vue/compiler-ssr": "3.5.39", + "@vue/shared": "3.5.39", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.15", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.39.tgz", + "integrity": "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw==", + "dependencies": { + "@vue/compiler-dom": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==" + }, + "node_modules/@vue/reactivity": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.39.tgz", + "integrity": "sha512-TpsuBJ9gGlZa5d23XcM2y8EXanz9dZeVDQBXRwzy46ItgvM+rWpzs+UVM0wcRLxGvcav0HE5jz2gNL53xlRAog==", + "dependencies": { + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.39.tgz", + "integrity": "sha512-9GLtNyRvPAUMbX+7ono0RC2j0guo2LXVi8LvcmAooImACUKm0oFf0jjwbX8/H0AE/t1nxhAkn8RSl9PMCzzxZw==", + "dependencies": { + "@vue/reactivity": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.39.tgz", + "integrity": "sha512-7Y6aAGboKcXAZ3ECuUy7RrS5yy2r47dhTp2SKaJmYxjopImaVFaNa5Ne66NwGovsrxVAl5S5rwc7m22UG7Lmww==", + "dependencies": { + "@vue/reactivity": "3.5.39", + "@vue/runtime-core": "3.5.39", + "@vue/shared": "3.5.39", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.39.tgz", + "integrity": "sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw==", + "dependencies": { + "@vue/compiler-ssr": "3.5.39", + "@vue/shared": "3.5.39" + }, + "peerDependencies": { + "vue": "3.5.39" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.39.tgz", + "integrity": "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA==" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "node_modules/pinia": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz", + "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==", + "dependencies": { + "@vue/devtools-api": "^6.6.3", + "vue-demi": "^0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/postcss": { + "version": "8.5.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.39.tgz", + "integrity": "sha512-xmZCYabFGcirU8r0fTuvl/LICc1OU620rnqepaJDL/a141ZigkG7AyaxQLdqJ02ZRYzWe6YPaDHeQx7MfknQfA==", + "dependencies": { + "@vue/compiler-dom": "3.5.39", + "@vue/compiler-sfc": "3.5.39", + "@vue/runtime-dom": "3.5.39", + "@vue/server-renderer": "3.5.39", + "@vue/shared": "3.5.39" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + } + } +} diff --git a/dashboards/transit-web/package.json b/dashboards/transit-web/package.json new file mode 100644 index 0000000..b00b7ba --- /dev/null +++ b/dashboards/transit-web/package.json @@ -0,0 +1,21 @@ +{ + "name": "transit-dashboard", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "vue": "^3.4.21", + "vue-router": "^4.3.0", + "pinia": "^2.1.7", + "leaflet": "^1.9.4" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.4", + "vite": "^5.2.8" + } +} diff --git a/dashboards/transit-web/src/App.vue b/dashboards/transit-web/src/App.vue new file mode 100644 index 0000000..98240ae --- /dev/null +++ b/dashboards/transit-web/src/App.vue @@ -0,0 +1,3 @@ + diff --git a/dashboards/transit-web/src/api/client.js b/dashboards/transit-web/src/api/client.js new file mode 100644 index 0000000..d1bac09 --- /dev/null +++ b/dashboards/transit-web/src/api/client.js @@ -0,0 +1,63 @@ +// api/client.js — عميل HTTP رفيع لبوابة transit/connect_admin.php +// كل الردود بنمط {status:'success'|'failure', message: ...} + +const API_BASE = import.meta.env.VITE_API_BASE || 'http://localhost:4020/transit' +const TOKEN_KEY = 'transit_admin_token' + +export function getToken() { + return localStorage.getItem(TOKEN_KEY) || '' +} +export function setToken(token) { + if (token) localStorage.setItem(TOKEN_KEY, token) + else localStorage.removeItem(TOKEN_KEY) +} + +class ApiError extends Error { + constructor(message, status) { + super(message) + this.status = status + } +} + +async function request(path, body, { isForm = false } = {}) { + const headers = {} + const token = getToken() + if (token) headers['Authorization'] = `Bearer ${token}` + + let payload + if (isForm) { + payload = body // already FormData + } else { + headers['Content-Type'] = 'application/json' + payload = JSON.stringify(body || {}) + } + + let res + try { + res = await fetch(`${API_BASE}/${path}`, { method: 'POST', headers, body: payload }) + } catch (e) { + throw new ApiError('تعذّر الاتصال بالخادم — تحقق من اتصالك بالإنترنت', 0) + } + + if (res.status === 401) { + setToken(null) + throw new ApiError('انتهت الجلسة — سجّل الدخول مجدداً', 401) + } + + let json + try { + json = await res.json() + } catch (e) { + throw new ApiError('استجابة غير صالحة من الخادم', res.status) + } + + if (json.status !== 'success') { + const msg = typeof json.message === 'string' ? json.message : 'حدث خطأ، حاول مجدداً' + throw new ApiError(msg, res.status) + } + + return json.message +} + +export const api = { request } +export { ApiError } diff --git a/dashboards/transit-web/src/api/transit.js b/dashboards/transit-web/src/api/transit.js new file mode 100644 index 0000000..b80b775 --- /dev/null +++ b/dashboards/transit-web/src/api/transit.js @@ -0,0 +1,54 @@ +// api/transit.js — كل نداءات backend/transit/* (بوابة connect_admin.php) +import { api } from './client' + +// ── الدخول ────────────────────────────────────────────── +export const loginRequest = (phone) => api.request('admin/login_request.php', { phone }) +export const loginVerify = (phone, otp) => api.request('admin/login_verify.php', { phone, otp }) +export const logout = () => api.request('admin/logout.php', {}) + +// ── لوحة اليوم ────────────────────────────────────────── +export const getDashboard = () => api.request('admin/dashboard.php', {}) + +// ── الأسطول: مركبات ───────────────────────────────────── +export const listVehicles = () => api.request('vehicle/list.php', {}) +export const addVehicle = (v) => api.request('vehicle/add.php', v) +export const updateVehicle = (v) => api.request('vehicle/update.php', v) +export const toggleVehicle = (vehicle_id, is_active) => + api.request('vehicle/toggle.php', { vehicle_id, is_active: is_active ? 1 : 0 }) + +// ── الأسطول: سائقون ────────────────────────────────────── +export const listDrivers = (status = 'all') => api.request('driver/list.php', { status }) +export const inviteDriver = (d) => api.request('driver/invite.php', d) +export const suspendDriver = (driver_id, action) => api.request('driver/suspend.php', { driver_id, action }) + +// ── الخطوط ────────────────────────────────────────────── +export const listRoutes = (status = 'all') => api.request('route/list.php', { status }) +export const getRoute = (route_id) => api.request('route/get.php', { route_id }) +export const addRoute = (payload) => + api.request('route/add.php', { ...payload, stops: JSON.stringify(payload.stops || []) }) +export const updateRoute = (payload) => + api.request('route/update.php', { ...payload, stops: JSON.stringify(payload.stops || []) }) + +// ── الجداول ───────────────────────────────────────────── +export const listSchedules = (route_id) => api.request('schedule/list.php', { route_id }) +export const addSchedule = (s) => api.request('schedule/add.php', s) +export const deleteSchedule = (schedule_id) => api.request('schedule/delete.php', { schedule_id }) + +// ── الطلاب / الأعضاء ──────────────────────────────────── +export const listEnrollments = (status = 'all', page = 1) => + api.request('enrollment/list.php', { status, page }) +export const approveEnrollment = (enrollment_id, action, expires_at) => + api.request('enrollment/approve.php', { enrollment_id, action, expires_at }) +export const listRosters = () => api.request('enrollment/rosters_list.php', {}) +export const importRoster = (file, semester) => { + const fd = new FormData() + fd.append('file', file) + if (semester) fd.append('semester', semester) + return api.request('enrollment/import_roster.php', fd, { isForm: true }) +} + +// ── الرحلات ───────────────────────────────────────────── +export const cancelTrip = (trip_id, reason) => api.request('trip/cancel.php', { trip_id, reason }) + +// ── الإعلانات ─────────────────────────────────────────── +export const sendBroadcast = (b) => api.request('broadcast/send.php', b) diff --git a/dashboards/transit-web/src/components/AppShell.vue b/dashboards/transit-web/src/components/AppShell.vue new file mode 100644 index 0000000..a70856d --- /dev/null +++ b/dashboards/transit-web/src/components/AppShell.vue @@ -0,0 +1,103 @@ + + + + + diff --git a/dashboards/transit-web/src/components/ConfirmDialog.vue b/dashboards/transit-web/src/components/ConfirmDialog.vue new file mode 100644 index 0000000..56a7409 --- /dev/null +++ b/dashboards/transit-web/src/components/ConfirmDialog.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/dashboards/transit-web/src/components/DayMaskPicker.vue b/dashboards/transit-web/src/components/DayMaskPicker.vue new file mode 100644 index 0000000..6314d6d --- /dev/null +++ b/dashboards/transit-web/src/components/DayMaskPicker.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/dashboards/transit-web/src/components/MapRouteBuilder.vue b/dashboards/transit-web/src/components/MapRouteBuilder.vue new file mode 100644 index 0000000..504e9ee --- /dev/null +++ b/dashboards/transit-web/src/components/MapRouteBuilder.vue @@ -0,0 +1,110 @@ + + + + + + + diff --git a/dashboards/transit-web/src/components/StatCard.vue b/dashboards/transit-web/src/components/StatCard.vue new file mode 100644 index 0000000..73febfc --- /dev/null +++ b/dashboards/transit-web/src/components/StatCard.vue @@ -0,0 +1,23 @@ + + + + + diff --git a/dashboards/transit-web/src/main.js b/dashboards/transit-web/src/main.js new file mode 100644 index 0000000..6d31d5f --- /dev/null +++ b/dashboards/transit-web/src/main.js @@ -0,0 +1,7 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import App from './App.vue' +import router from './router' +import './styles/main.css' + +createApp(App).use(createPinia()).use(router).mount('#app') diff --git a/dashboards/transit-web/src/router/index.js b/dashboards/transit-web/src/router/index.js new file mode 100644 index 0000000..b3b82ac --- /dev/null +++ b/dashboards/transit-web/src/router/index.js @@ -0,0 +1,32 @@ +import { createRouter, createWebHistory } from 'vue-router' +import { useAuthStore } from '../stores/auth' + +const routes = [ + { path: '/login', name: 'login', component: () => import('../views/LoginView.vue'), meta: { public: true } }, + { + path: '/', + component: () => import('../components/AppShell.vue'), + children: [ + { path: '', redirect: '/dashboard' }, + { path: 'dashboard', name: 'dashboard', component: () => import('../views/DashboardView.vue') }, + { path: 'fleet', name: 'fleet', component: () => import('../views/FleetView.vue') }, + { path: 'routes', name: 'routes', component: () => import('../views/RoutesView.vue') }, + { path: 'routes/:id', name: 'route-editor', component: () => import('../views/RouteEditorView.vue'), props: true }, + { path: 'routes/new', name: 'route-new', component: () => import('../views/RouteEditorView.vue') }, + { path: 'students', name: 'students', component: () => import('../views/StudentsView.vue') }, + { path: 'broadcasts', name: 'broadcasts', component: () => import('../views/BroadcastsView.vue') }, + ], + }, + { path: '/:pathMatch(.*)*', redirect: '/dashboard' }, +] + +const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes }) + +router.beforeEach((to) => { + const auth = useAuthStore() + if (!to.meta.public && !auth.isLoggedIn) return '/login' + if (to.name === 'login' && auth.isLoggedIn) return '/dashboard' + return true +}) + +export default router diff --git a/dashboards/transit-web/src/stores/auth.js b/dashboards/transit-web/src/stores/auth.js new file mode 100644 index 0000000..573a4de --- /dev/null +++ b/dashboards/transit-web/src/stores/auth.js @@ -0,0 +1,35 @@ +import { defineStore } from 'pinia' +import { getToken, setToken } from '../api/client' +import * as transitApi from '../api/transit' + +export const useAuthStore = defineStore('auth', { + state: () => ({ + token: getToken(), + admin: JSON.parse(localStorage.getItem('transit_admin_profile') || 'null'), + org: JSON.parse(localStorage.getItem('transit_admin_org') || 'null'), + }), + getters: { + isLoggedIn: (state) => !!state.token, + }, + actions: { + async verifyOtp(phone, otp) { + const res = await transitApi.loginVerify(phone, otp) + this.token = res.token + this.admin = res.admin + this.org = res.org + setToken(res.token) + localStorage.setItem('transit_admin_profile', JSON.stringify(res.admin)) + localStorage.setItem('transit_admin_org', JSON.stringify(res.org)) + return res + }, + async logout() { + try { await transitApi.logout() } catch (e) { /* الجلسة قد تكون منتهية أصلاً */ } + this.token = '' + this.admin = null + this.org = null + setToken(null) + localStorage.removeItem('transit_admin_profile') + localStorage.removeItem('transit_admin_org') + }, + }, +}) diff --git a/dashboards/transit-web/src/styles/main.css b/dashboards/transit-web/src/styles/main.css new file mode 100644 index 0000000..c8c2b87 --- /dev/null +++ b/dashboards/transit-web/src/styles/main.css @@ -0,0 +1,168 @@ +/* ============================================================ + مواصلاتي — لوحة المؤسسة: نظام تصميم + هوية هادئة وواثقة لأداة تشغيلية يومية (ليست صفحة تسويقية) + ============================================================ */ + +:root { + --bg: #F4F6F9; + --surface: #FFFFFF; + --surface-2: #EAEEF3; + --ink: #101826; + --ink-soft: #4A5568; + --ink-faint: #8896A6; + --line: #E1E6ED; + --line-strong: #C9D1DD; + + --brand: #0E7C86; /* أزرق-تيل الميناء — هوية اللوحة الخاصة، مختلفة عن سيرو الأساسي */ + --brand-deep: #0A5F67; + --brand-soft: #E1F2F3; + + --good: #1F9D6B; + --good-soft: #E3F6EC; + --warn: #C9821D; + --warn-soft: #FBF0DD; + --bad: #C6473D; + --bad-soft: #FAE6E3; + --info: #4A6FA5; + --info-soft: #E7EDF7; + + --shadow-sm: 0 1px 2px rgba(16,24,38,.05); + --shadow-md: 0 4px 16px rgba(16,24,38,.08); + --shadow-lg: 0 12px 32px rgba(16,24,38,.14); + --r-sm: 8px; + --r-md: 12px; + --r-lg: 16px; +} + +@media (prefers-color-scheme: dark) { + :root { + --bg: #0C1118; + --surface: #141B25; + --surface-2: #1B2430; + --ink: #EAF0F6; + --ink-soft: #9DAAB9; + --ink-faint: #64707F; + --line: #253140; + --line-strong: #334154; + + --brand: #29B6C2; + --brand-deep: #1D8E97; + --brand-soft: #102A2C; + + --good: #3FC489; + --good-soft: #10281F; + --warn: #E3A53D; + --warn-soft: #2E2410; + --bad: #E5695D; + --bad-soft: #2E1714; + --info: #7C9CCF; + --info-soft: #16202F; + + --shadow-sm: 0 1px 2px rgba(0,0,0,.3); + --shadow-md: 0 6px 20px rgba(0,0,0,.35); + --shadow-lg: 0 16px 40px rgba(0,0,0,.5); + } +} + +:root[data-theme="light"] { + --bg: #F4F6F9; --surface: #FFFFFF; --surface-2: #EAEEF3; --ink: #101826; + --ink-soft: #4A5568; --ink-faint: #8896A6; --line: #E1E6ED; --line-strong: #C9D1DD; + --brand: #0E7C86; --brand-deep: #0A5F67; --brand-soft: #E1F2F3; + --good: #1F9D6B; --good-soft: #E3F6EC; --warn: #C9821D; --warn-soft: #FBF0DD; + --bad: #C6473D; --bad-soft: #FAE6E3; --info: #4A6FA5; --info-soft: #E7EDF7; +} +:root[data-theme="dark"] { + --bg: #0C1118; --surface: #141B25; --surface-2: #1B2430; --ink: #EAF0F6; + --ink-soft: #9DAAB9; --ink-faint: #64707F; --line: #253140; --line-strong: #334154; + --brand: #29B6C2; --brand-deep: #1D8E97; --brand-soft: #102A2C; + --good: #3FC489; --good-soft: #10281F; --warn: #E3A53D; --warn-soft: #2E2410; + --bad: #E5695D; --bad-soft: #2E1714; --info: #7C9CCF; --info-soft: #16202F; +} + +* { box-sizing: border-box; } +html, body, #app { height: 100%; } +body { + margin: 0; + background: var(--bg); + color: var(--ink); + font-family: "Cairo", "Segoe UI", Tahoma, sans-serif; + direction: rtl; + -webkit-font-smoothing: antialiased; +} +.num { font-family: "IBM Plex Mono", ui-monospace, monospace; font-variant-numeric: tabular-nums; direction: ltr; unicode-bidi: isolate; } + +a { color: inherit; } +button { font-family: inherit; } + +h1, h2, h3, h4 { margin: 0; text-wrap: balance; font-weight: 700; } + +/* ─── Buttons ──────────────────────────────────────────── */ +.btn { + display: inline-flex; align-items: center; justify-content: center; gap: 8px; + font-family: inherit; font-size: 14px; font-weight: 600; + padding: 10px 18px; border-radius: var(--r-sm); border: 1px solid transparent; + cursor: pointer; transition: background .15s ease, border-color .15s ease, opacity .15s ease; + white-space: nowrap; +} +.btn:disabled { opacity: .5; cursor: not-allowed; } +.btn-primary { background: var(--brand); color: white; } +.btn-primary:hover:not(:disabled) { background: var(--brand-deep); } +.btn-ghost { background: transparent; border-color: var(--line-strong); color: var(--ink); } +.btn-ghost:hover:not(:disabled) { background: var(--surface-2); } +.btn-danger { background: var(--bad); color: white; } +.btn-danger:hover:not(:disabled) { opacity: .88; } +.btn-sm { padding: 6px 12px; font-size: 13px; } + +/* ─── Form controls ────────────────────────────────────── */ +.field { display: flex; flex-direction: column; gap: 6px; } +.field label { font-size: 13px; font-weight: 600; color: var(--ink-soft); } +.input, select.input, textarea.input { + font-family: inherit; font-size: 14.5px; color: var(--ink); + background: var(--surface); border: 1px solid var(--line-strong); border-radius: var(--r-sm); + padding: 10px 12px; outline: none; transition: border-color .15s ease; +} +.input:focus { border-color: var(--brand); } +.input::placeholder { color: var(--ink-faint); } + +/* ─── Cards / surfaces ─────────────────────────────────── */ +.card { + background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg); + box-shadow: var(--shadow-sm); +} +.card-pad { padding: 20px; } + +/* ─── Pills / badges ───────────────────────────────────── */ +.pill { + display: inline-flex; align-items: center; gap: 6px; + font-size: 12px; font-weight: 700; padding: 4px 11px; border-radius: 100px; + white-space: nowrap; +} +.pill-good { color: var(--good); background: var(--good-soft); } +.pill-warn { color: var(--warn); background: var(--warn-soft); } +.pill-bad { color: var(--bad); background: var(--bad-soft); } +.pill-info { color: var(--info); background: var(--info-soft); } +.pill-neutral { color: var(--ink-soft); background: var(--surface-2); } + +/* ─── Tables ────────────────────────────────────────────── */ +.table-wrap { overflow-x: auto; border-radius: var(--r-md); border: 1px solid var(--line); } +table.tbl { width: 100%; border-collapse: collapse; font-size: 14px; background: var(--surface); } +table.tbl th { + text-align: right; font-size: 12px; font-weight: 700; color: var(--ink-faint); + text-transform: uppercase; letter-spacing: .04em; padding: 12px 16px; + background: var(--surface-2); border-bottom: 1px solid var(--line); +} +table.tbl td { padding: 13px 16px; border-bottom: 1px solid var(--line); color: var(--ink); } +table.tbl tr:last-child td { border-bottom: none; } +table.tbl tbody tr:hover { background: var(--surface-2); } + +/* ─── Scrollbar (subtle) ───────────────────────────────── */ +::-webkit-scrollbar { width: 10px; height: 10px; } +::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 100px; } +::-webkit-scrollbar-track { background: transparent; } + +/* ─── Focus visibility ─────────────────────────────────── */ +:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; } + +@media (prefers-reduced-motion: reduce) { + * { transition: none !important; animation: none !important; } +} diff --git a/dashboards/transit-web/src/utils/polyline.js b/dashboards/transit-web/src/utils/polyline.js new file mode 100644 index 0000000..8d70ed1 --- /dev/null +++ b/dashboards/transit-web/src/utils/polyline.js @@ -0,0 +1,40 @@ +// utils/polyline.js — ترميز Google Polyline القياسي (يطابق PolylineUtils.decode في تطبيقات الفلاتر) + +export function encodePolyline(points) { + let output = '' + let prevLat = 0 + let prevLng = 0 + + for (const [lat, lng] of points) { + const lat5 = Math.round(lat * 1e5) + const lng5 = Math.round(lng * 1e5) + output += encodeValue(lat5 - prevLat) + output += encodeValue(lng5 - prevLng) + prevLat = lat5 + prevLng = lng5 + } + return output +} + +function encodeValue(value) { + let v = value < 0 ? ~(value << 1) : value << 1 + let output = '' + while (v >= 0x20) { + output += String.fromCharCode((0x20 | (v & 0x1f)) + 63) + v >>= 5 + } + output += String.fromCharCode(v + 63) + return output +} + +// هافرسين — مسافة بالمتر بين نقطتين +export function haversineMeters(lat1, lng1, lat2, lng2) { + const R = 6371000 + const dLat = deg2rad(lat2 - lat1) + const dLng = deg2rad(lng2 - lng1) + const a = + Math.sin(dLat / 2) ** 2 + + Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.sin(dLng / 2) ** 2 + return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) +} +function deg2rad(d) { return (d * Math.PI) / 180 } diff --git a/dashboards/transit-web/src/views/BroadcastsView.vue b/dashboards/transit-web/src/views/BroadcastsView.vue new file mode 100644 index 0000000..31aaf43 --- /dev/null +++ b/dashboards/transit-web/src/views/BroadcastsView.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/dashboards/transit-web/src/views/DashboardView.vue b/dashboards/transit-web/src/views/DashboardView.vue new file mode 100644 index 0000000..f9dcf68 --- /dev/null +++ b/dashboards/transit-web/src/views/DashboardView.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/dashboards/transit-web/src/views/FleetView.vue b/dashboards/transit-web/src/views/FleetView.vue new file mode 100644 index 0000000..db9a7b4 --- /dev/null +++ b/dashboards/transit-web/src/views/FleetView.vue @@ -0,0 +1,207 @@ + + + + + diff --git a/dashboards/transit-web/src/views/LoginView.vue b/dashboards/transit-web/src/views/LoginView.vue new file mode 100644 index 0000000..ca17a3d --- /dev/null +++ b/dashboards/transit-web/src/views/LoginView.vue @@ -0,0 +1,102 @@ + + + + + diff --git a/dashboards/transit-web/src/views/RouteEditorView.vue b/dashboards/transit-web/src/views/RouteEditorView.vue new file mode 100644 index 0000000..d20034c --- /dev/null +++ b/dashboards/transit-web/src/views/RouteEditorView.vue @@ -0,0 +1,293 @@ + + + + + diff --git a/dashboards/transit-web/src/views/RoutesView.vue b/dashboards/transit-web/src/views/RoutesView.vue new file mode 100644 index 0000000..19fbf84 --- /dev/null +++ b/dashboards/transit-web/src/views/RoutesView.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/dashboards/transit-web/src/views/StudentsView.vue b/dashboards/transit-web/src/views/StudentsView.vue new file mode 100644 index 0000000..6f3143a --- /dev/null +++ b/dashboards/transit-web/src/views/StudentsView.vue @@ -0,0 +1,171 @@ + + + + + diff --git a/dashboards/transit-web/vite.config.js b/dashboards/transit-web/vite.config.js new file mode 100644 index 0000000..39d2539 --- /dev/null +++ b/dashboards/transit-web/vite.config.js @@ -0,0 +1,18 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +import fs from 'fs' +import { resolve } from 'path' + +// تحديد مسار ملف .env ديناميكياً (حسب السيرفر أو الجهاز المحلي) +let envDirectory = '../backend' // المسار المحلي الافتراضي +if (fs.existsSync(resolve(__dirname, '../../../.env'))) { + envDirectory = '../../..' // المسار الخاص بـ CloudPanel +} + +export default defineConfig({ + plugins: [vue()], + base: '/transit/', + envDir: envDirectory, + server: { port: 5183 }, +})