Praveen-K-0503 commited on
Commit ·
8c2f1b1
1
Parent(s): 3a66575
config: wire HuggingFace Space praveendatascience/Crowd-Detection
Browse files- Set HF_WEIGHTS_REPO to praveendatascience/crowd-counting-weights
- Add HF Space URL to ALLOWED_ORIGINS in api.py
- Set VITE_API_URL to https://praveendatascience-crowd-detection.hf.space in .env.production
This view is limited to 50 files because it contains too many changes. See raw diff
- api.py +1 -1
- civic-platform/.env.example +0 -6
- civic-platform/.gitignore +0 -7
- civic-platform/README.md +0 -119
- civic-platform/ai/README.md +0 -15
- civic-platform/ai/schemas/civic-image-record.example.json +0 -11
- civic-platform/ai/schemas/complaint-text-record.example.json +0 -12
- civic-platform/ai/training/README.md +0 -73
- civic-platform/ai/training/civic-image-labels.md +0 -19
- civic-platform/app/api/session/login/route.ts +0 -77
- civic-platform/app/api/session/logout/route.ts +0 -10
- civic-platform/app/api/session/register/route.ts +0 -83
- civic-platform/app/complaints/[id]/page.tsx +0 -264
- civic-platform/app/complaints/page.tsx +0 -217
- civic-platform/app/dashboard/analytics/page.tsx +0 -184
- civic-platform/app/dashboard/complaints/[id]/page.tsx +0 -383
- civic-platform/app/dashboard/complaints/page.tsx +0 -113
- civic-platform/app/dashboard/page.tsx +0 -257
- civic-platform/app/emergency/page.tsx +0 -176
- civic-platform/app/globals.css +0 -61
- civic-platform/app/layout.tsx +0 -19
- civic-platform/app/login/page.tsx +0 -40
- civic-platform/app/map/page.tsx +0 -59
- civic-platform/app/notifications/page.tsx +0 -35
- civic-platform/app/page.tsx +0 -15
- civic-platform/app/report/location/page.tsx +0 -170
- civic-platform/app/report/page.tsx +0 -27
- civic-platform/app/report/review/page.tsx +0 -165
- civic-platform/app/report/success/page.tsx +0 -173
- civic-platform/app/tasks/page.tsx +0 -35
- civic-platform/app/template.tsx +0 -16
- civic-platform/backend/.env.example +0 -12
- civic-platform/backend/README.md +0 -38
- civic-platform/backend/backend-dev.log +0 -5
- civic-platform/backend/backend-run.log +0 -2
- civic-platform/backend/backend.crash.log +0 -0
- civic-platform/backend/backend.detached.log +0 -1
- civic-platform/backend/backend.dev.log +0 -0
- civic-platform/backend/package-lock.json +0 -1817
- civic-platform/backend/package.json +0 -30
- civic-platform/backend/server.err.log +0 -0
- civic-platform/backend/server.out.log +0 -0
- civic-platform/backend/sql/001_initial_schema.sql +0 -246
- civic-platform/backend/sql/002_seed_core_data.sql +0 -133
- civic-platform/backend/sql/003_performance_indexes.sql +0 -32
- civic-platform/backend/sql/README.md +0 -22
- civic-platform/backend/src/app.ts +0 -48
- civic-platform/backend/src/config/env.ts +0 -28
- civic-platform/backend/src/db/pool.ts +0 -11
- civic-platform/backend/src/lib/audit.ts +0 -29
api.py
CHANGED
|
@@ -27,7 +27,7 @@ app = FastAPI()
|
|
| 27 |
# The ALLOWED_ORIGINS env var can be set on HF Spaces to your exact Vercel URL.
|
| 28 |
_raw_origins = os.environ.get(
|
| 29 |
"ALLOWED_ORIGINS",
|
| 30 |
-
"http://localhost:5173,http://127.0.0.1:5173,http://localhost:4173,http://127.0.0.1:4173,http://localhost:3000,http://127.0.0.1:3000"
|
| 31 |
)
|
| 32 |
ALLOWED_ORIGINS = [o.strip() for o in _raw_origins.split(",") if o.strip()]
|
| 33 |
|
|
|
|
| 27 |
# The ALLOWED_ORIGINS env var can be set on HF Spaces to your exact Vercel URL.
|
| 28 |
_raw_origins = os.environ.get(
|
| 29 |
"ALLOWED_ORIGINS",
|
| 30 |
+
"http://localhost:5173,http://127.0.0.1:5173,http://localhost:4173,http://127.0.0.1:4173,http://localhost:3000,http://127.0.0.1:3000,https://praveendatascience-crowd-detection.hf.space"
|
| 31 |
)
|
| 32 |
ALLOWED_ORIGINS = [o.strip() for o in _raw_origins.split(",") if o.strip()]
|
| 33 |
|
civic-platform/.env.example
DELETED
|
@@ -1,6 +0,0 @@
|
|
| 1 |
-
NEXT_PUBLIC_API_BASE_URL=http://localhost:4000/api
|
| 2 |
-
|
| 3 |
-
# Optional. If set, the public map uses Mapbox streets tiles.
|
| 4 |
-
# Leave blank to use free OpenStreetMap tiles.
|
| 5 |
-
MAPBOX_TOKEN=
|
| 6 |
-
NEXT_PUBLIC_MAPBOX_TOKEN=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/.gitignore
DELETED
|
@@ -1,7 +0,0 @@
|
|
| 1 |
-
node_modules/
|
| 2 |
-
.next/
|
| 3 |
-
dist/
|
| 4 |
-
.env
|
| 5 |
-
.env.local
|
| 6 |
-
backend/.env
|
| 7 |
-
backend/dist/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/README.md
DELETED
|
@@ -1,119 +0,0 @@
|
|
| 1 |
-
# Civic Platform
|
| 2 |
-
|
| 3 |
-
A mobile-first civic issue reporting and resolution platform for day-to-day public problems and emergency incidents.
|
| 4 |
-
|
| 5 |
-
## Vision
|
| 6 |
-
|
| 7 |
-
Citizens report issues with photo, video, voice, text, and geo-location. The system classifies the complaint, assigns priority, routes it to the correct government department, and tracks the full lifecycle from submission to closure.
|
| 8 |
-
|
| 9 |
-
## Core goals
|
| 10 |
-
|
| 11 |
-
- Make complaint reporting simple and fast
|
| 12 |
-
- Support day-to-day civic domains and emergency domains
|
| 13 |
-
- Route complaints to the correct department with priority
|
| 14 |
-
- Provide transparent status tracking to citizens
|
| 15 |
-
- Give officials a fast dashboard for triage, assignment, and monitoring
|
| 16 |
-
- Keep the system scalable, low-latency, and future-ready
|
| 17 |
-
|
| 18 |
-
## Main domains
|
| 19 |
-
|
| 20 |
-
1. Roads and Transportation
|
| 21 |
-
2. Sanitation and Waste Management
|
| 22 |
-
3. Water Supply, Sewerage, and Drainage
|
| 23 |
-
4. Street Lighting and Electrical Infrastructure
|
| 24 |
-
5. Public Infrastructure and Amenities
|
| 25 |
-
6. Environment and Public Health
|
| 26 |
-
|
| 27 |
-
## Emergency domains
|
| 28 |
-
|
| 29 |
-
1. Fire Emergencies
|
| 30 |
-
2. Flood and Water Disaster
|
| 31 |
-
3. Structural and Infrastructure Hazard
|
| 32 |
-
4. Electrical Hazard
|
| 33 |
-
5. Disaster and Rescue
|
| 34 |
-
|
| 35 |
-
## Complaint lifecycle
|
| 36 |
-
|
| 37 |
-
1. Draft
|
| 38 |
-
2. Submitted
|
| 39 |
-
3. Validated
|
| 40 |
-
4. Classified
|
| 41 |
-
5. Prioritized
|
| 42 |
-
6. Assigned
|
| 43 |
-
7. Accepted
|
| 44 |
-
8. In Progress
|
| 45 |
-
9. Resolved
|
| 46 |
-
10. Citizen Verified
|
| 47 |
-
11. Closed
|
| 48 |
-
|
| 49 |
-
Additional states:
|
| 50 |
-
|
| 51 |
-
- Escalated
|
| 52 |
-
- Reopened
|
| 53 |
-
- Duplicate
|
| 54 |
-
- Rejected
|
| 55 |
-
- On Hold
|
| 56 |
-
|
| 57 |
-
## Proposed stack
|
| 58 |
-
|
| 59 |
-
### Frontend
|
| 60 |
-
|
| 61 |
-
- Next.js
|
| 62 |
-
- TypeScript
|
| 63 |
-
- Tailwind CSS
|
| 64 |
-
- shadcn/ui
|
| 65 |
-
- Framer Motion
|
| 66 |
-
- Leaflet with OpenStreetMap
|
| 67 |
-
|
| 68 |
-
### Platform
|
| 69 |
-
|
| 70 |
-
- Supabase Auth
|
| 71 |
-
- Supabase Postgres
|
| 72 |
-
- PostGIS
|
| 73 |
-
- Supabase Storage
|
| 74 |
-
- Supabase Realtime
|
| 75 |
-
- Supabase Edge Functions
|
| 76 |
-
|
| 77 |
-
### AI
|
| 78 |
-
|
| 79 |
-
- Whisper for speech-to-text
|
| 80 |
-
- DistilBERT or similar lightweight text classifier
|
| 81 |
-
- YOLOv8n or MobileNet for image classification
|
| 82 |
-
|
| 83 |
-
## App areas
|
| 84 |
-
|
| 85 |
-
- Citizen portal
|
| 86 |
-
- Department dashboard
|
| 87 |
-
- Field officer workspace
|
| 88 |
-
- Super admin configuration
|
| 89 |
-
- Analytics and SLA dashboard
|
| 90 |
-
|
| 91 |
-
## Folder plan
|
| 92 |
-
|
| 93 |
-
- `app/` Next.js App Router pages
|
| 94 |
-
- `components/` shared UI sections
|
| 95 |
-
- `lib/` config, types, and helpers
|
| 96 |
-
- `data/` static seed content used by the frontend
|
| 97 |
-
- `docs/` architecture, lifecycle, and page planning
|
| 98 |
-
|
| 99 |
-
## Planning docs
|
| 100 |
-
|
| 101 |
-
- `docs/PROJECT_BLUEPRINT.md`
|
| 102 |
-
- `docs/FRONTEND_PLAN.md`
|
| 103 |
-
- `docs/PAGE_MAP.md`
|
| 104 |
-
- `docs/ROLES_AND_PERMISSIONS.md`
|
| 105 |
-
- `docs/COMPLAINT_LIFECYCLE.md`
|
| 106 |
-
- `docs/DOMAIN_ROUTING_MAP.md`
|
| 107 |
-
- `docs/PRIORITY_SCORING.md`
|
| 108 |
-
- `docs/DATABASE_SCHEMA.md`
|
| 109 |
-
- `docs/BACKEND_STRUCTURE.md`
|
| 110 |
-
- `docs/LOCAL_DATABASE_SETUP.md`
|
| 111 |
-
|
| 112 |
-
## Next build steps
|
| 113 |
-
|
| 114 |
-
1. Finalize data model and routing rules
|
| 115 |
-
2. Build the design system and page structure
|
| 116 |
-
3. Implement citizen report flow
|
| 117 |
-
4. Add admin dashboard and complaint lifecycle updates
|
| 118 |
-
5. Connect Supabase auth, database, storage, and realtime
|
| 119 |
-
6. Add AI-assisted classification after the MVP workflow is stable
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/ai/README.md
DELETED
|
@@ -1,15 +0,0 @@
|
|
| 1 |
-
# CivicPulse AI Workspace
|
| 2 |
-
|
| 3 |
-
This folder is reserved for model training assets, dataset schemas, experiments, and evaluation results.
|
| 4 |
-
|
| 5 |
-
Recommended subfolders:
|
| 6 |
-
|
| 7 |
-
- `datasets/text`
|
| 8 |
-
- `datasets/vision`
|
| 9 |
-
- `datasets/audio`
|
| 10 |
-
- `evals`
|
| 11 |
-
- `notebooks`
|
| 12 |
-
- `schemas`
|
| 13 |
-
|
| 14 |
-
The live product currently uses an assistive heuristic AI layer in the application code. This workspace is where model-backed training and evaluation can be added next.
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/ai/schemas/civic-image-record.example.json
DELETED
|
@@ -1,11 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"image": "path-or-huggingface-image-object",
|
| 3 |
-
"label": "Roads and Transportation",
|
| 4 |
-
"metadata": {
|
| 5 |
-
"complaintCode": "CP-2026-000001",
|
| 6 |
-
"source": "citizen_upload",
|
| 7 |
-
"city": "Hyderabad",
|
| 8 |
-
"latitude": 17.385,
|
| 9 |
-
"longitude": 78.4867
|
| 10 |
-
}
|
| 11 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/ai/schemas/complaint-text-record.example.json
DELETED
|
@@ -1,12 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"title": "Streetlight not working near school gate",
|
| 3 |
-
"description": "Two streetlights are off near the main gate and the road is dark at night.",
|
| 4 |
-
"address_line": "School Road",
|
| 5 |
-
"landmark": "Near Government High School",
|
| 6 |
-
"city_name": "Sample City",
|
| 7 |
-
"state_name": "Sample State",
|
| 8 |
-
"domain": "Street Lighting and Electrical Infrastructure",
|
| 9 |
-
"sub_problem": "Broken streetlight",
|
| 10 |
-
"priority": "P2",
|
| 11 |
-
"is_emergency": false
|
| 12 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/ai/training/README.md
DELETED
|
@@ -1,73 +0,0 @@
|
|
| 1 |
-
# CivicPulse Vision Training Plan
|
| 2 |
-
|
| 3 |
-
The current app has a safe baseline for image evidence:
|
| 4 |
-
|
| 5 |
-
- It extracts GPS metadata from JPEG photos when available.
|
| 6 |
-
- It uses address search, GPS, or map pinning when photo GPS metadata is missing.
|
| 7 |
-
- It passes lightweight visual signals into the AI assistant when filenames contain civic issue hints.
|
| 8 |
-
|
| 9 |
-
For accurate image classification, train a real model on labeled civic images. A perfect model is not realistic, but a good production model is achievable with enough labeled examples and evaluation.
|
| 10 |
-
|
| 11 |
-
## Recommended Labels
|
| 12 |
-
|
| 13 |
-
Use the same project domains as labels:
|
| 14 |
-
|
| 15 |
-
- Roads and Transportation
|
| 16 |
-
- Sanitation and Waste Management
|
| 17 |
-
- Water Supply, Sewerage, and Drainage
|
| 18 |
-
- Street Lighting and Electrical Infrastructure
|
| 19 |
-
- Public Infrastructure and Amenities
|
| 20 |
-
- Environment and Public Health
|
| 21 |
-
- Fire Emergencies
|
| 22 |
-
- Flood and Water Disaster
|
| 23 |
-
- Structural and Infrastructure Hazard
|
| 24 |
-
- Electrical Hazard
|
| 25 |
-
- Disaster and Rescue
|
| 26 |
-
|
| 27 |
-
## Dataset Format
|
| 28 |
-
|
| 29 |
-
For Hugging Face image classification, publish a dataset with:
|
| 30 |
-
|
| 31 |
-
- `image`: image column
|
| 32 |
-
- `label`: one of the labels above
|
| 33 |
-
|
| 34 |
-
Example record:
|
| 35 |
-
|
| 36 |
-
[civic-image-record.example.json](/C:/Users/Praveen/Documents/New%20project/civic-platform/ai/schemas/civic-image-record.example.json)
|
| 37 |
-
|
| 38 |
-
## Training Recommendation
|
| 39 |
-
|
| 40 |
-
Start with image classification:
|
| 41 |
-
|
| 42 |
-
- Model: `timm/mobilenetv3_small_100.lamb_in1k` for fast iteration
|
| 43 |
-
- Upgrade: `timm/resnet50.a1_in1k` or `timm/vit_base_patch16_dinov3.lvd1689m` for better accuracy
|
| 44 |
-
- Target metric: validation accuracy and per-class recall
|
| 45 |
-
- Minimum data target: 300 to 500 images per label for a useful first model
|
| 46 |
-
- Better data target: 1,000+ images per label with different lighting, angles, weather, and phones
|
| 47 |
-
|
| 48 |
-
For localization such as detecting a pothole box inside the image, move to object detection later with D-FINE or RT-DETR.
|
| 49 |
-
|
| 50 |
-
## Deployment Path
|
| 51 |
-
|
| 52 |
-
1. Collect and label civic images from real reports.
|
| 53 |
-
2. Upload the dataset to Hugging Face Hub.
|
| 54 |
-
3. Validate the dataset format before training.
|
| 55 |
-
4. Fine-tune an image classifier using Hugging Face Jobs.
|
| 56 |
-
5. Push the trained model to the Hub.
|
| 57 |
-
6. Add a backend inference endpoint that calls the trained model and returns:
|
| 58 |
-
- predicted domain
|
| 59 |
-
- confidence
|
| 60 |
-
- top 3 alternatives
|
| 61 |
-
- model version
|
| 62 |
-
7. Keep the operator override flow because image models can be wrong.
|
| 63 |
-
|
| 64 |
-
## Important Note
|
| 65 |
-
|
| 66 |
-
Do not rely only on image classification. The best production result should combine:
|
| 67 |
-
|
| 68 |
-
- image prediction
|
| 69 |
-
- text/voice description
|
| 70 |
-
- issue domain chosen by the citizen
|
| 71 |
-
- geo-location and ward
|
| 72 |
-
- nearby duplicate complaints
|
| 73 |
-
- operator override
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/ai/training/civic-image-labels.md
DELETED
|
@@ -1,19 +0,0 @@
|
|
| 1 |
-
# CivicPulse Image Labels
|
| 2 |
-
|
| 3 |
-
Use these labels for the first image classification dataset.
|
| 4 |
-
|
| 5 |
-
| Label | Example visual cases |
|
| 6 |
-
|---|---|
|
| 7 |
-
| Roads and Transportation | potholes, broken roads, road blockage, damaged footpath, damaged divider |
|
| 8 |
-
| Sanitation and Waste Management | garbage piles, overflowing bins, illegal dumping, dirty streets |
|
| 9 |
-
| Water Supply, Sewerage, and Drainage | water leakage, sewage overflow, clogged drains, waterlogging |
|
| 10 |
-
| Street Lighting and Electrical Infrastructure | broken streetlights, damaged poles, exposed wires, transformer issues |
|
| 11 |
-
| Public Infrastructure and Amenities | open manholes, broken bus stops, damaged public toilets, park damage |
|
| 12 |
-
| Environment and Public Health | fallen trees, stagnant water, smoke, dead animals, mosquito breeding areas |
|
| 13 |
-
| Fire Emergencies | visible fire, heavy smoke, burning debris |
|
| 14 |
-
| Flood and Water Disaster | floodwater, severe rain overflow, storm damage |
|
| 15 |
-
| Structural and Infrastructure Hazard | wall cracks, building collapse signs, bridge damage |
|
| 16 |
-
| Electrical Hazard | live wires, transformer blast, major short circuit evidence |
|
| 17 |
-
| Disaster and Rescue | landslide, storm collapse, road-blocking tree fall, rescue hazard |
|
| 18 |
-
|
| 19 |
-
Labeling rule: if one image contains multiple issues, choose the most urgent visible issue and store secondary labels in metadata for future multi-label training.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/app/api/session/login/route.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
| 1 |
-
import { cookies } from "next/headers";
|
| 2 |
-
import { NextRequest, NextResponse } from "next/server";
|
| 3 |
-
import bcrypt from "bcryptjs";
|
| 4 |
-
import pool from "@/lib/db";
|
| 5 |
-
import { encodeSession, SESSION_COOKIE_NAME, type SessionUser } from "@/lib/session";
|
| 6 |
-
|
| 7 |
-
type UserRow = SessionUser & { passwordHash: string | null };
|
| 8 |
-
|
| 9 |
-
export async function POST(request: NextRequest) {
|
| 10 |
-
try {
|
| 11 |
-
const body = (await request.json()) as { email?: string; password?: string };
|
| 12 |
-
const email = body.email?.trim().toLowerCase() ?? "";
|
| 13 |
-
const password = body.password ?? "";
|
| 14 |
-
|
| 15 |
-
if (!email) {
|
| 16 |
-
return NextResponse.json({ error: "Email is required" }, { status: 400 });
|
| 17 |
-
}
|
| 18 |
-
if (!password) {
|
| 19 |
-
return NextResponse.json({ error: "Password is required" }, { status: 400 });
|
| 20 |
-
}
|
| 21 |
-
|
| 22 |
-
// Query users table joined with roles
|
| 23 |
-
const result = await pool.query<UserRow>(
|
| 24 |
-
`SELECT
|
| 25 |
-
u.id,
|
| 26 |
-
u.full_name AS "fullName",
|
| 27 |
-
u.email,
|
| 28 |
-
r.name AS role,
|
| 29 |
-
u.department_id AS "departmentId",
|
| 30 |
-
u.ward_id AS "wardId",
|
| 31 |
-
u.password_hash AS "passwordHash"
|
| 32 |
-
FROM users u
|
| 33 |
-
INNER JOIN roles r ON r.id = u.role_id
|
| 34 |
-
WHERE lower(u.email) = $1
|
| 35 |
-
AND u.is_active = TRUE
|
| 36 |
-
LIMIT 1`,
|
| 37 |
-
[email],
|
| 38 |
-
);
|
| 39 |
-
|
| 40 |
-
const user = result.rows[0] ?? null;
|
| 41 |
-
|
| 42 |
-
if (!user) {
|
| 43 |
-
return NextResponse.json({ error: "Invalid email or password" }, { status: 401 });
|
| 44 |
-
}
|
| 45 |
-
|
| 46 |
-
// Support both real bcrypt hashes AND the dev-placeholder-hash used in seed data
|
| 47 |
-
let passwordValid = false;
|
| 48 |
-
if (user.passwordHash === "dev-placeholder-hash" && password === "civicpulse123") {
|
| 49 |
-
passwordValid = true;
|
| 50 |
-
} else if (user.passwordHash) {
|
| 51 |
-
passwordValid = await bcrypt.compare(password, user.passwordHash);
|
| 52 |
-
}
|
| 53 |
-
|
| 54 |
-
if (!passwordValid) {
|
| 55 |
-
return NextResponse.json({ error: "Invalid email or password" }, { status: 401 });
|
| 56 |
-
}
|
| 57 |
-
|
| 58 |
-
const { passwordHash: _ph, ...sessionUser } = user;
|
| 59 |
-
const cookieStore = await cookies();
|
| 60 |
-
|
| 61 |
-
cookieStore.set(SESSION_COOKIE_NAME, encodeSession(sessionUser), {
|
| 62 |
-
httpOnly: true,
|
| 63 |
-
sameSite: "lax",
|
| 64 |
-
path: "/",
|
| 65 |
-
secure: process.env.NODE_ENV === "production",
|
| 66 |
-
maxAge: 60 * 60 * 12,
|
| 67 |
-
});
|
| 68 |
-
|
| 69 |
-
return NextResponse.json({ data: sessionUser });
|
| 70 |
-
} catch (error) {
|
| 71 |
-
console.error("[login] Error:", error);
|
| 72 |
-
return NextResponse.json(
|
| 73 |
-
{ error: error instanceof Error ? error.message : "Login failed" },
|
| 74 |
-
{ status: 500 },
|
| 75 |
-
);
|
| 76 |
-
}
|
| 77 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/app/api/session/logout/route.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
| 1 |
-
import { cookies } from "next/headers";
|
| 2 |
-
import { NextResponse } from "next/server";
|
| 3 |
-
import { SESSION_COOKIE_NAME } from "@/lib/session";
|
| 4 |
-
|
| 5 |
-
export async function POST() {
|
| 6 |
-
const cookieStore = await cookies();
|
| 7 |
-
cookieStore.delete(SESSION_COOKIE_NAME);
|
| 8 |
-
|
| 9 |
-
return NextResponse.json({ data: { ok: true } });
|
| 10 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/app/api/session/register/route.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
| 1 |
-
import { cookies } from "next/headers";
|
| 2 |
-
import { NextRequest, NextResponse } from "next/server";
|
| 3 |
-
import bcrypt from "bcryptjs";
|
| 4 |
-
import pool from "@/lib/db";
|
| 5 |
-
import { encodeSession, SESSION_COOKIE_NAME, type SessionUser } from "@/lib/session";
|
| 6 |
-
|
| 7 |
-
type UserRow = SessionUser & { passwordHash: string | null };
|
| 8 |
-
|
| 9 |
-
export async function POST(request: NextRequest) {
|
| 10 |
-
try {
|
| 11 |
-
const body = (await request.json()) as {
|
| 12 |
-
fullName?: string;
|
| 13 |
-
email?: string;
|
| 14 |
-
phone?: string;
|
| 15 |
-
password?: string;
|
| 16 |
-
};
|
| 17 |
-
|
| 18 |
-
const fullName = body.fullName?.trim() ?? "";
|
| 19 |
-
const email = body.email?.trim().toLowerCase() ?? "";
|
| 20 |
-
const phone = body.phone?.trim() || null;
|
| 21 |
-
const password = body.password ?? "";
|
| 22 |
-
|
| 23 |
-
// Validation
|
| 24 |
-
if (!fullName) {
|
| 25 |
-
return NextResponse.json({ error: "Full name is required" }, { status: 400 });
|
| 26 |
-
}
|
| 27 |
-
if (!email) {
|
| 28 |
-
return NextResponse.json({ error: "Email is required" }, { status: 400 });
|
| 29 |
-
}
|
| 30 |
-
if (!password || password.length < 8) {
|
| 31 |
-
return NextResponse.json({ error: "Password must be at least 8 characters" }, { status: 400 });
|
| 32 |
-
}
|
| 33 |
-
|
| 34 |
-
// Check if email already exists
|
| 35 |
-
const existing = await pool.query("SELECT id FROM users WHERE lower(email) = $1", [email]);
|
| 36 |
-
if (existing.rows.length > 0) {
|
| 37 |
-
return NextResponse.json({ error: "An account already exists for this email" }, { status: 409 });
|
| 38 |
-
}
|
| 39 |
-
|
| 40 |
-
// Hash password
|
| 41 |
-
const passwordHash = await bcrypt.hash(password, 12);
|
| 42 |
-
|
| 43 |
-
// Look up citizen role ID
|
| 44 |
-
const roleResult = await pool.query("SELECT id FROM roles WHERE name = 'citizen' LIMIT 1");
|
| 45 |
-
const roleId = roleResult.rows[0]?.id;
|
| 46 |
-
if (!roleId) {
|
| 47 |
-
return NextResponse.json({ error: "Citizen role is not configured in the database" }, { status: 500 });
|
| 48 |
-
}
|
| 49 |
-
|
| 50 |
-
// Insert new user
|
| 51 |
-
const insertResult = await pool.query<UserRow>(
|
| 52 |
-
`INSERT INTO users (full_name, email, phone, password_hash, role_id)
|
| 53 |
-
VALUES ($1, $2, $3, $4, $5)
|
| 54 |
-
RETURNING
|
| 55 |
-
id,
|
| 56 |
-
full_name AS "fullName",
|
| 57 |
-
email,
|
| 58 |
-
'citizen' AS role,
|
| 59 |
-
department_id AS "departmentId",
|
| 60 |
-
ward_id AS "wardId",
|
| 61 |
-
password_hash AS "passwordHash"`,
|
| 62 |
-
[fullName, email, phone, passwordHash, roleId],
|
| 63 |
-
);
|
| 64 |
-
|
| 65 |
-
const user = insertResult.rows[0];
|
| 66 |
-
const { passwordHash: _ph, ...sessionUser } = user;
|
| 67 |
-
|
| 68 |
-
const cookieStore = await cookies();
|
| 69 |
-
cookieStore.set(SESSION_COOKIE_NAME, encodeSession(sessionUser), {
|
| 70 |
-
httpOnly: true,
|
| 71 |
-
sameSite: "lax",
|
| 72 |
-
path: "/",
|
| 73 |
-
secure: process.env.NODE_ENV === "production",
|
| 74 |
-
maxAge: 60 * 60 * 12,
|
| 75 |
-
});
|
| 76 |
-
|
| 77 |
-
return NextResponse.json({ data: sessionUser }, { status: 201 });
|
| 78 |
-
} catch (error) {
|
| 79 |
-
console.error("[register] Error:", error);
|
| 80 |
-
const message = error instanceof Error ? error.message : "Registration failed";
|
| 81 |
-
return NextResponse.json({ error: message }, { status: 500 });
|
| 82 |
-
}
|
| 83 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/app/complaints/[id]/page.tsx
DELETED
|
@@ -1,264 +0,0 @@
|
|
| 1 |
-
import Link from "next/link";
|
| 2 |
-
import { AlertTriangle, Clock3, MapPinned, ShieldCheck, Waves } from "lucide-react";
|
| 3 |
-
import { AppShell } from "@/components/app-shell";
|
| 4 |
-
import { CitizenFollowUpPanel } from "@/components/citizen-follow-up-panel";
|
| 5 |
-
import { ComplaintMediaGallery } from "@/components/complaint-media-gallery";
|
| 6 |
-
import { SectionHeader } from "@/components/section-header";
|
| 7 |
-
import { requireRole } from "@/lib/auth";
|
| 8 |
-
import {
|
| 9 |
-
getComplaintDetail,
|
| 10 |
-
getComplaintFeedback,
|
| 11 |
-
getComplaintHistory,
|
| 12 |
-
getComplaintMedia,
|
| 13 |
-
type ComplaintFeedbackItem,
|
| 14 |
-
type ComplaintMediaItem,
|
| 15 |
-
type ComplaintStatusHistoryItem,
|
| 16 |
-
} from "@/lib/api";
|
| 17 |
-
|
| 18 |
-
type ComplaintDetailPageProps = {
|
| 19 |
-
params: Promise<{
|
| 20 |
-
id: string;
|
| 21 |
-
}>;
|
| 22 |
-
};
|
| 23 |
-
|
| 24 |
-
function formatStatus(status: string) {
|
| 25 |
-
return status
|
| 26 |
-
.split("_")
|
| 27 |
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
| 28 |
-
.join(" ");
|
| 29 |
-
}
|
| 30 |
-
|
| 31 |
-
function formatPriority(priority: string) {
|
| 32 |
-
switch (priority) {
|
| 33 |
-
case "P1":
|
| 34 |
-
return "P1 Critical";
|
| 35 |
-
case "P2":
|
| 36 |
-
return "P2 High";
|
| 37 |
-
case "P3":
|
| 38 |
-
return "P3 Medium";
|
| 39 |
-
default:
|
| 40 |
-
return "P4 Low";
|
| 41 |
-
}
|
| 42 |
-
}
|
| 43 |
-
|
| 44 |
-
function formatDateTime(value: string) {
|
| 45 |
-
return new Date(value).toLocaleString();
|
| 46 |
-
}
|
| 47 |
-
|
| 48 |
-
function timelineTone(status: string) {
|
| 49 |
-
switch (status) {
|
| 50 |
-
case "submitted":
|
| 51 |
-
return "bg-civic-primary";
|
| 52 |
-
case "validated":
|
| 53 |
-
return "bg-civic-secondary";
|
| 54 |
-
case "classified":
|
| 55 |
-
case "prioritized":
|
| 56 |
-
return "bg-amber-500";
|
| 57 |
-
case "assigned":
|
| 58 |
-
return "bg-slate-800";
|
| 59 |
-
case "in_progress":
|
| 60 |
-
return "bg-emerald-600";
|
| 61 |
-
case "resolved":
|
| 62 |
-
case "closed":
|
| 63 |
-
return "bg-emerald-700";
|
| 64 |
-
case "reopened":
|
| 65 |
-
case "escalated":
|
| 66 |
-
return "bg-civic-danger";
|
| 67 |
-
default:
|
| 68 |
-
return "bg-slate-300";
|
| 69 |
-
}
|
| 70 |
-
}
|
| 71 |
-
|
| 72 |
-
export default async function ComplaintDetailPage({ params }: ComplaintDetailPageProps) {
|
| 73 |
-
await requireRole(["citizen"]);
|
| 74 |
-
const { id } = await params;
|
| 75 |
-
|
| 76 |
-
let complaintStatus = "In Progress";
|
| 77 |
-
let complaintPriority = "P2 High";
|
| 78 |
-
let complaintLocation = "Market Road, Ward 12";
|
| 79 |
-
let complaintDomain = "Water Supply, Sewerage, and Drainage";
|
| 80 |
-
let complaintDescription = "Overflow affecting entry and traffic movement near the market.";
|
| 81 |
-
let complaintCode = "CP-2026-00124";
|
| 82 |
-
let complaintDepartment = "Water and Sewerage";
|
| 83 |
-
let media: ComplaintMediaItem[] = [];
|
| 84 |
-
let feedback: ComplaintFeedbackItem[] = [];
|
| 85 |
-
let rawStatus = "in_progress";
|
| 86 |
-
let citizenId = "00000000-0000-0000-0000-000000000001";
|
| 87 |
-
let timeline: Array<{ title: string; time: string; note: string; tone: string }> = [
|
| 88 |
-
{
|
| 89 |
-
title: "Submitted",
|
| 90 |
-
time: "Pending",
|
| 91 |
-
note: "Complaint created by citizen with image, description, and geo-tagged location.",
|
| 92 |
-
tone: "bg-civic-primary",
|
| 93 |
-
},
|
| 94 |
-
];
|
| 95 |
-
|
| 96 |
-
try {
|
| 97 |
-
const [complaint, history, mediaItems, feedbackItems] = await Promise.all([
|
| 98 |
-
getComplaintDetail(id),
|
| 99 |
-
getComplaintHistory(id),
|
| 100 |
-
getComplaintMedia(id),
|
| 101 |
-
getComplaintFeedback(id),
|
| 102 |
-
]);
|
| 103 |
-
|
| 104 |
-
rawStatus = complaint.status;
|
| 105 |
-
complaintStatus = formatStatus(complaint.status);
|
| 106 |
-
complaintPriority = formatPriority(complaint.priorityLevel);
|
| 107 |
-
complaintLocation = complaint.addressLine ?? complaint.landmark ?? "Location pending";
|
| 108 |
-
complaintDomain = complaint.domainName ?? "Unclassified";
|
| 109 |
-
complaintDescription = complaint.description ?? complaintDescription;
|
| 110 |
-
complaintCode = complaint.complaintCode;
|
| 111 |
-
complaintDepartment = complaint.departmentName ?? complaintDepartment;
|
| 112 |
-
citizenId = complaint.citizenId;
|
| 113 |
-
media = mediaItems;
|
| 114 |
-
feedback = feedbackItems;
|
| 115 |
-
|
| 116 |
-
if (history.length > 0) {
|
| 117 |
-
timeline = history.map((entry: ComplaintStatusHistoryItem) => ({
|
| 118 |
-
title: formatStatus(entry.newStatus),
|
| 119 |
-
time: formatDateTime(entry.createdAt),
|
| 120 |
-
note: entry.changeReason ?? `Complaint moved to ${formatStatus(entry.newStatus)}.`,
|
| 121 |
-
tone: timelineTone(entry.newStatus),
|
| 122 |
-
}));
|
| 123 |
-
}
|
| 124 |
-
} catch {
|
| 125 |
-
// Keep fallback values for design flow when backend is unavailable.
|
| 126 |
-
}
|
| 127 |
-
|
| 128 |
-
return (
|
| 129 |
-
<AppShell>
|
| 130 |
-
<section className="grid gap-6 rounded-[2rem] bg-civic-primary px-6 py-8 text-white shadow-civic lg:grid-cols-[1.08fr_0.92fr] lg:px-8">
|
| 131 |
-
<div className="space-y-4">
|
| 132 |
-
<p className="text-sm font-semibold uppercase tracking-[0.22em] text-white/75">Complaint detail</p>
|
| 133 |
-
<h1 className="text-4xl font-semibold tracking-tight">Track your complaint in one place.</h1>
|
| 134 |
-
<p className="max-w-2xl text-sm leading-7 text-white/80">
|
| 135 |
-
Check the current status, department ownership, attached evidence, and any updates from the city team.
|
| 136 |
-
</p>
|
| 137 |
-
</div>
|
| 138 |
-
|
| 139 |
-
<div className="grid gap-4 sm:grid-cols-2">
|
| 140 |
-
{[
|
| 141 |
-
{ label: "Complaint ID", value: complaintCode },
|
| 142 |
-
{ label: "Current status", value: complaintStatus },
|
| 143 |
-
{ label: "Priority", value: complaintPriority },
|
| 144 |
-
{ label: "Assigned department", value: complaintDepartment },
|
| 145 |
-
].map((card) => (
|
| 146 |
-
<div key={card.label} className="rounded-3xl border border-white/15 bg-white/10 p-5 backdrop-blur">
|
| 147 |
-
<p className="text-sm font-semibold uppercase tracking-[0.16em] text-white/75">{card.label}</p>
|
| 148 |
-
<p className="mt-3 text-xl font-semibold text-white">{card.value}</p>
|
| 149 |
-
</div>
|
| 150 |
-
))}
|
| 151 |
-
</div>
|
| 152 |
-
</section>
|
| 153 |
-
|
| 154 |
-
<section className="grid gap-6 lg:grid-cols-[1.1fr_0.9fr]">
|
| 155 |
-
<section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm sm:p-8">
|
| 156 |
-
<div className="flex flex-wrap items-start justify-between gap-4">
|
| 157 |
-
<div>
|
| 158 |
-
<p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Timeline</p>
|
| 159 |
-
<h2 className="mt-2 text-2xl font-semibold text-civic-text">Status history</h2>
|
| 160 |
-
</div>
|
| 161 |
-
<div className="rounded-full bg-amber-50 px-4 py-2 text-sm font-semibold text-amber-800">
|
| 162 |
-
Current state: {complaintStatus}
|
| 163 |
-
</div>
|
| 164 |
-
</div>
|
| 165 |
-
|
| 166 |
-
<div className="mt-8 space-y-6">
|
| 167 |
-
{timeline.map((entry, index) => (
|
| 168 |
-
<div key={`${entry.title}-${entry.time}-${index}`} className="grid gap-4 md:grid-cols-[auto_1fr] md:gap-5">
|
| 169 |
-
<div className="flex items-start gap-4 md:flex-col md:items-center">
|
| 170 |
-
<div className={["mt-1 h-4 w-4 rounded-full", entry.tone].join(" ")} />
|
| 171 |
-
{index !== timeline.length - 1 ? <div className="hidden h-16 w-px bg-slate-200 md:block" /> : null}
|
| 172 |
-
</div>
|
| 173 |
-
<div className="rounded-3xl bg-slate-50 p-5">
|
| 174 |
-
<div className="flex flex-wrap items-center justify-between gap-3">
|
| 175 |
-
<h3 className="text-lg font-semibold text-civic-text">{entry.title}</h3>
|
| 176 |
-
<div className="inline-flex items-center gap-2 rounded-full bg-white px-3 py-2 text-xs font-semibold text-slate-700">
|
| 177 |
-
<Clock3 className="h-4 w-4" />
|
| 178 |
-
{entry.time}
|
| 179 |
-
</div>
|
| 180 |
-
</div>
|
| 181 |
-
<p className="mt-3 text-sm leading-6 text-civic-muted">{entry.note}</p>
|
| 182 |
-
</div>
|
| 183 |
-
</div>
|
| 184 |
-
))}
|
| 185 |
-
</div>
|
| 186 |
-
</section>
|
| 187 |
-
|
| 188 |
-
<div className="space-y-6">
|
| 189 |
-
<section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
|
| 190 |
-
<SectionHeader eyebrow="Location" icon={MapPinned} title="Complaint context" />
|
| 191 |
-
|
| 192 |
-
<div className="mt-5 space-y-3">
|
| 193 |
-
{[complaintLocation, `Domain: ${complaintDomain}`, complaintDescription].map((item) => (
|
| 194 |
-
<div key={item} className="rounded-2xl bg-slate-50 px-4 py-3 text-sm leading-6 text-civic-text">
|
| 195 |
-
{item}
|
| 196 |
-
</div>
|
| 197 |
-
))}
|
| 198 |
-
</div>
|
| 199 |
-
</section>
|
| 200 |
-
|
| 201 |
-
<ComplaintMediaGallery
|
| 202 |
-
items={media}
|
| 203 |
-
title="Photos, video, and audio"
|
| 204 |
-
emptyLabel="No evidence has been added to this complaint yet."
|
| 205 |
-
/>
|
| 206 |
-
|
| 207 |
-
<CitizenFollowUpPanel complaintId={id} citizenId={citizenId} status={rawStatus} feedback={feedback} />
|
| 208 |
-
</div>
|
| 209 |
-
</section>
|
| 210 |
-
|
| 211 |
-
<section className="grid gap-6 lg:grid-cols-[0.95fr_1.05fr]">
|
| 212 |
-
<section className="rounded-[2rem] border border-emerald-200 bg-emerald-50 p-6 shadow-sm">
|
| 213 |
-
<div className="flex items-start gap-3">
|
| 214 |
-
<ShieldCheck className="mt-0.5 h-5 w-5 text-emerald-700" />
|
| 215 |
-
<div>
|
| 216 |
-
<h2 className="text-lg font-semibold text-emerald-950">What you can see here</h2>
|
| 217 |
-
<p className="mt-2 text-sm leading-6 text-emerald-900/85">
|
| 218 |
-
This page shows your complaint progress and public-safe updates without exposing internal staff notes.
|
| 219 |
-
</p>
|
| 220 |
-
</div>
|
| 221 |
-
</div>
|
| 222 |
-
</section>
|
| 223 |
-
|
| 224 |
-
<section className="rounded-[2rem] border border-amber-200 bg-amber-50 p-6 shadow-sm">
|
| 225 |
-
<div className="flex items-start gap-3">
|
| 226 |
-
<AlertTriangle className="mt-0.5 h-5 w-5 text-amber-700" />
|
| 227 |
-
<div>
|
| 228 |
-
<h2 className="text-lg font-semibold text-amber-950">Need more help?</h2>
|
| 229 |
-
<p className="mt-2 text-sm leading-6 text-amber-900/85">
|
| 230 |
-
If the issue is marked resolved but still exists, use the reopen option below so the team can review it again.
|
| 231 |
-
</p>
|
| 232 |
-
</div>
|
| 233 |
-
</div>
|
| 234 |
-
</section>
|
| 235 |
-
</section>
|
| 236 |
-
|
| 237 |
-
<section className="flex flex-wrap items-center justify-between gap-4 rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
|
| 238 |
-
<div className="space-y-2">
|
| 239 |
-
<p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Quick actions</p>
|
| 240 |
-
<h2 className="text-2xl font-semibold text-civic-text">Keep track of this report or return to your list.</h2>
|
| 241 |
-
<p className="max-w-2xl text-sm leading-6 text-civic-muted">
|
| 242 |
-
You can go back to all complaints, stay on this page for updates, or report another issue if needed.
|
| 243 |
-
</p>
|
| 244 |
-
</div>
|
| 245 |
-
|
| 246 |
-
<div className="flex flex-wrap gap-3">
|
| 247 |
-
<Link
|
| 248 |
-
className="inline-flex rounded-full border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:border-civic-primary hover:text-civic-primary"
|
| 249 |
-
href="/complaints"
|
| 250 |
-
>
|
| 251 |
-
Back to complaints
|
| 252 |
-
</Link>
|
| 253 |
-
<Link
|
| 254 |
-
className="inline-flex items-center gap-2 rounded-full bg-slate-900 px-5 py-3 text-sm font-semibold text-white transition hover:-translate-y-0.5"
|
| 255 |
-
href="/report"
|
| 256 |
-
>
|
| 257 |
-
Report another issue
|
| 258 |
-
<Waves className="h-4 w-4" />
|
| 259 |
-
</Link>
|
| 260 |
-
</div>
|
| 261 |
-
</section>
|
| 262 |
-
</AppShell>
|
| 263 |
-
);
|
| 264 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/app/complaints/page.tsx
DELETED
|
@@ -1,217 +0,0 @@
|
|
| 1 |
-
import Link from "next/link";
|
| 2 |
-
import { Clock3, MapPinned, Search } from "lucide-react";
|
| 3 |
-
import { AppShell } from "@/components/app-shell";
|
| 4 |
-
import { PriorityBadge } from "@/components/priority-badge";
|
| 5 |
-
import { StatusBadge } from "@/components/status-badge";
|
| 6 |
-
import { getCitizenComplaints } from "@/lib/api";
|
| 7 |
-
import { requireRole } from "@/lib/auth";
|
| 8 |
-
|
| 9 |
-
const complaintStats = [
|
| 10 |
-
{ label: "Active", value: "04" },
|
| 11 |
-
{ label: "Resolved", value: "09" },
|
| 12 |
-
{ label: "Reopened", value: "01" },
|
| 13 |
-
];
|
| 14 |
-
|
| 15 |
-
type ComplaintCard = {
|
| 16 |
-
id: string;
|
| 17 |
-
complaintCode?: string;
|
| 18 |
-
title: string;
|
| 19 |
-
domain: string;
|
| 20 |
-
status: string;
|
| 21 |
-
priority: string;
|
| 22 |
-
location: string;
|
| 23 |
-
updatedAt: string;
|
| 24 |
-
};
|
| 25 |
-
|
| 26 |
-
const fallbackComplaints: ComplaintCard[] = [
|
| 27 |
-
{
|
| 28 |
-
id: "CP-2026-00124",
|
| 29 |
-
complaintCode: "CP-2026-00124",
|
| 30 |
-
title: "Sewage overflow near market entrance",
|
| 31 |
-
domain: "Water Supply, Sewerage, and Drainage",
|
| 32 |
-
status: "In Progress",
|
| 33 |
-
priority: "P2 High",
|
| 34 |
-
location: "Market Road, Ward 12",
|
| 35 |
-
updatedAt: "Updated 1 hour ago",
|
| 36 |
-
},
|
| 37 |
-
{
|
| 38 |
-
id: "CP-2026-00108",
|
| 39 |
-
complaintCode: "CP-2026-00108",
|
| 40 |
-
title: "Streetlight not working on school road",
|
| 41 |
-
domain: "Street Lighting and Electrical Infrastructure",
|
| 42 |
-
status: "Assigned",
|
| 43 |
-
priority: "P3 Medium",
|
| 44 |
-
location: "School Street, Ward 8",
|
| 45 |
-
updatedAt: "Updated today",
|
| 46 |
-
},
|
| 47 |
-
{
|
| 48 |
-
id: "CP-2026-00097",
|
| 49 |
-
complaintCode: "CP-2026-00097",
|
| 50 |
-
title: "Overflowing garbage bin near bus stop",
|
| 51 |
-
domain: "Sanitation and Waste Management",
|
| 52 |
-
status: "Resolved",
|
| 53 |
-
priority: "P3 Medium",
|
| 54 |
-
location: "Central Bus Stop, Ward 3",
|
| 55 |
-
updatedAt: "Resolved yesterday",
|
| 56 |
-
},
|
| 57 |
-
{
|
| 58 |
-
id: "CP-2026-00091",
|
| 59 |
-
complaintCode: "CP-2026-00091",
|
| 60 |
-
title: "Open manhole beside community hall",
|
| 61 |
-
domain: "Public Infrastructure and Amenities",
|
| 62 |
-
status: "Reopened",
|
| 63 |
-
priority: "P1 Critical",
|
| 64 |
-
location: "Community Hall Lane, Ward 5",
|
| 65 |
-
updatedAt: "Reopened 3 hours ago",
|
| 66 |
-
},
|
| 67 |
-
];
|
| 68 |
-
|
| 69 |
-
const filters = ["All", "Active", "Resolved", "Reopened", "Emergency"];
|
| 70 |
-
|
| 71 |
-
function formatStatus(status: string) {
|
| 72 |
-
return status
|
| 73 |
-
.split("_")
|
| 74 |
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
| 75 |
-
.join(" ");
|
| 76 |
-
}
|
| 77 |
-
|
| 78 |
-
function formatPriority(priority: string) {
|
| 79 |
-
switch (priority) {
|
| 80 |
-
case "P1":
|
| 81 |
-
return "P1 Critical";
|
| 82 |
-
case "P2":
|
| 83 |
-
return "P2 High";
|
| 84 |
-
case "P3":
|
| 85 |
-
return "P3 Medium";
|
| 86 |
-
default:
|
| 87 |
-
return "P4 Low";
|
| 88 |
-
}
|
| 89 |
-
}
|
| 90 |
-
|
| 91 |
-
export default async function ComplaintsPage() {
|
| 92 |
-
const user = await requireRole(["citizen"]);
|
| 93 |
-
let complaints: ComplaintCard[] = fallbackComplaints;
|
| 94 |
-
|
| 95 |
-
try {
|
| 96 |
-
const apiComplaints = await getCitizenComplaints(user.id);
|
| 97 |
-
|
| 98 |
-
if (apiComplaints.length > 0) {
|
| 99 |
-
complaints = apiComplaints.map((complaint) => ({
|
| 100 |
-
id: complaint.id,
|
| 101 |
-
title: complaint.title,
|
| 102 |
-
domain: complaint.domainName ?? "Unclassified",
|
| 103 |
-
status: formatStatus(complaint.status),
|
| 104 |
-
priority: formatPriority(complaint.priorityLevel),
|
| 105 |
-
location: complaint.departmentName ?? "Department pending",
|
| 106 |
-
updatedAt: new Date(complaint.submittedAt).toLocaleString(),
|
| 107 |
-
complaintCode: complaint.complaintCode,
|
| 108 |
-
}));
|
| 109 |
-
}
|
| 110 |
-
} catch {
|
| 111 |
-
complaints = fallbackComplaints;
|
| 112 |
-
}
|
| 113 |
-
|
| 114 |
-
return (
|
| 115 |
-
<AppShell>
|
| 116 |
-
<section className="relative overflow-hidden rounded-[2.5rem] bg-gradient-to-br from-[#0F172A] via-civic-primary to-[#1E3A8A] px-8 py-12 text-white shadow-civic lg:px-12 lg:py-16">
|
| 117 |
-
<div className="absolute -left-32 -top-32 h-96 w-96 rounded-full bg-white/10 blur-[100px]" />
|
| 118 |
-
|
| 119 |
-
<div className="relative flex flex-wrap items-center justify-between gap-6">
|
| 120 |
-
<div className="space-y-4">
|
| 121 |
-
<h1 className="text-4xl font-extrabold tracking-tight sm:text-5xl">My Reports</h1>
|
| 122 |
-
<p className="text-lg text-slate-300">Track the status of the issues you've reported in your neighborhood.</p>
|
| 123 |
-
</div>
|
| 124 |
-
<Link
|
| 125 |
-
className="group inline-flex items-center gap-2 rounded-full bg-white px-7 py-4 text-base font-bold text-civic-primary shadow-xl transition-all hover:-translate-y-1 hover:shadow-2xl"
|
| 126 |
-
href="/report"
|
| 127 |
-
>
|
| 128 |
-
Report a new issue
|
| 129 |
-
</Link>
|
| 130 |
-
</div>
|
| 131 |
-
|
| 132 |
-
<div className="relative mt-10 grid gap-5 sm:grid-cols-3">
|
| 133 |
-
{complaintStats.map((stat) => (
|
| 134 |
-
<div key={stat.label} className="rounded-[2rem] border border-white/15 bg-white/10 p-6 backdrop-blur-xl">
|
| 135 |
-
<p className="text-xs font-bold uppercase tracking-[0.2em] text-white/60">{stat.label}</p>
|
| 136 |
-
<p className="mt-4 text-4xl font-extrabold text-white">{stat.value}</p>
|
| 137 |
-
</div>
|
| 138 |
-
))}
|
| 139 |
-
</div>
|
| 140 |
-
</section>
|
| 141 |
-
|
| 142 |
-
<section className="space-y-6">
|
| 143 |
-
<div className="rounded-[2rem] border border-slate-200 bg-white p-5 shadow-sm">
|
| 144 |
-
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
| 145 |
-
<div className="flex flex-wrap gap-2">
|
| 146 |
-
{filters.map((filter) => (
|
| 147 |
-
<button
|
| 148 |
-
key={filter}
|
| 149 |
-
type="button"
|
| 150 |
-
className={[
|
| 151 |
-
"rounded-full px-4 py-2 text-sm font-semibold transition",
|
| 152 |
-
filter === "All"
|
| 153 |
-
? "bg-civic-primary text-white"
|
| 154 |
-
: "bg-slate-100 text-slate-700 hover:bg-slate-200",
|
| 155 |
-
].join(" ")}
|
| 156 |
-
>
|
| 157 |
-
{filter}
|
| 158 |
-
</button>
|
| 159 |
-
))}
|
| 160 |
-
</div>
|
| 161 |
-
|
| 162 |
-
<div className="flex items-center gap-3 rounded-full border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-civic-muted lg:min-w-80">
|
| 163 |
-
<Search className="h-4 w-4" />
|
| 164 |
-
Search by complaint ID or area
|
| 165 |
-
</div>
|
| 166 |
-
</div>
|
| 167 |
-
</div>
|
| 168 |
-
|
| 169 |
-
<section className="rounded-[2.5rem] border border-slate-200 bg-white/70 p-8 shadow-glass backdrop-blur-xl">
|
| 170 |
-
<h2 className="text-2xl font-extrabold text-slate-900">Recent Activity</h2>
|
| 171 |
-
|
| 172 |
-
<div className="mt-8 space-y-5">
|
| 173 |
-
{complaints.map((complaint) => (
|
| 174 |
-
<article
|
| 175 |
-
key={complaint.id}
|
| 176 |
-
className="group relative overflow-hidden rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm transition-all hover:-translate-y-1 hover:border-slate-300 hover:shadow-xl"
|
| 177 |
-
>
|
| 178 |
-
<div className="flex flex-wrap items-start justify-between gap-4">
|
| 179 |
-
<div className="space-y-3">
|
| 180 |
-
<div className="flex flex-wrap items-center gap-3">
|
| 181 |
-
<span className="rounded-full bg-slate-100 px-3.5 py-1 text-xs font-bold text-slate-700">
|
| 182 |
-
{complaint.complaintCode ?? complaint.id}
|
| 183 |
-
</span>
|
| 184 |
-
<StatusBadge status={complaint.status} />
|
| 185 |
-
<PriorityBadge priority={complaint.priority} />
|
| 186 |
-
</div>
|
| 187 |
-
<h3 className="text-2xl font-bold text-slate-900">{complaint.title}</h3>
|
| 188 |
-
<p className="text-sm font-medium text-slate-500">{complaint.domain}</p>
|
| 189 |
-
</div>
|
| 190 |
-
|
| 191 |
-
<div className="flex items-center gap-2 rounded-full border border-slate-100 bg-slate-50 px-4 py-2 text-xs font-bold text-slate-500">
|
| 192 |
-
<Clock3 className="h-4 w-4" />
|
| 193 |
-
{complaint.updatedAt}
|
| 194 |
-
</div>
|
| 195 |
-
</div>
|
| 196 |
-
|
| 197 |
-
<div className="mt-6 flex items-center gap-2 text-sm font-medium text-slate-500">
|
| 198 |
-
<MapPinned className="h-4 w-4" />
|
| 199 |
-
{complaint.location}
|
| 200 |
-
</div>
|
| 201 |
-
|
| 202 |
-
<div className="mt-6 flex flex-wrap gap-3">
|
| 203 |
-
<Link
|
| 204 |
-
className="inline-flex items-center gap-2 rounded-full bg-slate-900 px-6 py-3 text-sm font-bold text-white shadow-md transition hover:bg-slate-800 hover:shadow-lg"
|
| 205 |
-
href={`/complaints/${complaint.id}`}
|
| 206 |
-
>
|
| 207 |
-
View Details
|
| 208 |
-
</Link>
|
| 209 |
-
</div>
|
| 210 |
-
</article>
|
| 211 |
-
))}
|
| 212 |
-
</div>
|
| 213 |
-
</section>
|
| 214 |
-
</section>
|
| 215 |
-
</AppShell>
|
| 216 |
-
);
|
| 217 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/app/dashboard/analytics/page.tsx
DELETED
|
@@ -1,184 +0,0 @@
|
|
| 1 |
-
import Link from "next/link";
|
| 2 |
-
import { AppShell } from "@/components/app-shell";
|
| 3 |
-
import { EmptyState } from "@/components/empty-state";
|
| 4 |
-
import { LiveSyncBadge } from "@/components/live-sync-badge";
|
| 5 |
-
import { requireRole } from "@/lib/auth";
|
| 6 |
-
import { getAnalyticsExportUrl, getAnalyticsSummary, type AnalyticsSummary } from "@/lib/api";
|
| 7 |
-
import { ArrowDownToLine, BarChart3, Clock3, Siren, TrendingUp } from "lucide-react";
|
| 8 |
-
|
| 9 |
-
const fallbackAnalytics: AnalyticsSummary = {
|
| 10 |
-
trends: [],
|
| 11 |
-
domainBreakdown: [],
|
| 12 |
-
departmentPerformance: [],
|
| 13 |
-
kpis: {
|
| 14 |
-
emergencyCount: 0,
|
| 15 |
-
reopenedCount: 0,
|
| 16 |
-
repeatComplaintRiskCount: 0,
|
| 17 |
-
slaBreaches: 0,
|
| 18 |
-
},
|
| 19 |
-
};
|
| 20 |
-
|
| 21 |
-
export default async function DashboardAnalyticsPage() {
|
| 22 |
-
await requireRole(["department_operator", "municipal_admin"]);
|
| 23 |
-
|
| 24 |
-
let analytics = fallbackAnalytics;
|
| 25 |
-
|
| 26 |
-
try {
|
| 27 |
-
analytics = await getAnalyticsSummary();
|
| 28 |
-
} catch {
|
| 29 |
-
analytics = fallbackAnalytics;
|
| 30 |
-
}
|
| 31 |
-
|
| 32 |
-
const maxTrend = Math.max(
|
| 33 |
-
1,
|
| 34 |
-
...analytics.trends.flatMap((item) => [item.reportedCount, item.resolvedCount]),
|
| 35 |
-
);
|
| 36 |
-
|
| 37 |
-
return (
|
| 38 |
-
<AppShell>
|
| 39 |
-
<section className="rounded-[2rem] bg-gradient-to-br from-slate-950 via-civic-primary to-[#0b6a8f] px-6 py-8 text-white shadow-civic lg:px-8">
|
| 40 |
-
<div className="flex flex-wrap items-center justify-between gap-4">
|
| 41 |
-
<div className="space-y-3">
|
| 42 |
-
<p className="text-sm font-semibold uppercase tracking-[0.22em] text-white/70">Analytics</p>
|
| 43 |
-
<h1 className="text-4xl font-semibold tracking-tight">Track performance, trends, and launch readiness.</h1>
|
| 44 |
-
<p className="max-w-2xl text-sm leading-7 text-white/80">
|
| 45 |
-
Use this view to monitor complaint trends, department throughput, SLA risk, and repeat civic failures.
|
| 46 |
-
</p>
|
| 47 |
-
</div>
|
| 48 |
-
<div className="flex flex-wrap items-center gap-3">
|
| 49 |
-
<LiveSyncBadge label="Analytics live refresh" />
|
| 50 |
-
<a
|
| 51 |
-
className="inline-flex items-center gap-2 rounded-full bg-white px-5 py-3 text-sm font-semibold text-civic-primary transition hover:-translate-y-0.5"
|
| 52 |
-
href={getAnalyticsExportUrl()}
|
| 53 |
-
target="_blank"
|
| 54 |
-
rel="noreferrer"
|
| 55 |
-
>
|
| 56 |
-
<ArrowDownToLine className="h-4 w-4" />
|
| 57 |
-
Export CSV
|
| 58 |
-
</a>
|
| 59 |
-
</div>
|
| 60 |
-
</div>
|
| 61 |
-
</section>
|
| 62 |
-
|
| 63 |
-
<section className="grid gap-6 lg:grid-cols-4">
|
| 64 |
-
{[
|
| 65 |
-
{ label: "Emergency", value: analytics.kpis.emergencyCount, icon: Siren },
|
| 66 |
-
{ label: "Reopened", value: analytics.kpis.reopenedCount, icon: TrendingUp },
|
| 67 |
-
{ label: "Repeat risk", value: analytics.kpis.repeatComplaintRiskCount, icon: BarChart3 },
|
| 68 |
-
{ label: "SLA breaches", value: analytics.kpis.slaBreaches, icon: Clock3 },
|
| 69 |
-
].map((item) => {
|
| 70 |
-
const Icon = item.icon;
|
| 71 |
-
return (
|
| 72 |
-
<div key={item.label} className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
|
| 73 |
-
<div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-civic-secondary/15 text-civic-secondary">
|
| 74 |
-
<Icon className="h-5 w-5" />
|
| 75 |
-
</div>
|
| 76 |
-
<p className="mt-5 text-sm font-semibold uppercase tracking-[0.16em] text-civic-muted">{item.label}</p>
|
| 77 |
-
<p className="mt-2 text-3xl font-semibold text-civic-text">{item.value}</p>
|
| 78 |
-
</div>
|
| 79 |
-
);
|
| 80 |
-
})}
|
| 81 |
-
</section>
|
| 82 |
-
|
| 83 |
-
<section className="grid gap-6 xl:grid-cols-[1.15fr_0.85fr]">
|
| 84 |
-
<section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
|
| 85 |
-
<div>
|
| 86 |
-
<p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Trends</p>
|
| 87 |
-
<h2 className="mt-2 text-2xl font-semibold text-civic-text">Reported vs resolved over the last 7 days</h2>
|
| 88 |
-
</div>
|
| 89 |
-
<div className="mt-6 space-y-4">
|
| 90 |
-
{analytics.trends.length === 0 ? (
|
| 91 |
-
<EmptyState icon={TrendingUp} title="No trend data yet" description="Trend data will appear once complaints start moving through the system." />
|
| 92 |
-
) : (
|
| 93 |
-
analytics.trends.map((item) => (
|
| 94 |
-
<div key={item.day} className="space-y-2">
|
| 95 |
-
<div className="flex items-center justify-between gap-4 text-sm">
|
| 96 |
-
<span className="font-semibold text-civic-text">{item.day}</span>
|
| 97 |
-
<span className="text-civic-muted">
|
| 98 |
-
reported {item.reportedCount} · resolved {item.resolvedCount}
|
| 99 |
-
</span>
|
| 100 |
-
</div>
|
| 101 |
-
<div className="grid gap-2">
|
| 102 |
-
<div className="h-3 overflow-hidden rounded-full bg-slate-100">
|
| 103 |
-
<div
|
| 104 |
-
className="h-full rounded-full bg-civic-primary"
|
| 105 |
-
style={{ width: `${(item.reportedCount / maxTrend) * 100}%` }}
|
| 106 |
-
/>
|
| 107 |
-
</div>
|
| 108 |
-
<div className="h-3 overflow-hidden rounded-full bg-slate-100">
|
| 109 |
-
<div
|
| 110 |
-
className="h-full rounded-full bg-civic-success"
|
| 111 |
-
style={{ width: `${(item.resolvedCount / maxTrend) * 100}%` }}
|
| 112 |
-
/>
|
| 113 |
-
</div>
|
| 114 |
-
</div>
|
| 115 |
-
</div>
|
| 116 |
-
))
|
| 117 |
-
)}
|
| 118 |
-
</div>
|
| 119 |
-
</section>
|
| 120 |
-
|
| 121 |
-
<section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
|
| 122 |
-
<div>
|
| 123 |
-
<p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Domains</p>
|
| 124 |
-
<h2 className="mt-2 text-2xl font-semibold text-civic-text">Complaint share by category</h2>
|
| 125 |
-
</div>
|
| 126 |
-
<div className="mt-6 space-y-3">
|
| 127 |
-
{analytics.domainBreakdown.length === 0 ? (
|
| 128 |
-
<EmptyState icon={BarChart3} title="No domain data yet" description="Domain distribution appears once complaints are recorded." />
|
| 129 |
-
) : (
|
| 130 |
-
analytics.domainBreakdown.map((item) => (
|
| 131 |
-
<div key={item.domainName} className="rounded-3xl bg-slate-50 p-4">
|
| 132 |
-
<div className="flex items-center justify-between gap-4">
|
| 133 |
-
<span className="text-sm font-semibold text-civic-text">{item.domainName}</span>
|
| 134 |
-
<span className="rounded-full bg-white px-3 py-1 text-xs font-semibold text-slate-700">
|
| 135 |
-
{item.complaintCount}
|
| 136 |
-
</span>
|
| 137 |
-
</div>
|
| 138 |
-
</div>
|
| 139 |
-
))
|
| 140 |
-
)}
|
| 141 |
-
</div>
|
| 142 |
-
</section>
|
| 143 |
-
</section>
|
| 144 |
-
|
| 145 |
-
<section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
|
| 146 |
-
<div className="flex flex-wrap items-center justify-between gap-4">
|
| 147 |
-
<div>
|
| 148 |
-
<p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Departments</p>
|
| 149 |
-
<h2 className="mt-2 text-2xl font-semibold text-civic-text">Performance and response time</h2>
|
| 150 |
-
</div>
|
| 151 |
-
<Link
|
| 152 |
-
href="/dashboard"
|
| 153 |
-
className="rounded-full border border-slate-200 px-4 py-2 text-sm font-semibold text-slate-700 transition hover:border-civic-primary hover:text-civic-primary"
|
| 154 |
-
>
|
| 155 |
-
Back to overview
|
| 156 |
-
</Link>
|
| 157 |
-
</div>
|
| 158 |
-
|
| 159 |
-
<div className="mt-6 overflow-x-auto">
|
| 160 |
-
<table className="min-w-full text-left text-sm">
|
| 161 |
-
<thead>
|
| 162 |
-
<tr className="border-b border-slate-200 text-civic-muted">
|
| 163 |
-
<th className="px-3 py-3 font-semibold">Department</th>
|
| 164 |
-
<th className="px-3 py-3 font-semibold">Pending</th>
|
| 165 |
-
<th className="px-3 py-3 font-semibold">Resolved</th>
|
| 166 |
-
<th className="px-3 py-3 font-semibold">Avg resolution (hrs)</th>
|
| 167 |
-
</tr>
|
| 168 |
-
</thead>
|
| 169 |
-
<tbody>
|
| 170 |
-
{analytics.departmentPerformance.map((item) => (
|
| 171 |
-
<tr key={item.departmentName} className="border-b border-slate-100">
|
| 172 |
-
<td className="px-3 py-4 font-semibold text-civic-text">{item.departmentName}</td>
|
| 173 |
-
<td className="px-3 py-4 text-civic-muted">{item.pendingCount}</td>
|
| 174 |
-
<td className="px-3 py-4 text-civic-muted">{item.resolvedCount}</td>
|
| 175 |
-
<td className="px-3 py-4 text-civic-muted">{item.averageResolutionHours.toFixed(2)}</td>
|
| 176 |
-
</tr>
|
| 177 |
-
))}
|
| 178 |
-
</tbody>
|
| 179 |
-
</table>
|
| 180 |
-
</div>
|
| 181 |
-
</section>
|
| 182 |
-
</AppShell>
|
| 183 |
-
);
|
| 184 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/app/dashboard/complaints/[id]/page.tsx
DELETED
|
@@ -1,383 +0,0 @@
|
|
| 1 |
-
import Link from "next/link";
|
| 2 |
-
import {
|
| 3 |
-
ArrowRight,
|
| 4 |
-
CheckCircle2,
|
| 5 |
-
ClipboardList,
|
| 6 |
-
Clock3,
|
| 7 |
-
MessageSquareText,
|
| 8 |
-
ShieldAlert,
|
| 9 |
-
UserCog,
|
| 10 |
-
} from "lucide-react";
|
| 11 |
-
import { AdminAssignAction } from "@/components/admin-assign-action";
|
| 12 |
-
import { AdminAssignmentHistory } from "@/components/admin-assignment-history";
|
| 13 |
-
import { AdminNotesPanel } from "@/components/admin-notes-panel";
|
| 14 |
-
import { AppShell } from "@/components/app-shell";
|
| 15 |
-
import { AdminStatusActions } from "@/components/admin-status-actions";
|
| 16 |
-
import { ComplaintAiPanel } from "@/components/complaint-ai-panel";
|
| 17 |
-
import { ComplaintMediaGallery } from "@/components/complaint-media-gallery";
|
| 18 |
-
import { ResolutionProofUploader } from "@/components/resolution-proof-uploader";
|
| 19 |
-
import { SectionHeader } from "@/components/section-header";
|
| 20 |
-
import { requireRole } from "@/lib/auth";
|
| 21 |
-
import {
|
| 22 |
-
getComplaintDetail,
|
| 23 |
-
getComplaintAssignments,
|
| 24 |
-
getComplaintAiInsights,
|
| 25 |
-
getComplaintHistory,
|
| 26 |
-
getComplaintMedia,
|
| 27 |
-
getComplaintNotes,
|
| 28 |
-
type ComplaintAiInsights,
|
| 29 |
-
type ComplaintAssignmentItem,
|
| 30 |
-
type ComplaintMediaItem,
|
| 31 |
-
type ComplaintNoteItem,
|
| 32 |
-
type ComplaintStatusHistoryItem,
|
| 33 |
-
} from "@/lib/api";
|
| 34 |
-
|
| 35 |
-
const internalActions = [
|
| 36 |
-
"Validate complaint",
|
| 37 |
-
"Adjust domain or sub-problem if needed",
|
| 38 |
-
"Assign responsible department",
|
| 39 |
-
"Assign field officer later",
|
| 40 |
-
"Change lifecycle state",
|
| 41 |
-
"Escalate if public danger is immediate",
|
| 42 |
-
];
|
| 43 |
-
|
| 44 |
-
type DashboardComplaintDetailPageProps = {
|
| 45 |
-
params: Promise<{
|
| 46 |
-
id: string;
|
| 47 |
-
}>;
|
| 48 |
-
};
|
| 49 |
-
|
| 50 |
-
function formatStatus(status: string) {
|
| 51 |
-
return status
|
| 52 |
-
.split("_")
|
| 53 |
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
| 54 |
-
.join(" ");
|
| 55 |
-
}
|
| 56 |
-
|
| 57 |
-
function formatPriority(priority: string) {
|
| 58 |
-
switch (priority) {
|
| 59 |
-
case "P1":
|
| 60 |
-
return "P1 Critical";
|
| 61 |
-
case "P2":
|
| 62 |
-
return "P2 High";
|
| 63 |
-
case "P3":
|
| 64 |
-
return "P3 Medium";
|
| 65 |
-
default:
|
| 66 |
-
return "P4 Low";
|
| 67 |
-
}
|
| 68 |
-
}
|
| 69 |
-
|
| 70 |
-
function formatDateTime(value: string) {
|
| 71 |
-
return new Date(value).toLocaleString();
|
| 72 |
-
}
|
| 73 |
-
|
| 74 |
-
function getPriorityRationale(priority: string, isEmergency: boolean) {
|
| 75 |
-
if (isEmergency || priority === "P1 Critical") {
|
| 76 |
-
return "Emergency or public-danger signals require immediate routing and shortest possible response time.";
|
| 77 |
-
}
|
| 78 |
-
|
| 79 |
-
if (priority === "P2 High") {
|
| 80 |
-
return "High-impact infrastructure or safety risk needs fast ownership before it grows into a wider area failure.";
|
| 81 |
-
}
|
| 82 |
-
|
| 83 |
-
if (priority === "P3 Medium") {
|
| 84 |
-
return "Standard operational issue with visible citizen impact. Keep it moving through the active queue.";
|
| 85 |
-
}
|
| 86 |
-
|
| 87 |
-
return "Lower-impact maintenance issue that can stay in the planned service backlog unless conditions worsen.";
|
| 88 |
-
}
|
| 89 |
-
|
| 90 |
-
function getRoutingConfidence(domain: string, assignments: ComplaintAssignmentItem[], department: string) {
|
| 91 |
-
if (assignments.length > 0) {
|
| 92 |
-
return `Routing has already been acted on. The latest ownership sits with ${assignments[0]?.departmentName ?? department}.`;
|
| 93 |
-
}
|
| 94 |
-
|
| 95 |
-
if (domain === "Fire Emergencies" || domain === "Flood and Water Disaster" || domain === "Disaster and Rescue") {
|
| 96 |
-
return `Emergency domain strongly matches ${department} and should stay on the fastest escalation path.`;
|
| 97 |
-
}
|
| 98 |
-
|
| 99 |
-
return `Current domain and complaint context point to ${department} as the primary queue owner.`;
|
| 100 |
-
}
|
| 101 |
-
|
| 102 |
-
function getOperationalState(status: string, assignments: ComplaintAssignmentItem[]) {
|
| 103 |
-
if (status === "Resolved" || status === "Closed") {
|
| 104 |
-
return "Work appears completed. Review proof, citizen follow-up, and any reopen risk.";
|
| 105 |
-
}
|
| 106 |
-
|
| 107 |
-
if (status === "Reopened" || status === "Escalated") {
|
| 108 |
-
return "Complaint needs renewed attention. Recheck ownership, on-ground risk, and SLA exposure.";
|
| 109 |
-
}
|
| 110 |
-
|
| 111 |
-
if (assignments.length > 0) {
|
| 112 |
-
return "Complaint is in active departmental handling. Track acceptance and field progress closely.";
|
| 113 |
-
}
|
| 114 |
-
|
| 115 |
-
return "Complaint still needs clear ownership. Assignment is the next critical operator action.";
|
| 116 |
-
}
|
| 117 |
-
|
| 118 |
-
function timelineTone(status: string) {
|
| 119 |
-
switch (status) {
|
| 120 |
-
case "submitted":
|
| 121 |
-
return "bg-civic-primary";
|
| 122 |
-
case "validated":
|
| 123 |
-
return "bg-civic-secondary";
|
| 124 |
-
case "classified":
|
| 125 |
-
case "prioritized":
|
| 126 |
-
return "bg-amber-500";
|
| 127 |
-
case "assigned":
|
| 128 |
-
return "bg-slate-800";
|
| 129 |
-
case "in_progress":
|
| 130 |
-
return "bg-emerald-600";
|
| 131 |
-
case "resolved":
|
| 132 |
-
case "closed":
|
| 133 |
-
return "bg-emerald-700";
|
| 134 |
-
case "reopened":
|
| 135 |
-
case "escalated":
|
| 136 |
-
return "bg-civic-danger";
|
| 137 |
-
default:
|
| 138 |
-
return "bg-slate-300";
|
| 139 |
-
}
|
| 140 |
-
}
|
| 141 |
-
|
| 142 |
-
export default async function DashboardComplaintDetailPage({ params }: DashboardComplaintDetailPageProps) {
|
| 143 |
-
const user = await requireRole(["department_operator", "municipal_admin"]);
|
| 144 |
-
const { id } = await params;
|
| 145 |
-
|
| 146 |
-
let complaintCode = "CP-2026-00141";
|
| 147 |
-
let complaintStatusValue = "submitted";
|
| 148 |
-
let complaintStatus = "Submitted";
|
| 149 |
-
let complaintPriority = "P1 Critical";
|
| 150 |
-
let complaintDomain = "Public Infrastructure and Amenities";
|
| 151 |
-
let complaintTitle = "Open manhole on high-traffic road";
|
| 152 |
-
let complaintLocation = "Temple Junction, Ward 4";
|
| 153 |
-
let complaintDescription = "High-risk public hazard affecting traffic near the junction.";
|
| 154 |
-
let complaintDepartment = "Municipal Maintenance";
|
| 155 |
-
let isEmergency = true;
|
| 156 |
-
let media: ComplaintMediaItem[] = [];
|
| 157 |
-
let assignments: ComplaintAssignmentItem[] = [];
|
| 158 |
-
let notes: ComplaintNoteItem[] = [];
|
| 159 |
-
let aiInsights: ComplaintAiInsights | null = null;
|
| 160 |
-
let internalTimeline: Array<{ title: string; time: string; note: string; tone: string }> = [
|
| 161 |
-
{
|
| 162 |
-
title: "Submitted",
|
| 163 |
-
time: "Pending",
|
| 164 |
-
note: "Complaint created by citizen with image and geo-tag.",
|
| 165 |
-
tone: "bg-civic-primary",
|
| 166 |
-
},
|
| 167 |
-
];
|
| 168 |
-
|
| 169 |
-
try {
|
| 170 |
-
const [complaint, assignmentItems, history, mediaItems, noteItems, aiData] = await Promise.all([
|
| 171 |
-
getComplaintDetail(id),
|
| 172 |
-
getComplaintAssignments(id),
|
| 173 |
-
getComplaintHistory(id),
|
| 174 |
-
getComplaintMedia(id),
|
| 175 |
-
getComplaintNotes(id),
|
| 176 |
-
getComplaintAiInsights(id),
|
| 177 |
-
]);
|
| 178 |
-
|
| 179 |
-
complaintCode = complaint.complaintCode;
|
| 180 |
-
complaintStatusValue = complaint.status;
|
| 181 |
-
complaintStatus = formatStatus(complaint.status);
|
| 182 |
-
complaintPriority = formatPriority(complaint.priorityLevel);
|
| 183 |
-
complaintDomain = complaint.domainName ?? complaintDomain;
|
| 184 |
-
complaintTitle = complaint.title;
|
| 185 |
-
complaintLocation = complaint.addressLine ?? complaint.landmark ?? complaintLocation;
|
| 186 |
-
complaintDescription = complaint.description ?? complaintDescription;
|
| 187 |
-
complaintDepartment = complaint.departmentName ?? complaintDepartment;
|
| 188 |
-
isEmergency = complaint.isEmergency;
|
| 189 |
-
assignments = assignmentItems;
|
| 190 |
-
media = mediaItems;
|
| 191 |
-
notes = noteItems;
|
| 192 |
-
aiInsights = aiData;
|
| 193 |
-
|
| 194 |
-
if (history.length > 0) {
|
| 195 |
-
internalTimeline = history.map((entry: ComplaintStatusHistoryItem) => ({
|
| 196 |
-
title: formatStatus(entry.newStatus),
|
| 197 |
-
time: formatDateTime(entry.createdAt),
|
| 198 |
-
note: entry.changeReason ?? `Complaint moved to ${formatStatus(entry.newStatus)}.`,
|
| 199 |
-
tone: timelineTone(entry.newStatus),
|
| 200 |
-
}));
|
| 201 |
-
}
|
| 202 |
-
} catch {
|
| 203 |
-
// Keep fallback values when backend is unavailable.
|
| 204 |
-
}
|
| 205 |
-
|
| 206 |
-
const complaintMeta = [
|
| 207 |
-
{ label: "Complaint ID", value: complaintCode },
|
| 208 |
-
{ label: "Current status", value: complaintStatus },
|
| 209 |
-
{ label: "Priority", value: complaintPriority },
|
| 210 |
-
{ label: "Domain", value: complaintDomain },
|
| 211 |
-
];
|
| 212 |
-
|
| 213 |
-
const complaintContext = [
|
| 214 |
-
`Issue: ${complaintTitle}`,
|
| 215 |
-
`Location: ${complaintLocation}`,
|
| 216 |
-
`Likely department: ${complaintDepartment}`,
|
| 217 |
-
complaintDescription,
|
| 218 |
-
];
|
| 219 |
-
|
| 220 |
-
const routingDecisionItems = [
|
| 221 |
-
`Current queue owner: ${assignments[0]?.departmentName ?? complaintDepartment}`,
|
| 222 |
-
`Operational mode: ${isEmergency ? "Emergency escalation path" : "Standard civic workflow"}`,
|
| 223 |
-
`Priority rationale: ${getPriorityRationale(complaintPriority, isEmergency)}`,
|
| 224 |
-
`Routing confidence: ${getRoutingConfidence(complaintDomain, assignments, complaintDepartment)}`,
|
| 225 |
-
`Current action state: ${getOperationalState(complaintStatus, assignments)}`,
|
| 226 |
-
];
|
| 227 |
-
|
| 228 |
-
return (
|
| 229 |
-
<AppShell>
|
| 230 |
-
<section className="grid gap-6 rounded-[2rem] bg-civic-primary px-6 py-8 text-white shadow-civic lg:grid-cols-[1.08fr_0.92fr] lg:px-8">
|
| 231 |
-
<div className="space-y-4">
|
| 232 |
-
<p className="text-sm font-semibold uppercase tracking-[0.22em] text-white/75">Internal complaint view</p>
|
| 233 |
-
<h1 className="text-4xl font-semibold tracking-tight">Manage one complaint from triage to resolution.</h1>
|
| 234 |
-
<p className="max-w-2xl text-sm leading-7 text-white/80">
|
| 235 |
-
Review context, route the work, assign the right team, and move the complaint through the next action.
|
| 236 |
-
</p>
|
| 237 |
-
</div>
|
| 238 |
-
|
| 239 |
-
<div className="grid gap-4 sm:grid-cols-2">
|
| 240 |
-
{complaintMeta.map((item) => (
|
| 241 |
-
<div key={item.label} className="rounded-3xl border border-white/15 bg-white/10 p-5 backdrop-blur">
|
| 242 |
-
<p className="text-sm font-semibold uppercase tracking-[0.16em] text-white/75">{item.label}</p>
|
| 243 |
-
<p className="mt-3 text-xl font-semibold text-white">{item.value}</p>
|
| 244 |
-
</div>
|
| 245 |
-
))}
|
| 246 |
-
</div>
|
| 247 |
-
</section>
|
| 248 |
-
|
| 249 |
-
<section className="grid gap-6 lg:grid-cols-[1.12fr_0.88fr]">
|
| 250 |
-
<div className="space-y-6">
|
| 251 |
-
<section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
|
| 252 |
-
<SectionHeader eyebrow="Complaint context" icon={ClipboardList} title="Citizen submission details" />
|
| 253 |
-
|
| 254 |
-
<div className="mt-5 space-y-3">
|
| 255 |
-
{complaintContext.map((item) => (
|
| 256 |
-
<div key={item} className="rounded-2xl bg-slate-50 px-4 py-3 text-sm leading-6 text-civic-text">
|
| 257 |
-
{item}
|
| 258 |
-
</div>
|
| 259 |
-
))}
|
| 260 |
-
</div>
|
| 261 |
-
</section>
|
| 262 |
-
|
| 263 |
-
<section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
|
| 264 |
-
<SectionHeader eyebrow="Lifecycle" icon={Clock3} iconClassName="bg-civic-primary text-white" title="Internal status timeline" />
|
| 265 |
-
|
| 266 |
-
<div className="mt-6 space-y-5">
|
| 267 |
-
{internalTimeline.map((entry, index) => (
|
| 268 |
-
<div key={`${entry.title}-${entry.time}-${index}`} className="grid gap-4 md:grid-cols-[auto_1fr] md:gap-5">
|
| 269 |
-
<div className="flex items-start gap-4 md:flex-col md:items-center">
|
| 270 |
-
<div className={["mt-1 h-4 w-4 rounded-full", entry.tone].join(" ")} />
|
| 271 |
-
{index !== internalTimeline.length - 1 ? <div className="hidden h-16 w-px bg-slate-200 md:block" /> : null}
|
| 272 |
-
</div>
|
| 273 |
-
<div className="rounded-3xl bg-slate-50 p-5">
|
| 274 |
-
<div className="flex flex-wrap items-center justify-between gap-3">
|
| 275 |
-
<h3 className="text-lg font-semibold text-civic-text">{entry.title}</h3>
|
| 276 |
-
<div className="inline-flex items-center gap-2 rounded-full bg-white px-3 py-2 text-xs font-semibold text-slate-700">
|
| 277 |
-
<Clock3 className="h-4 w-4" />
|
| 278 |
-
{entry.time}
|
| 279 |
-
</div>
|
| 280 |
-
</div>
|
| 281 |
-
<p className="mt-3 text-sm leading-6 text-civic-muted">{entry.note}</p>
|
| 282 |
-
</div>
|
| 283 |
-
</div>
|
| 284 |
-
))}
|
| 285 |
-
</div>
|
| 286 |
-
</section>
|
| 287 |
-
|
| 288 |
-
<section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
|
| 289 |
-
<SectionHeader eyebrow="Internal notes" icon={MessageSquareText} iconClassName="bg-amber-100 text-amber-700" title="Operator-only information" />
|
| 290 |
-
|
| 291 |
-
<div className="mt-5">
|
| 292 |
-
<AdminNotesPanel complaintId={id} initialNotes={notes} operatorId={user.id} />
|
| 293 |
-
</div>
|
| 294 |
-
</section>
|
| 295 |
-
|
| 296 |
-
<AdminAssignmentHistory assignments={assignments} />
|
| 297 |
-
</div>
|
| 298 |
-
|
| 299 |
-
<div className="space-y-6">
|
| 300 |
-
<section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
|
| 301 |
-
<SectionHeader eyebrow="Priority and routing" icon={ShieldAlert} iconClassName="bg-red-100 text-civic-danger" title="Department decision block" />
|
| 302 |
-
|
| 303 |
-
<div className="mt-5 space-y-3">
|
| 304 |
-
{routingDecisionItems.map((item) => (
|
| 305 |
-
<div key={item} className="rounded-2xl bg-slate-50 px-4 py-3 text-sm leading-6 text-civic-text">
|
| 306 |
-
{item}
|
| 307 |
-
</div>
|
| 308 |
-
))}
|
| 309 |
-
</div>
|
| 310 |
-
|
| 311 |
-
<div className="mt-5">
|
| 312 |
-
<AdminAssignAction complaintId={id} suggestedDepartment={complaintDepartment} operatorId={user.id} />
|
| 313 |
-
</div>
|
| 314 |
-
</section>
|
| 315 |
-
|
| 316 |
-
<ComplaintAiPanel insights={aiInsights} />
|
| 317 |
-
|
| 318 |
-
<section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
|
| 319 |
-
<SectionHeader eyebrow="Actions" icon={UserCog} title="Operator workflow controls" />
|
| 320 |
-
|
| 321 |
-
<div className="mt-5 space-y-3">
|
| 322 |
-
{internalActions.map((action) => (
|
| 323 |
-
<div key={action} className="rounded-2xl bg-slate-50 px-4 py-3 text-sm leading-6 text-civic-text">
|
| 324 |
-
{action}
|
| 325 |
-
</div>
|
| 326 |
-
))}
|
| 327 |
-
</div>
|
| 328 |
-
|
| 329 |
-
<div className="mt-5">
|
| 330 |
-
<AdminStatusActions complaintId={id} currentStatus={complaintStatusValue} operatorId={user.id} />
|
| 331 |
-
</div>
|
| 332 |
-
</section>
|
| 333 |
-
|
| 334 |
-
<ComplaintMediaGallery
|
| 335 |
-
items={media}
|
| 336 |
-
title="Complaint evidence"
|
| 337 |
-
emptyLabel="No evidence has been uploaded for this complaint yet."
|
| 338 |
-
/>
|
| 339 |
-
|
| 340 |
-
<ResolutionProofUploader complaintId={id} uploadedBy={user.id} label="Attach final proof from operations" />
|
| 341 |
-
|
| 342 |
-
<section className="rounded-[2rem] border border-emerald-200 bg-emerald-50 p-6 shadow-sm">
|
| 343 |
-
<div className="flex items-start gap-3">
|
| 344 |
-
<CheckCircle2 className="mt-0.5 h-5 w-5 text-emerald-700" />
|
| 345 |
-
<div>
|
| 346 |
-
<h2 className="text-lg font-semibold text-emerald-950">Internal-only context</h2>
|
| 347 |
-
<p className="mt-2 text-sm leading-6 text-emerald-900/85">
|
| 348 |
-
Notes, routing context, and assignment controls stay visible only to internal teams.
|
| 349 |
-
</p>
|
| 350 |
-
</div>
|
| 351 |
-
</div>
|
| 352 |
-
</section>
|
| 353 |
-
</div>
|
| 354 |
-
</section>
|
| 355 |
-
|
| 356 |
-
<section className="flex flex-wrap items-center justify-between gap-4 rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
|
| 357 |
-
<div className="space-y-2">
|
| 358 |
-
<p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Queue navigation</p>
|
| 359 |
-
<h2 className="text-2xl font-semibold text-civic-text">Jump back to the queue or move into emergency operations.</h2>
|
| 360 |
-
<p className="max-w-2xl text-sm leading-6 text-civic-muted">
|
| 361 |
-
Use the queue to continue triage work, or switch to emergency reporting when a fast-response situation needs immediate handling.
|
| 362 |
-
</p>
|
| 363 |
-
</div>
|
| 364 |
-
|
| 365 |
-
<div className="flex flex-wrap gap-3">
|
| 366 |
-
<Link
|
| 367 |
-
className="inline-flex rounded-full border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:border-civic-primary hover:text-civic-primary"
|
| 368 |
-
href="/dashboard/complaints"
|
| 369 |
-
>
|
| 370 |
-
Back to queue
|
| 371 |
-
</Link>
|
| 372 |
-
<Link
|
| 373 |
-
className="inline-flex items-center gap-2 rounded-full bg-slate-900 px-5 py-3 text-sm font-semibold text-white transition hover:-translate-y-0.5"
|
| 374 |
-
href="/emergency"
|
| 375 |
-
>
|
| 376 |
-
Open emergency reporting
|
| 377 |
-
<ArrowRight className="h-4 w-4" />
|
| 378 |
-
</Link>
|
| 379 |
-
</div>
|
| 380 |
-
</section>
|
| 381 |
-
</AppShell>
|
| 382 |
-
);
|
| 383 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/app/dashboard/complaints/page.tsx
DELETED
|
@@ -1,113 +0,0 @@
|
|
| 1 |
-
import { AppShell } from "@/components/app-shell";
|
| 2 |
-
import {
|
| 3 |
-
DashboardQueueClient,
|
| 4 |
-
type QueueComplaintCard,
|
| 5 |
-
} from "@/components/dashboard-queue-client";
|
| 6 |
-
import { getAllComplaints } from "@/lib/api";
|
| 7 |
-
import { requireRole } from "@/lib/auth";
|
| 8 |
-
|
| 9 |
-
const filterGroups = [
|
| 10 |
-
{
|
| 11 |
-
title: "Status" as const,
|
| 12 |
-
options: ["All", "Submitted", "Assigned", "In Progress", "Resolved", "Reopened"],
|
| 13 |
-
},
|
| 14 |
-
{
|
| 15 |
-
title: "Priority" as const,
|
| 16 |
-
options: ["All", "P1 Critical", "P2 High", "P3 Medium", "P4 Low"],
|
| 17 |
-
},
|
| 18 |
-
{
|
| 19 |
-
title: "Domain" as const,
|
| 20 |
-
options: ["All", "Roads", "Sanitation", "Water", "Electrical", "Infrastructure", "Environment"],
|
| 21 |
-
},
|
| 22 |
-
];
|
| 23 |
-
|
| 24 |
-
const fallbackComplaints: QueueComplaintCard[] = [
|
| 25 |
-
{
|
| 26 |
-
id: "CP-2026-00141",
|
| 27 |
-
complaintCode: "CP-2026-00141",
|
| 28 |
-
title: "Open manhole on high-traffic road",
|
| 29 |
-
domain: "Public Infrastructure and Amenities",
|
| 30 |
-
status: "Submitted",
|
| 31 |
-
priority: "P1 Critical",
|
| 32 |
-
department: "Municipal Maintenance",
|
| 33 |
-
location: "Temple Junction, Ward 4",
|
| 34 |
-
updatedAt: "5 minutes ago",
|
| 35 |
-
submittedAt: new Date(Date.now() - 5 * 60 * 1000).toISOString(),
|
| 36 |
-
},
|
| 37 |
-
{
|
| 38 |
-
id: "CP-2026-00139",
|
| 39 |
-
complaintCode: "CP-2026-00139",
|
| 40 |
-
title: "Live wire hanging near bus stop",
|
| 41 |
-
domain: "Electrical Hazard",
|
| 42 |
-
status: "Assigned",
|
| 43 |
-
priority: "P1 Critical",
|
| 44 |
-
department: "Electrical Safety Response",
|
| 45 |
-
location: "Bus Stand Road, Ward 9",
|
| 46 |
-
updatedAt: "12 minutes ago",
|
| 47 |
-
submittedAt: new Date(Date.now() - 12 * 60 * 1000).toISOString(),
|
| 48 |
-
},
|
| 49 |
-
{
|
| 50 |
-
id: "CP-2026-00124",
|
| 51 |
-
complaintCode: "CP-2026-00124",
|
| 52 |
-
title: "Sewage overflow near market entrance",
|
| 53 |
-
domain: "Water Supply, Sewerage, and Drainage",
|
| 54 |
-
status: "In Progress",
|
| 55 |
-
priority: "P2 High",
|
| 56 |
-
department: "Water and Sewerage",
|
| 57 |
-
location: "Market Road, Ward 12",
|
| 58 |
-
updatedAt: "1 hour ago",
|
| 59 |
-
submittedAt: new Date(Date.now() - 60 * 60 * 1000).toISOString(),
|
| 60 |
-
},
|
| 61 |
-
];
|
| 62 |
-
|
| 63 |
-
function formatStatus(status: string) {
|
| 64 |
-
return status
|
| 65 |
-
.split("_")
|
| 66 |
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
| 67 |
-
.join(" ");
|
| 68 |
-
}
|
| 69 |
-
|
| 70 |
-
function formatPriority(priority: string) {
|
| 71 |
-
switch (priority) {
|
| 72 |
-
case "P1":
|
| 73 |
-
return "P1 Critical";
|
| 74 |
-
case "P2":
|
| 75 |
-
return "P2 High";
|
| 76 |
-
case "P3":
|
| 77 |
-
return "P3 Medium";
|
| 78 |
-
default:
|
| 79 |
-
return "P4 Low";
|
| 80 |
-
}
|
| 81 |
-
}
|
| 82 |
-
|
| 83 |
-
export default async function DashboardComplaintsPage() {
|
| 84 |
-
const user = await requireRole(["department_operator", "municipal_admin"]);
|
| 85 |
-
let complaints: QueueComplaintCard[] = fallbackComplaints;
|
| 86 |
-
|
| 87 |
-
try {
|
| 88 |
-
const apiComplaints = await getAllComplaints();
|
| 89 |
-
|
| 90 |
-
if (apiComplaints.length > 0) {
|
| 91 |
-
complaints = apiComplaints.map((complaint) => ({
|
| 92 |
-
id: complaint.id,
|
| 93 |
-
complaintCode: complaint.complaintCode,
|
| 94 |
-
title: complaint.title,
|
| 95 |
-
domain: complaint.domainName ?? "Unclassified",
|
| 96 |
-
status: formatStatus(complaint.status),
|
| 97 |
-
priority: formatPriority(complaint.priorityLevel),
|
| 98 |
-
department: complaint.departmentName ?? "Department pending",
|
| 99 |
-
location: complaint.addressLine ?? complaint.landmark ?? "Location pending",
|
| 100 |
-
updatedAt: new Date(complaint.submittedAt).toLocaleString(),
|
| 101 |
-
submittedAt: complaint.submittedAt,
|
| 102 |
-
}));
|
| 103 |
-
}
|
| 104 |
-
} catch {
|
| 105 |
-
complaints = fallbackComplaints;
|
| 106 |
-
}
|
| 107 |
-
|
| 108 |
-
return (
|
| 109 |
-
<AppShell>
|
| 110 |
-
<DashboardQueueClient complaints={complaints} filterGroups={filterGroups} operatorId={user.id} />
|
| 111 |
-
</AppShell>
|
| 112 |
-
);
|
| 113 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/app/dashboard/page.tsx
DELETED
|
@@ -1,257 +0,0 @@
|
|
| 1 |
-
import Link from "next/link";
|
| 2 |
-
import {
|
| 3 |
-
ArrowRight,
|
| 4 |
-
Building2,
|
| 5 |
-
Clock3,
|
| 6 |
-
MapPinned,
|
| 7 |
-
Siren,
|
| 8 |
-
TimerReset,
|
| 9 |
-
} from "lucide-react";
|
| 10 |
-
import { AppShell } from "@/components/app-shell";
|
| 11 |
-
import { EmptyState } from "@/components/empty-state";
|
| 12 |
-
import { LiveSyncBadge } from "@/components/live-sync-badge";
|
| 13 |
-
import { PriorityBadge } from "@/components/priority-badge";
|
| 14 |
-
import { requireRole } from "@/lib/auth";
|
| 15 |
-
import { getDashboardSummary, type DashboardSummary } from "@/lib/api";
|
| 16 |
-
|
| 17 |
-
const focusItems = [
|
| 18 |
-
"Urgent complaints",
|
| 19 |
-
"Unaccepted assignments",
|
| 20 |
-
"SLA breaches",
|
| 21 |
-
"Pending field updates",
|
| 22 |
-
];
|
| 23 |
-
|
| 24 |
-
export default async function DashboardPage() {
|
| 25 |
-
await requireRole(["department_operator", "municipal_admin"]);
|
| 26 |
-
|
| 27 |
-
const fallbackSummary: DashboardSummary = {
|
| 28 |
-
overview: {
|
| 29 |
-
openComplaints: 184,
|
| 30 |
-
criticalComplaints: 7,
|
| 31 |
-
slaRisk: 19,
|
| 32 |
-
resolvedToday: 42,
|
| 33 |
-
},
|
| 34 |
-
urgentQueue: [
|
| 35 |
-
{
|
| 36 |
-
id: "sample-urgent-1",
|
| 37 |
-
complaintCode: "CP-2026-00141",
|
| 38 |
-
title: "Open manhole on high-traffic road",
|
| 39 |
-
domainName: "Public Infrastructure and Amenities",
|
| 40 |
-
priorityLevel: "P1",
|
| 41 |
-
location: "Temple Junction, Ward 4",
|
| 42 |
-
submittedAt: new Date().toISOString(),
|
| 43 |
-
},
|
| 44 |
-
{
|
| 45 |
-
id: "sample-urgent-2",
|
| 46 |
-
complaintCode: "CP-2026-00139",
|
| 47 |
-
title: "Live wire hanging near bus stop",
|
| 48 |
-
domainName: "Electrical Hazard",
|
| 49 |
-
priorityLevel: "P1",
|
| 50 |
-
location: "Bus Stand Road, Ward 9",
|
| 51 |
-
submittedAt: new Date().toISOString(),
|
| 52 |
-
},
|
| 53 |
-
],
|
| 54 |
-
departmentWorkload: [
|
| 55 |
-
{ departmentName: "Roads and Public Works", pendingCount: 31 },
|
| 56 |
-
{ departmentName: "Sanitation and Solid Waste", pendingCount: 28 },
|
| 57 |
-
{ departmentName: "Water and Sewerage", pendingCount: 44 },
|
| 58 |
-
{ departmentName: "Electrical and Street Lighting", pendingCount: 22 },
|
| 59 |
-
],
|
| 60 |
-
};
|
| 61 |
-
|
| 62 |
-
let summary: DashboardSummary = fallbackSummary;
|
| 63 |
-
|
| 64 |
-
try {
|
| 65 |
-
summary = await getDashboardSummary();
|
| 66 |
-
} catch {
|
| 67 |
-
summary = fallbackSummary;
|
| 68 |
-
}
|
| 69 |
-
|
| 70 |
-
const overviewStats = [
|
| 71 |
-
{ label: "Open complaints", value: String(summary.overview.openComplaints), tone: "text-civic-text" },
|
| 72 |
-
{ label: "Critical", value: String(summary.overview.criticalComplaints), tone: "text-civic-danger" },
|
| 73 |
-
{ label: "SLA risk", value: String(summary.overview.slaRisk), tone: "text-amber-700" },
|
| 74 |
-
{ label: "Resolved today", value: String(summary.overview.resolvedToday), tone: "text-emerald-700" },
|
| 75 |
-
];
|
| 76 |
-
|
| 77 |
-
return (
|
| 78 |
-
<AppShell>
|
| 79 |
-
<section className="relative overflow-hidden rounded-[2.5rem] bg-gradient-to-br from-[#09090b] via-civic-primary to-[#1e3a8a] px-8 py-10 text-white shadow-civic lg:px-10 lg:py-12">
|
| 80 |
-
<div className="absolute -left-32 -top-32 h-96 w-96 rounded-full bg-white/5 blur-[100px]" />
|
| 81 |
-
<div className="relative flex flex-wrap items-end justify-between gap-6">
|
| 82 |
-
<div className="space-y-4">
|
| 83 |
-
<p className="text-sm font-bold uppercase tracking-[0.25em] text-white/70">Operations</p>
|
| 84 |
-
<h1 className="text-5xl font-extrabold tracking-tight">Command Center</h1>
|
| 85 |
-
</div>
|
| 86 |
-
<div className="flex flex-wrap items-center gap-4">
|
| 87 |
-
<LiveSyncBadge label="Live Refresh" />
|
| 88 |
-
<Link
|
| 89 |
-
className="group inline-flex items-center gap-2 rounded-full border border-white/20 bg-white/5 px-6 py-3.5 text-sm font-semibold text-white backdrop-blur-md transition hover:border-white/40 hover:bg-white/10"
|
| 90 |
-
href="/dashboard/analytics"
|
| 91 |
-
>
|
| 92 |
-
Analytics
|
| 93 |
-
<ArrowRight className="h-4 w-4 transition-transform group-hover:translate-x-1" />
|
| 94 |
-
</Link>
|
| 95 |
-
<Link
|
| 96 |
-
className="group inline-flex items-center gap-2 rounded-full bg-white px-6 py-3.5 text-sm font-bold text-civic-primary shadow-xl transition hover:-translate-y-0.5 hover:shadow-2xl"
|
| 97 |
-
href="/dashboard/complaints"
|
| 98 |
-
>
|
| 99 |
-
Open Queue
|
| 100 |
-
<ArrowRight className="h-4 w-4 transition-transform group-hover:translate-x-1" />
|
| 101 |
-
</Link>
|
| 102 |
-
</div>
|
| 103 |
-
</div>
|
| 104 |
-
|
| 105 |
-
<div className="relative mt-8 grid gap-5 sm:grid-cols-2 xl:grid-cols-4">
|
| 106 |
-
{overviewStats.map((stat) => (
|
| 107 |
-
<div key={stat.label} className="group rounded-[2rem] border border-white/15 bg-white/10 p-6 backdrop-blur-xl transition hover:bg-white/15">
|
| 108 |
-
<p className="text-xs font-bold uppercase tracking-[0.2em] text-white/60">{stat.label}</p>
|
| 109 |
-
<p className={["mt-4 text-4xl font-extrabold tracking-tight", stat.tone.replace("text-civic-text", "text-white").replace("text-civic-danger", "text-rose-400").replace("text-amber-700", "text-amber-400").replace("text-emerald-700", "text-emerald-400")].join(" ")}>{stat.value}</p>
|
| 110 |
-
</div>
|
| 111 |
-
))}
|
| 112 |
-
</div>
|
| 113 |
-
</section>
|
| 114 |
-
|
| 115 |
-
<section className="grid gap-6 xl:grid-cols-[1.1fr_0.9fr]">
|
| 116 |
-
<section className="rounded-[2.5rem] border border-slate-200 bg-white/80 p-8 shadow-glass backdrop-blur-3xl transition hover:shadow-glass-hover">
|
| 117 |
-
<div className="flex flex-wrap items-center justify-between gap-4">
|
| 118 |
-
<div>
|
| 119 |
-
<p className="text-sm font-bold uppercase tracking-[0.25em] text-civic-secondary">Urgent queue</p>
|
| 120 |
-
<h2 className="mt-2 text-3xl font-extrabold text-slate-900">Priority Incidents</h2>
|
| 121 |
-
</div>
|
| 122 |
-
<div className="inline-flex animate-pulse items-center gap-2 rounded-full bg-rose-100 px-4 py-2 text-sm font-bold text-rose-700">
|
| 123 |
-
<Siren className="h-4 w-4" />
|
| 124 |
-
Live Feed
|
| 125 |
-
</div>
|
| 126 |
-
</div>
|
| 127 |
-
|
| 128 |
-
<div className="mt-8 space-y-5">
|
| 129 |
-
{summary.urgentQueue.length === 0 ? (
|
| 130 |
-
<EmptyState
|
| 131 |
-
description="Critical complaints will appear here as soon as they enter the queue."
|
| 132 |
-
icon={Siren}
|
| 133 |
-
title="No urgent complaints right now"
|
| 134 |
-
/>
|
| 135 |
-
) : (
|
| 136 |
-
summary.urgentQueue.map((item) => (
|
| 137 |
-
<article key={item.id} className="group relative overflow-hidden rounded-[2rem] border-2 border-rose-100 bg-gradient-to-br from-rose-50 to-white p-6 shadow-sm transition-all hover:-translate-y-1 hover:border-rose-200 hover:shadow-xl">
|
| 138 |
-
<div className="absolute right-0 top-0 h-32 w-32 -translate-y-8 translate-x-8 rounded-full bg-rose-200/50 blur-3xl transition group-hover:bg-rose-300/50" />
|
| 139 |
-
<div className="relative flex flex-wrap items-start justify-between gap-4">
|
| 140 |
-
<div className="space-y-3">
|
| 141 |
-
<div className="flex flex-wrap items-center gap-3">
|
| 142 |
-
<span className="rounded-full bg-white px-3.5 py-1 text-xs font-bold text-slate-700 shadow-sm">
|
| 143 |
-
{item.complaintCode}
|
| 144 |
-
</span>
|
| 145 |
-
<PriorityBadge priority={`${item.priorityLevel} ${item.priorityLevel === "P1" ? "Critical" : item.priorityLevel === "P2" ? "High" : item.priorityLevel === "P3" ? "Medium" : "Low"}`} />
|
| 146 |
-
</div>
|
| 147 |
-
<h3 className="text-2xl font-bold text-slate-900">{item.title}</h3>
|
| 148 |
-
<p className="text-sm font-medium text-slate-500">{item.domainName ?? "Unclassified"}</p>
|
| 149 |
-
</div>
|
| 150 |
-
|
| 151 |
-
<div className="rounded-full bg-white/80 px-4 py-2 text-xs font-bold text-rose-700 shadow-sm backdrop-blur-md">
|
| 152 |
-
{new Date(item.submittedAt).toLocaleString()}
|
| 153 |
-
</div>
|
| 154 |
-
</div>
|
| 155 |
-
|
| 156 |
-
<div className="relative mt-5 flex flex-wrap items-center gap-4 text-sm font-medium text-slate-600">
|
| 157 |
-
<div className="inline-flex items-center gap-2">
|
| 158 |
-
<MapPinned className="h-4 w-4 text-slate-400" />
|
| 159 |
-
{item.location ?? "Location pending"}
|
| 160 |
-
</div>
|
| 161 |
-
</div>
|
| 162 |
-
|
| 163 |
-
<div className="relative mt-6">
|
| 164 |
-
<Link
|
| 165 |
-
className="group/btn inline-flex items-center gap-2 rounded-full bg-slate-900 px-6 py-3 text-sm font-bold text-white shadow-md transition hover:bg-slate-800 hover:shadow-lg"
|
| 166 |
-
href={`/dashboard/complaints/${item.id}`}
|
| 167 |
-
>
|
| 168 |
-
Open Incident
|
| 169 |
-
<ArrowRight className="h-4 w-4 transition-transform group-hover/btn:translate-x-1" />
|
| 170 |
-
</Link>
|
| 171 |
-
</div>
|
| 172 |
-
</article>
|
| 173 |
-
))
|
| 174 |
-
)}
|
| 175 |
-
</div>
|
| 176 |
-
</section>
|
| 177 |
-
|
| 178 |
-
<div className="space-y-6">
|
| 179 |
-
<section className="rounded-[2.5rem] border border-slate-200 bg-white/80 p-8 shadow-glass backdrop-blur-3xl transition hover:shadow-glass-hover">
|
| 180 |
-
<div className="flex items-center gap-4">
|
| 181 |
-
<div className="flex h-14 w-14 items-center justify-center rounded-2xl bg-civic-secondary/15 text-civic-secondary shadow-inner">
|
| 182 |
-
<Building2 className="h-6 w-6" />
|
| 183 |
-
</div>
|
| 184 |
-
<div>
|
| 185 |
-
<p className="text-sm font-bold uppercase tracking-[0.2em] text-civic-secondary">Departments</p>
|
| 186 |
-
<h2 className="text-2xl font-extrabold text-slate-900">Current Workload</h2>
|
| 187 |
-
</div>
|
| 188 |
-
</div>
|
| 189 |
-
|
| 190 |
-
<div className="mt-8 space-y-4">
|
| 191 |
-
{summary.departmentWorkload.length === 0 ? (
|
| 192 |
-
<EmptyState
|
| 193 |
-
description="Department workload will appear here once complaints are assigned into active queues."
|
| 194 |
-
icon={Building2}
|
| 195 |
-
title="No department workload yet"
|
| 196 |
-
/>
|
| 197 |
-
) : (
|
| 198 |
-
summary.departmentWorkload.map((item) => (
|
| 199 |
-
<div key={item.departmentName} className="group rounded-3xl border border-slate-100 bg-white p-5 shadow-sm transition hover:-translate-y-1 hover:border-slate-200 hover:shadow-md">
|
| 200 |
-
<div className="flex items-center justify-between gap-4">
|
| 201 |
-
<h3 className="text-lg font-bold text-slate-800">{item.departmentName}</h3>
|
| 202 |
-
<span className="rounded-full bg-slate-100 px-4 py-1.5 text-xs font-bold text-slate-700 transition group-hover:bg-civic-secondary group-hover:text-white">
|
| 203 |
-
{item.pendingCount} pending
|
| 204 |
-
</span>
|
| 205 |
-
</div>
|
| 206 |
-
</div>
|
| 207 |
-
))
|
| 208 |
-
)}
|
| 209 |
-
</div>
|
| 210 |
-
</section>
|
| 211 |
-
|
| 212 |
-
<section className="rounded-[2.5rem] border border-slate-200 bg-white/80 p-8 shadow-glass backdrop-blur-3xl transition hover:shadow-glass-hover">
|
| 213 |
-
<div className="flex items-center gap-4">
|
| 214 |
-
<div className="flex h-14 w-14 items-center justify-center rounded-2xl bg-amber-100 text-amber-600 shadow-inner">
|
| 215 |
-
<TimerReset className="h-6 w-6" />
|
| 216 |
-
</div>
|
| 217 |
-
<div>
|
| 218 |
-
<p className="text-sm font-bold uppercase tracking-[0.2em] text-civic-secondary">Focus</p>
|
| 219 |
-
<h2 className="text-2xl font-extrabold text-slate-900">Immediate Attention</h2>
|
| 220 |
-
</div>
|
| 221 |
-
</div>
|
| 222 |
-
|
| 223 |
-
<div className="mt-8 grid gap-4 sm:grid-cols-2">
|
| 224 |
-
{focusItems.map((item) => (
|
| 225 |
-
<div key={item} className="rounded-2xl border border-slate-100 bg-white px-5 py-4 text-sm font-bold text-slate-700 shadow-sm transition hover:border-slate-200 hover:shadow-md">
|
| 226 |
-
{item}
|
| 227 |
-
</div>
|
| 228 |
-
))}
|
| 229 |
-
</div>
|
| 230 |
-
</section>
|
| 231 |
-
|
| 232 |
-
<section className="overflow-hidden rounded-[2.5rem] bg-gradient-to-br from-civic-primary to-blue-900 p-8 text-white shadow-xl transition hover:shadow-2xl">
|
| 233 |
-
<div className="flex items-center gap-4">
|
| 234 |
-
<div className="flex h-14 w-14 items-center justify-center rounded-2xl bg-white/20 shadow-inner backdrop-blur-md">
|
| 235 |
-
<Clock3 className="h-6 w-6 text-white" />
|
| 236 |
-
</div>
|
| 237 |
-
<div>
|
| 238 |
-
<p className="text-sm font-bold uppercase tracking-[0.2em] text-white/70">Queue</p>
|
| 239 |
-
<h2 className="text-2xl font-extrabold">Continue Operations</h2>
|
| 240 |
-
</div>
|
| 241 |
-
</div>
|
| 242 |
-
|
| 243 |
-
<div className="mt-8">
|
| 244 |
-
<Link
|
| 245 |
-
className="group inline-flex items-center gap-3 rounded-full bg-white px-7 py-4 text-sm font-bold text-civic-primary shadow-lg transition hover:-translate-y-1 hover:shadow-xl"
|
| 246 |
-
href="/dashboard/complaints"
|
| 247 |
-
>
|
| 248 |
-
Go to Complaint Queue
|
| 249 |
-
<ArrowRight className="h-5 w-5 transition-transform group-hover:translate-x-1" />
|
| 250 |
-
</Link>
|
| 251 |
-
</div>
|
| 252 |
-
</section>
|
| 253 |
-
</div>
|
| 254 |
-
</section>
|
| 255 |
-
</AppShell>
|
| 256 |
-
);
|
| 257 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/app/emergency/page.tsx
DELETED
|
@@ -1,176 +0,0 @@
|
|
| 1 |
-
import Link from "next/link";
|
| 2 |
-
import { AlertTriangle, Flame, RadioTower, ShieldAlert, Siren, Waves, Zap } from "lucide-react";
|
| 3 |
-
import { AppShell } from "@/components/app-shell";
|
| 4 |
-
|
| 5 |
-
const emergencyTypes = [
|
| 6 |
-
{
|
| 7 |
-
title: "Fire",
|
| 8 |
-
summary: "Fire outbreak, smoke, electrical fire, or public flame hazard.",
|
| 9 |
-
icon: Flame,
|
| 10 |
-
},
|
| 11 |
-
{
|
| 12 |
-
title: "Flood",
|
| 13 |
-
summary: "Flooding, dangerous water accumulation, or rapid overflow.",
|
| 14 |
-
icon: Waves,
|
| 15 |
-
},
|
| 16 |
-
{
|
| 17 |
-
title: "Electrical Hazard",
|
| 18 |
-
summary: "Live wire, transformer blast, or severe public electrical risk.",
|
| 19 |
-
icon: Zap,
|
| 20 |
-
},
|
| 21 |
-
{
|
| 22 |
-
title: "Rescue / Disaster",
|
| 23 |
-
summary: "Collapse, storm damage, trapped people, or multi-agency incident.",
|
| 24 |
-
icon: RadioTower,
|
| 25 |
-
},
|
| 26 |
-
];
|
| 27 |
-
|
| 28 |
-
const emergencyRules = [
|
| 29 |
-
"Emergency reports should take fewer steps than standard complaints.",
|
| 30 |
-
"Location must be captured first or confirmed manually.",
|
| 31 |
-
"Priority should default to P1 unless the complaint is invalid.",
|
| 32 |
-
"The system should route directly to the emergency queue.",
|
| 33 |
-
];
|
| 34 |
-
|
| 35 |
-
const minimalFields = [
|
| 36 |
-
"Emergency type",
|
| 37 |
-
"Photo or short video",
|
| 38 |
-
"Current location or exact pin",
|
| 39 |
-
"Short description of danger",
|
| 40 |
-
"Optional contact number",
|
| 41 |
-
];
|
| 42 |
-
|
| 43 |
-
export default function EmergencyPage() {
|
| 44 |
-
return (
|
| 45 |
-
<AppShell>
|
| 46 |
-
<section className="grid gap-6 rounded-[2rem] bg-civic-danger px-6 py-8 text-white shadow-civic lg:grid-cols-[1.08fr_0.92fr] lg:px-8">
|
| 47 |
-
<div className="space-y-4">
|
| 48 |
-
<div className="inline-flex items-center gap-2 rounded-full bg-white/10 px-4 py-2 text-sm font-medium text-white/90">
|
| 49 |
-
<Siren className="h-4 w-4" />
|
| 50 |
-
Emergency reporting flow
|
| 51 |
-
</div>
|
| 52 |
-
<h1 className="text-4xl font-semibold tracking-tight">Use the fastest possible flow for urgent public danger.</h1>
|
| 53 |
-
<p className="max-w-2xl text-sm leading-7 text-white/85">
|
| 54 |
-
Report fire, flood, electrical danger, or rescue situations with the fewest possible steps so the response team can act quickly.
|
| 55 |
-
</p>
|
| 56 |
-
</div>
|
| 57 |
-
|
| 58 |
-
<div className="rounded-[1.75rem] border border-white/15 bg-white/10 p-5 backdrop-blur">
|
| 59 |
-
<p className="text-sm font-semibold uppercase tracking-[0.22em] text-white/75">Default response behavior</p>
|
| 60 |
-
<div className="mt-4 space-y-3">
|
| 61 |
-
{[
|
| 62 |
-
"Priority set to P1 Critical",
|
| 63 |
-
"Emergency queue routing",
|
| 64 |
-
"Immediate operator visibility",
|
| 65 |
-
"Stronger notification path",
|
| 66 |
-
].map((item) => (
|
| 67 |
-
<div key={item} className="rounded-2xl bg-white/10 px-4 py-3 text-sm font-medium text-white">
|
| 68 |
-
{item}
|
| 69 |
-
</div>
|
| 70 |
-
))}
|
| 71 |
-
</div>
|
| 72 |
-
</div>
|
| 73 |
-
</section>
|
| 74 |
-
|
| 75 |
-
<section className="grid gap-6 lg:grid-cols-[1.02fr_0.98fr]">
|
| 76 |
-
<section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
|
| 77 |
-
<div className="flex flex-wrap items-start justify-between gap-4">
|
| 78 |
-
<div>
|
| 79 |
-
<p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Emergency type</p>
|
| 80 |
-
<h2 className="mt-2 text-2xl font-semibold text-civic-text">Choose the urgent incident category</h2>
|
| 81 |
-
</div>
|
| 82 |
-
<div className="rounded-full bg-red-50 px-4 py-2 text-sm font-semibold text-red-700">
|
| 83 |
-
High priority flow
|
| 84 |
-
</div>
|
| 85 |
-
</div>
|
| 86 |
-
|
| 87 |
-
<div className="mt-6 grid gap-4 md:grid-cols-2">
|
| 88 |
-
{emergencyTypes.map(({ title, summary, icon: Icon }) => (
|
| 89 |
-
<button
|
| 90 |
-
key={title}
|
| 91 |
-
type="button"
|
| 92 |
-
className="group rounded-3xl border border-red-100 bg-red-50/50 p-5 text-left transition duration-200 hover:-translate-y-1 hover:border-civic-danger hover:bg-white hover:shadow-civic"
|
| 93 |
-
>
|
| 94 |
-
<div className="flex items-start justify-between gap-4">
|
| 95 |
-
<div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-white text-civic-danger">
|
| 96 |
-
<Icon className="h-6 w-6" />
|
| 97 |
-
</div>
|
| 98 |
-
<span className="rounded-full bg-white px-3 py-1 text-xs font-semibold text-civic-danger">Select</span>
|
| 99 |
-
</div>
|
| 100 |
-
<h3 className="mt-4 text-lg font-semibold text-civic-text">{title}</h3>
|
| 101 |
-
<p className="mt-2 text-sm leading-6 text-civic-muted">{summary}</p>
|
| 102 |
-
</button>
|
| 103 |
-
))}
|
| 104 |
-
</div>
|
| 105 |
-
</section>
|
| 106 |
-
|
| 107 |
-
<div className="space-y-6">
|
| 108 |
-
<section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
|
| 109 |
-
<div className="flex items-center gap-3">
|
| 110 |
-
<div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-civic-danger/10 text-civic-danger">
|
| 111 |
-
<ShieldAlert className="h-5 w-5" />
|
| 112 |
-
</div>
|
| 113 |
-
<div>
|
| 114 |
-
<p className="text-sm font-semibold uppercase tracking-[0.16em] text-civic-secondary">Form scope</p>
|
| 115 |
-
<h2 className="text-xl font-semibold text-civic-text">Minimal fields for emergency submission</h2>
|
| 116 |
-
</div>
|
| 117 |
-
</div>
|
| 118 |
-
|
| 119 |
-
<div className="mt-5 space-y-3">
|
| 120 |
-
{minimalFields.map((field) => (
|
| 121 |
-
<div key={field} className="rounded-2xl bg-slate-50 px-4 py-3 text-sm font-medium text-civic-text">
|
| 122 |
-
{field}
|
| 123 |
-
</div>
|
| 124 |
-
))}
|
| 125 |
-
</div>
|
| 126 |
-
</section>
|
| 127 |
-
|
| 128 |
-
<section className="rounded-[2rem] border border-amber-200 bg-amber-50 p-6 shadow-sm">
|
| 129 |
-
<div className="flex items-start gap-3">
|
| 130 |
-
<AlertTriangle className="mt-0.5 h-5 w-5 text-amber-700" />
|
| 131 |
-
<div>
|
| 132 |
-
<h2 className="text-lg font-semibold text-amber-950">Emergency reporting principles</h2>
|
| 133 |
-
<p className="mt-2 text-sm leading-6 text-amber-900/85">
|
| 134 |
-
This flow stays shorter than normal reporting and focuses only on what responders need first.
|
| 135 |
-
</p>
|
| 136 |
-
</div>
|
| 137 |
-
</div>
|
| 138 |
-
|
| 139 |
-
<div className="mt-5 space-y-3">
|
| 140 |
-
{emergencyRules.map((rule) => (
|
| 141 |
-
<div key={rule} className="rounded-2xl bg-white/75 px-4 py-3 text-sm leading-6 text-amber-950">
|
| 142 |
-
{rule}
|
| 143 |
-
</div>
|
| 144 |
-
))}
|
| 145 |
-
</div>
|
| 146 |
-
</section>
|
| 147 |
-
</div>
|
| 148 |
-
</section>
|
| 149 |
-
|
| 150 |
-
<section className="flex flex-wrap items-center justify-between gap-4 rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
|
| 151 |
-
<div>
|
| 152 |
-
<p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Switch reporting modes</p>
|
| 153 |
-
<h2 className="mt-2 text-2xl font-semibold text-civic-text">Return to the normal report flow or operations dashboard.</h2>
|
| 154 |
-
<p className="mt-2 max-w-2xl text-sm leading-6 text-civic-muted">
|
| 155 |
-
Use the standard reporting flow for non-emergency complaints, or return to the dashboard to monitor active work.
|
| 156 |
-
</p>
|
| 157 |
-
</div>
|
| 158 |
-
|
| 159 |
-
<div className="flex flex-wrap gap-3">
|
| 160 |
-
<Link
|
| 161 |
-
className="inline-flex rounded-full border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:border-civic-primary hover:text-civic-primary"
|
| 162 |
-
href="/report"
|
| 163 |
-
>
|
| 164 |
-
Back to normal report flow
|
| 165 |
-
</Link>
|
| 166 |
-
<Link
|
| 167 |
-
className="inline-flex rounded-full bg-slate-900 px-5 py-3 text-sm font-semibold text-white transition hover:-translate-y-0.5"
|
| 168 |
-
href="/dashboard"
|
| 169 |
-
>
|
| 170 |
-
Return to dashboard
|
| 171 |
-
</Link>
|
| 172 |
-
</div>
|
| 173 |
-
</section>
|
| 174 |
-
</AppShell>
|
| 175 |
-
);
|
| 176 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/app/globals.css
DELETED
|
@@ -1,61 +0,0 @@
|
|
| 1 |
-
@tailwind base;
|
| 2 |
-
@tailwind components;
|
| 3 |
-
@tailwind utilities;
|
| 4 |
-
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
|
| 5 |
-
@import "mapbox-gl/dist/mapbox-gl.css";
|
| 6 |
-
|
| 7 |
-
:root {
|
| 8 |
-
color-scheme: light;
|
| 9 |
-
}
|
| 10 |
-
|
| 11 |
-
html {
|
| 12 |
-
scroll-behavior: smooth;
|
| 13 |
-
}
|
| 14 |
-
|
| 15 |
-
body {
|
| 16 |
-
margin: 0;
|
| 17 |
-
min-height: 100vh;
|
| 18 |
-
background:
|
| 19 |
-
radial-gradient(circle at top left, rgba(20, 184, 166, 0.08), transparent 45%),
|
| 20 |
-
radial-gradient(circle at bottom right, rgba(15, 76, 129, 0.08), transparent 40%),
|
| 21 |
-
linear-gradient(to bottom, #f8fafc, #f1f5f9);
|
| 22 |
-
background-attachment: fixed;
|
| 23 |
-
color: #0f172a;
|
| 24 |
-
font-family: 'Inter', Arial, Helvetica, sans-serif;
|
| 25 |
-
letter-spacing: -0.015em;
|
| 26 |
-
overflow-x: hidden;
|
| 27 |
-
-webkit-font-smoothing: antialiased;
|
| 28 |
-
}
|
| 29 |
-
|
| 30 |
-
/* Premium Scrollbar */
|
| 31 |
-
::-webkit-scrollbar {
|
| 32 |
-
width: 8px;
|
| 33 |
-
height: 8px;
|
| 34 |
-
}
|
| 35 |
-
|
| 36 |
-
::-webkit-scrollbar-track {
|
| 37 |
-
background: transparent;
|
| 38 |
-
}
|
| 39 |
-
|
| 40 |
-
::-webkit-scrollbar-thumb {
|
| 41 |
-
background: rgba(100, 116, 139, 0.3);
|
| 42 |
-
border-radius: 10px;
|
| 43 |
-
}
|
| 44 |
-
|
| 45 |
-
::-webkit-scrollbar-thumb:hover {
|
| 46 |
-
background: rgba(100, 116, 139, 0.5);
|
| 47 |
-
}
|
| 48 |
-
|
| 49 |
-
* {
|
| 50 |
-
box-sizing: border-box;
|
| 51 |
-
}
|
| 52 |
-
|
| 53 |
-
a {
|
| 54 |
-
color: inherit;
|
| 55 |
-
text-decoration: none;
|
| 56 |
-
}
|
| 57 |
-
|
| 58 |
-
a {
|
| 59 |
-
color: inherit;
|
| 60 |
-
text-decoration: none;
|
| 61 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/app/layout.tsx
DELETED
|
@@ -1,19 +0,0 @@
|
|
| 1 |
-
import type { Metadata } from "next";
|
| 2 |
-
import "./globals.css";
|
| 3 |
-
|
| 4 |
-
export const metadata: Metadata = {
|
| 5 |
-
title: "Civic Platform",
|
| 6 |
-
description: "Crowdsourced civic issue reporting and resolution system",
|
| 7 |
-
};
|
| 8 |
-
|
| 9 |
-
export default function RootLayout({
|
| 10 |
-
children,
|
| 11 |
-
}: Readonly<{
|
| 12 |
-
children: React.ReactNode;
|
| 13 |
-
}>) {
|
| 14 |
-
return (
|
| 15 |
-
<html lang="en">
|
| 16 |
-
<body suppressHydrationWarning>{children}</body>
|
| 17 |
-
</html>
|
| 18 |
-
);
|
| 19 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/app/login/page.tsx
DELETED
|
@@ -1,40 +0,0 @@
|
|
| 1 |
-
import { redirect } from "next/navigation";
|
| 2 |
-
import { ShieldCheck } from "lucide-react";
|
| 3 |
-
import { AppShell } from "@/components/app-shell";
|
| 4 |
-
import { LoginForm } from "@/components/login-form";
|
| 5 |
-
import { getCurrentUser } from "@/lib/auth";
|
| 6 |
-
|
| 7 |
-
export default async function LoginPage() {
|
| 8 |
-
const user = await getCurrentUser();
|
| 9 |
-
|
| 10 |
-
if (user) {
|
| 11 |
-
redirect(user.role === "citizen" ? "/complaints" : user.role === "field_officer" ? "/tasks" : "/dashboard");
|
| 12 |
-
}
|
| 13 |
-
|
| 14 |
-
return (
|
| 15 |
-
<AppShell>
|
| 16 |
-
<div className="mx-auto flex min-h-[75vh] w-full max-w-[420px] flex-col justify-center py-10 px-4 sm:px-0">
|
| 17 |
-
<div className="mb-8 flex flex-col items-center text-center">
|
| 18 |
-
<div className="mb-6 flex h-16 w-16 items-center justify-center rounded-[1.5rem] bg-gradient-to-br from-civic-primary to-[#0b5d87] text-white shadow-xl shadow-civic-primary/20">
|
| 19 |
-
<ShieldCheck className="h-8 w-8" />
|
| 20 |
-
</div>
|
| 21 |
-
<h1 className="text-3xl font-extrabold tracking-tight text-slate-900">
|
| 22 |
-
Welcome back
|
| 23 |
-
</h1>
|
| 24 |
-
<p className="mt-2 text-base text-slate-500">
|
| 25 |
-
Sign in or create an account to participate
|
| 26 |
-
</p>
|
| 27 |
-
</div>
|
| 28 |
-
|
| 29 |
-
<div className="relative w-full">
|
| 30 |
-
{/* Subtle background glow */}
|
| 31 |
-
<div className="absolute -inset-1 rounded-[2.5rem] bg-gradient-to-br from-civic-primary/15 via-transparent to-civic-secondary/10 blur-xl"></div>
|
| 32 |
-
|
| 33 |
-
<div className="relative rounded-[2.5rem] border border-white/60 bg-white/70 p-6 shadow-2xl shadow-sky-900/5 backdrop-blur-2xl sm:p-8">
|
| 34 |
-
<LoginForm />
|
| 35 |
-
</div>
|
| 36 |
-
</div>
|
| 37 |
-
</div>
|
| 38 |
-
</AppShell>
|
| 39 |
-
);
|
| 40 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/app/map/page.tsx
DELETED
|
@@ -1,59 +0,0 @@
|
|
| 1 |
-
import { AppShell } from "@/components/app-shell";
|
| 2 |
-
import { IssueMapBoard } from "@/components/issue-map-board";
|
| 3 |
-
import { getCurrentUser } from "@/lib/auth";
|
| 4 |
-
import {
|
| 5 |
-
getMapComplaints,
|
| 6 |
-
getPublicHotspots,
|
| 7 |
-
type ComplaintHotspotItem,
|
| 8 |
-
type ComplaintMapItem,
|
| 9 |
-
} from "@/lib/api";
|
| 10 |
-
|
| 11 |
-
const fallbackComplaints: ComplaintMapItem[] = [];
|
| 12 |
-
const fallbackHotspots: ComplaintHotspotItem[] = [];
|
| 13 |
-
|
| 14 |
-
export default async function MapPage() {
|
| 15 |
-
const user = await getCurrentUser();
|
| 16 |
-
const mapboxToken = process.env.MAPBOX_TOKEN ?? process.env.NEXT_PUBLIC_MAPBOX_TOKEN;
|
| 17 |
-
const isOperationsView =
|
| 18 |
-
user?.role === "department_operator" || user?.role === "municipal_admin" || user?.role === "field_officer";
|
| 19 |
-
|
| 20 |
-
let complaints = fallbackComplaints;
|
| 21 |
-
let hotspots = fallbackHotspots;
|
| 22 |
-
|
| 23 |
-
try {
|
| 24 |
-
[complaints, hotspots] = await Promise.all([
|
| 25 |
-
getMapComplaints({ publicOnly: !isOperationsView }),
|
| 26 |
-
getPublicHotspots(),
|
| 27 |
-
]);
|
| 28 |
-
} catch {
|
| 29 |
-
complaints = fallbackComplaints;
|
| 30 |
-
hotspots = fallbackHotspots;
|
| 31 |
-
}
|
| 32 |
-
|
| 33 |
-
return (
|
| 34 |
-
<AppShell>
|
| 35 |
-
<section className="space-y-3">
|
| 36 |
-
<p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">
|
| 37 |
-
{isOperationsView ? "Operations map" : "Public map"}
|
| 38 |
-
</p>
|
| 39 |
-
<h1 className="text-4xl font-semibold tracking-tight text-civic-text">
|
| 40 |
-
{isOperationsView
|
| 41 |
-
? "Monitor live civic issue activity with city-wide visibility."
|
| 42 |
-
: "Explore active civic issues and hotspots across the city."}
|
| 43 |
-
</h1>
|
| 44 |
-
<p className="max-w-3xl text-sm leading-7 text-civic-muted">
|
| 45 |
-
{isOperationsView
|
| 46 |
-
? "Use the live operations map to spot workload clusters, emergency incidents, and active complaint zones."
|
| 47 |
-
: "Residents can browse public-safe complaint activity, see active clusters, and understand where issues are already being addressed."}
|
| 48 |
-
</p>
|
| 49 |
-
</section>
|
| 50 |
-
|
| 51 |
-
<IssueMapBoard
|
| 52 |
-
complaints={complaints}
|
| 53 |
-
hotspots={hotspots}
|
| 54 |
-
mapboxToken={mapboxToken}
|
| 55 |
-
mode={isOperationsView ? "operations" : "public"}
|
| 56 |
-
/>
|
| 57 |
-
</AppShell>
|
| 58 |
-
);
|
| 59 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/app/notifications/page.tsx
DELETED
|
@@ -1,35 +0,0 @@
|
|
| 1 |
-
import { AppShell } from "@/components/app-shell";
|
| 2 |
-
import { LiveSyncBadge } from "@/components/live-sync-badge";
|
| 3 |
-
import { NotificationCenter } from "@/components/notification-center";
|
| 4 |
-
import { requireUser } from "@/lib/auth";
|
| 5 |
-
import { getNotifications, type ComplaintNotificationItem } from "@/lib/api";
|
| 6 |
-
|
| 7 |
-
const fallbackNotifications: ComplaintNotificationItem[] = [];
|
| 8 |
-
|
| 9 |
-
export default async function NotificationsPage() {
|
| 10 |
-
const user = await requireUser();
|
| 11 |
-
let notifications = fallbackNotifications;
|
| 12 |
-
|
| 13 |
-
try {
|
| 14 |
-
notifications = await getNotifications(user.id);
|
| 15 |
-
} catch {
|
| 16 |
-
notifications = fallbackNotifications;
|
| 17 |
-
}
|
| 18 |
-
|
| 19 |
-
return (
|
| 20 |
-
<AppShell>
|
| 21 |
-
<section className="space-y-3">
|
| 22 |
-
<div className="flex flex-wrap items-center justify-between gap-3">
|
| 23 |
-
<p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Notifications</p>
|
| 24 |
-
<LiveSyncBadge label="Inbox live refresh" />
|
| 25 |
-
</div>
|
| 26 |
-
<h1 className="text-4xl font-semibold tracking-tight text-civic-text">See the latest updates on your work and complaints.</h1>
|
| 27 |
-
<p className="max-w-3xl text-sm leading-7 text-civic-muted">
|
| 28 |
-
Assignment, field progress, reopening, and citizen verification updates appear here.
|
| 29 |
-
</p>
|
| 30 |
-
</section>
|
| 31 |
-
|
| 32 |
-
<NotificationCenter notifications={notifications} userId={user.id} />
|
| 33 |
-
</AppShell>
|
| 34 |
-
);
|
| 35 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/app/page.tsx
DELETED
|
@@ -1,15 +0,0 @@
|
|
| 1 |
-
import { DomainGrid } from "@/components/domain-grid";
|
| 2 |
-
import { Hero } from "@/components/hero";
|
| 3 |
-
import { AppShell } from "@/components/app-shell";
|
| 4 |
-
|
| 5 |
-
export default function Home() {
|
| 6 |
-
return (
|
| 7 |
-
<AppShell>
|
| 8 |
-
<Hero />
|
| 9 |
-
|
| 10 |
-
<section id="domains" className="py-6">
|
| 11 |
-
<DomainGrid />
|
| 12 |
-
</section>
|
| 13 |
-
</AppShell>
|
| 14 |
-
);
|
| 15 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/app/report/location/page.tsx
DELETED
|
@@ -1,170 +0,0 @@
|
|
| 1 |
-
import Link from "next/link";
|
| 2 |
-
import { Crosshair, MapPinned, Navigation, ShieldAlert, Sparkles } from "lucide-react";
|
| 3 |
-
import { AppShell } from "@/components/app-shell";
|
| 4 |
-
import { FlowSteps } from "@/components/flow-steps";
|
| 5 |
-
|
| 6 |
-
const steps = ["Choose domain", "Add evidence", "Confirm location", "Review", "Submit"];
|
| 7 |
-
|
| 8 |
-
const locationChecks = [
|
| 9 |
-
"Use current GPS for the quickest routing path.",
|
| 10 |
-
"If GPS is weak, move the map pin to the exact road, lane, or landmark.",
|
| 11 |
-
"Mention a nearby landmark so field staff can find the problem faster.",
|
| 12 |
-
"Emergency incidents should be pinned as precisely as possible.",
|
| 13 |
-
];
|
| 14 |
-
|
| 15 |
-
const locationFields = [
|
| 16 |
-
"Latitude and longitude",
|
| 17 |
-
"Resolved address",
|
| 18 |
-
"Ward or zone",
|
| 19 |
-
"Nearby landmark",
|
| 20 |
-
"Road or locality name",
|
| 21 |
-
"Manual pin adjustment status",
|
| 22 |
-
];
|
| 23 |
-
|
| 24 |
-
export default function ReportLocationPage() {
|
| 25 |
-
return (
|
| 26 |
-
<AppShell>
|
| 27 |
-
<section className="space-y-6">
|
| 28 |
-
<div className="space-y-3">
|
| 29 |
-
<p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Report flow</p>
|
| 30 |
-
<h1 className="text-4xl font-semibold tracking-tight text-civic-text">Confirm the issue location</h1>
|
| 31 |
-
<p className="max-w-3xl text-sm leading-7 text-civic-muted">
|
| 32 |
-
This step helps the system assign the correct ward, identify the responsible department, and improve
|
| 33 |
-
routing accuracy for field teams.
|
| 34 |
-
</p>
|
| 35 |
-
</div>
|
| 36 |
-
|
| 37 |
-
<FlowSteps currentStep={3} steps={steps} />
|
| 38 |
-
</section>
|
| 39 |
-
|
| 40 |
-
<section className="grid gap-6 lg:grid-cols-[1.15fr_0.85fr]">
|
| 41 |
-
<div className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm sm:p-8">
|
| 42 |
-
<div className="flex flex-wrap items-start justify-between gap-4">
|
| 43 |
-
<div className="space-y-2">
|
| 44 |
-
<p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Step 3</p>
|
| 45 |
-
<h2 className="text-2xl font-semibold text-civic-text">Map and address confirmation</h2>
|
| 46 |
-
<p className="max-w-2xl text-sm leading-6 text-civic-muted">
|
| 47 |
-
In the MVP, this screen should open the user location, show a map preview, and let the citizen correct
|
| 48 |
-
the pin before moving to review.
|
| 49 |
-
</p>
|
| 50 |
-
</div>
|
| 51 |
-
<div className="rounded-full bg-slate-100 px-4 py-2 text-sm font-semibold text-slate-700">
|
| 52 |
-
Routing-ready
|
| 53 |
-
</div>
|
| 54 |
-
</div>
|
| 55 |
-
|
| 56 |
-
<div className="mt-6 rounded-[1.75rem] border border-slate-200 bg-slate-50 p-5">
|
| 57 |
-
<div className="flex items-center gap-3">
|
| 58 |
-
<div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-civic-primary text-white">
|
| 59 |
-
<MapPinned className="h-6 w-6" />
|
| 60 |
-
</div>
|
| 61 |
-
<div>
|
| 62 |
-
<p className="text-sm font-semibold uppercase tracking-[0.16em] text-civic-secondary">Interactive map</p>
|
| 63 |
-
<h3 className="text-lg font-semibold text-civic-text">Pinned complaint location</h3>
|
| 64 |
-
</div>
|
| 65 |
-
</div>
|
| 66 |
-
|
| 67 |
-
<div className="mt-5 flex min-h-[22rem] items-center justify-center rounded-[1.5rem] border border-dashed border-slate-300 bg-[linear-gradient(135deg,rgba(15,76,129,0.05),rgba(20,184,166,0.08))] p-6 text-center">
|
| 68 |
-
<div className="max-w-md space-y-3">
|
| 69 |
-
<div className="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-white shadow-sm">
|
| 70 |
-
<Navigation className="h-6 w-6 text-civic-primary" />
|
| 71 |
-
</div>
|
| 72 |
-
<h4 className="text-xl font-semibold text-civic-text">Map preview placeholder</h4>
|
| 73 |
-
<p className="text-sm leading-6 text-civic-muted">
|
| 74 |
-
This area will use Leaflet with OpenStreetMap tiles, current location detection, drag-to-adjust pin,
|
| 75 |
-
and reverse geocoding for address confirmation.
|
| 76 |
-
</p>
|
| 77 |
-
</div>
|
| 78 |
-
</div>
|
| 79 |
-
|
| 80 |
-
<div className="mt-5 flex flex-wrap gap-3">
|
| 81 |
-
<button
|
| 82 |
-
type="button"
|
| 83 |
-
className="inline-flex items-center gap-2 rounded-full bg-civic-primary px-5 py-3 text-sm font-semibold text-white transition hover:-translate-y-0.5"
|
| 84 |
-
>
|
| 85 |
-
<Crosshair className="h-4 w-4" />
|
| 86 |
-
Use current location
|
| 87 |
-
</button>
|
| 88 |
-
<button
|
| 89 |
-
type="button"
|
| 90 |
-
className="inline-flex items-center gap-2 rounded-full border border-slate-200 bg-white px-5 py-3 text-sm font-semibold text-slate-700 transition hover:border-civic-primary hover:text-civic-primary"
|
| 91 |
-
>
|
| 92 |
-
<MapPinned className="h-4 w-4" />
|
| 93 |
-
Adjust map pin
|
| 94 |
-
</button>
|
| 95 |
-
</div>
|
| 96 |
-
</div>
|
| 97 |
-
</div>
|
| 98 |
-
|
| 99 |
-
<div className="space-y-6">
|
| 100 |
-
<section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
|
| 101 |
-
<div className="flex items-center gap-3">
|
| 102 |
-
<div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-civic-secondary/15 text-civic-secondary">
|
| 103 |
-
<Sparkles className="h-5 w-5" />
|
| 104 |
-
</div>
|
| 105 |
-
<div>
|
| 106 |
-
<p className="text-sm font-semibold uppercase tracking-[0.16em] text-civic-secondary">Stored data</p>
|
| 107 |
-
<h2 className="text-xl font-semibold text-civic-text">Location fields for the complaint</h2>
|
| 108 |
-
</div>
|
| 109 |
-
</div>
|
| 110 |
-
|
| 111 |
-
<div className="mt-5 grid gap-3">
|
| 112 |
-
{locationFields.map((field) => (
|
| 113 |
-
<div key={field} className="rounded-2xl bg-slate-50 px-4 py-3 text-sm font-medium text-civic-text">
|
| 114 |
-
{field}
|
| 115 |
-
</div>
|
| 116 |
-
))}
|
| 117 |
-
</div>
|
| 118 |
-
</section>
|
| 119 |
-
|
| 120 |
-
<section className="rounded-[2rem] border border-amber-200 bg-amber-50 p-6 shadow-sm">
|
| 121 |
-
<div className="flex items-start gap-3">
|
| 122 |
-
<ShieldAlert className="mt-0.5 h-5 w-5 text-amber-700" />
|
| 123 |
-
<div>
|
| 124 |
-
<h2 className="text-lg font-semibold text-amber-950">Accuracy matters for emergency routing</h2>
|
| 125 |
-
<p className="mt-2 text-sm leading-6 text-amber-900/85">
|
| 126 |
-
A precise location helps responders find the incident faster, especially in flood, fire, collapse,
|
| 127 |
-
or live wire complaints.
|
| 128 |
-
</p>
|
| 129 |
-
</div>
|
| 130 |
-
</div>
|
| 131 |
-
|
| 132 |
-
<div className="mt-5 space-y-3">
|
| 133 |
-
{locationChecks.map((item) => (
|
| 134 |
-
<div key={item} className="rounded-2xl bg-white/70 px-4 py-3 text-sm leading-6 text-amber-950">
|
| 135 |
-
{item}
|
| 136 |
-
</div>
|
| 137 |
-
))}
|
| 138 |
-
</div>
|
| 139 |
-
</section>
|
| 140 |
-
</div>
|
| 141 |
-
</section>
|
| 142 |
-
|
| 143 |
-
<section className="flex flex-wrap items-center justify-between gap-4 rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
|
| 144 |
-
<div>
|
| 145 |
-
<p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Next step</p>
|
| 146 |
-
<h2 className="mt-2 text-2xl font-semibold text-civic-text">Review complaint details before submission</h2>
|
| 147 |
-
<p className="mt-2 max-w-2xl text-sm leading-6 text-civic-muted">
|
| 148 |
-
After location is confirmed, the user should see a review screen with domain, evidence, location, and the
|
| 149 |
-
initial routing preview.
|
| 150 |
-
</p>
|
| 151 |
-
</div>
|
| 152 |
-
|
| 153 |
-
<div className="flex flex-wrap gap-3">
|
| 154 |
-
<Link
|
| 155 |
-
className="inline-flex rounded-full border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:border-civic-primary hover:text-civic-primary"
|
| 156 |
-
href="/report"
|
| 157 |
-
>
|
| 158 |
-
Back to report
|
| 159 |
-
</Link>
|
| 160 |
-
<Link
|
| 161 |
-
className="inline-flex rounded-full bg-civic-primary px-5 py-3 text-sm font-semibold text-white transition hover:-translate-y-0.5"
|
| 162 |
-
href="/report/review"
|
| 163 |
-
>
|
| 164 |
-
Continue to review
|
| 165 |
-
</Link>
|
| 166 |
-
</div>
|
| 167 |
-
</section>
|
| 168 |
-
</AppShell>
|
| 169 |
-
);
|
| 170 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/app/report/page.tsx
DELETED
|
@@ -1,27 +0,0 @@
|
|
| 1 |
-
import { AppShell } from "@/components/app-shell";
|
| 2 |
-
import { ReportForm } from "@/components/report-form";
|
| 3 |
-
import { requireRole } from "@/lib/auth";
|
| 4 |
-
|
| 5 |
-
export default async function ReportIssuePage() {
|
| 6 |
-
const user = await requireRole(["citizen"]);
|
| 7 |
-
|
| 8 |
-
return (
|
| 9 |
-
<AppShell>
|
| 10 |
-
<div className="mx-auto max-w-3xl space-y-8 pt-8">
|
| 11 |
-
<div className="text-center space-y-4">
|
| 12 |
-
<p className="text-sm font-bold uppercase tracking-[0.2em] text-civic-secondary">
|
| 13 |
-
Citizen Reporting
|
| 14 |
-
</p>
|
| 15 |
-
<h1 className="text-4xl font-extrabold tracking-tight text-slate-900 sm:text-5xl">
|
| 16 |
-
File a Report
|
| 17 |
-
</h1>
|
| 18 |
-
<p className="mx-auto max-w-xl text-lg text-slate-500">
|
| 19 |
-
Tell us what happened and where. We will get it to the right department.
|
| 20 |
-
</p>
|
| 21 |
-
</div>
|
| 22 |
-
|
| 23 |
-
<ReportForm citizenId={user.id} />
|
| 24 |
-
</div>
|
| 25 |
-
</AppShell>
|
| 26 |
-
);
|
| 27 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/app/report/review/page.tsx
DELETED
|
@@ -1,165 +0,0 @@
|
|
| 1 |
-
import Link from "next/link";
|
| 2 |
-
import { AlertTriangle, CheckCircle2, ChevronRight, ClipboardList, LocateFixed, ShieldAlert } from "lucide-react";
|
| 3 |
-
import { AppShell } from "@/components/app-shell";
|
| 4 |
-
import { FlowSteps } from "@/components/flow-steps";
|
| 5 |
-
|
| 6 |
-
const steps = ["Choose domain", "Add evidence", "Confirm location", "Review", "Submit"];
|
| 7 |
-
|
| 8 |
-
const reviewSections = [
|
| 9 |
-
{
|
| 10 |
-
icon: ClipboardList,
|
| 11 |
-
title: "Complaint summary",
|
| 12 |
-
items: [
|
| 13 |
-
"Selected domain: Water Supply, Sewerage, and Drainage",
|
| 14 |
-
"Likely sub-problem: Sewage overflow",
|
| 15 |
-
"Urgency hint: High public impact",
|
| 16 |
-
"Citizen note: Overflow near market entrance and bus stop",
|
| 17 |
-
],
|
| 18 |
-
},
|
| 19 |
-
{
|
| 20 |
-
icon: LocateFixed,
|
| 21 |
-
title: "Location confirmation",
|
| 22 |
-
items: [
|
| 23 |
-
"Coordinates captured from device",
|
| 24 |
-
"Address resolved from map pin",
|
| 25 |
-
"Ward and routing zone ready",
|
| 26 |
-
"Nearby landmark noted for field staff",
|
| 27 |
-
],
|
| 28 |
-
},
|
| 29 |
-
{
|
| 30 |
-
icon: CheckCircle2,
|
| 31 |
-
title: "Evidence attached",
|
| 32 |
-
items: [
|
| 33 |
-
"1 photo uploaded",
|
| 34 |
-
"0 video clips",
|
| 35 |
-
"Optional voice note available for later support",
|
| 36 |
-
"Media summary ready for department review",
|
| 37 |
-
],
|
| 38 |
-
},
|
| 39 |
-
];
|
| 40 |
-
|
| 41 |
-
const routingPreview = [
|
| 42 |
-
"Primary department: Water and Sewerage",
|
| 43 |
-
"Secondary support: Drainage unit if monsoon overflow is detected",
|
| 44 |
-
"Initial priority: P2 High",
|
| 45 |
-
"Notification path: citizen confirmation after submission",
|
| 46 |
-
];
|
| 47 |
-
|
| 48 |
-
const submitChecks = [
|
| 49 |
-
"Location is correct",
|
| 50 |
-
"Complaint is within city jurisdiction",
|
| 51 |
-
"Media is safe and relevant",
|
| 52 |
-
"Description is clear enough for field response",
|
| 53 |
-
];
|
| 54 |
-
|
| 55 |
-
export default function ReportReviewPage() {
|
| 56 |
-
return (
|
| 57 |
-
<AppShell>
|
| 58 |
-
<section className="space-y-6">
|
| 59 |
-
<div className="space-y-3">
|
| 60 |
-
<p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Report flow</p>
|
| 61 |
-
<h1 className="text-4xl font-semibold tracking-tight text-civic-text">Review before final submission</h1>
|
| 62 |
-
<p className="max-w-3xl text-sm leading-7 text-civic-muted">
|
| 63 |
-
Check the issue details, location, and evidence once more before sending the complaint to the city team.
|
| 64 |
-
</p>
|
| 65 |
-
</div>
|
| 66 |
-
|
| 67 |
-
<FlowSteps currentStep={4} steps={steps} />
|
| 68 |
-
</section>
|
| 69 |
-
|
| 70 |
-
<section className="grid gap-6 lg:grid-cols-[1.12fr_0.88fr]">
|
| 71 |
-
<div className="space-y-6">
|
| 72 |
-
{reviewSections.map(({ icon: Icon, title, items }) => (
|
| 73 |
-
<section key={title} className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
|
| 74 |
-
<div className="flex items-center gap-3">
|
| 75 |
-
<div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-civic-secondary/15 text-civic-secondary">
|
| 76 |
-
<Icon className="h-5 w-5" />
|
| 77 |
-
</div>
|
| 78 |
-
<div>
|
| 79 |
-
<p className="text-sm font-semibold uppercase tracking-[0.16em] text-civic-secondary">Summary</p>
|
| 80 |
-
<h2 className="text-xl font-semibold text-civic-text">{title}</h2>
|
| 81 |
-
</div>
|
| 82 |
-
</div>
|
| 83 |
-
|
| 84 |
-
<div className="mt-5 grid gap-3">
|
| 85 |
-
{items.map((item) => (
|
| 86 |
-
<div key={item} className="rounded-2xl bg-slate-50 px-4 py-3 text-sm leading-6 text-civic-text">
|
| 87 |
-
{item}
|
| 88 |
-
</div>
|
| 89 |
-
))}
|
| 90 |
-
</div>
|
| 91 |
-
</section>
|
| 92 |
-
))}
|
| 93 |
-
</div>
|
| 94 |
-
|
| 95 |
-
<div className="space-y-6">
|
| 96 |
-
<section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
|
| 97 |
-
<div className="flex items-center gap-3">
|
| 98 |
-
<div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-civic-primary text-white">
|
| 99 |
-
<ShieldAlert className="h-5 w-5" />
|
| 100 |
-
</div>
|
| 101 |
-
<div>
|
| 102 |
-
<p className="text-sm font-semibold uppercase tracking-[0.16em] text-civic-secondary">Routing preview</p>
|
| 103 |
-
<h2 className="text-xl font-semibold text-civic-text">What the system will do next</h2>
|
| 104 |
-
</div>
|
| 105 |
-
</div>
|
| 106 |
-
|
| 107 |
-
<div className="mt-5 space-y-3">
|
| 108 |
-
{routingPreview.map((item) => (
|
| 109 |
-
<div key={item} className="rounded-2xl bg-slate-50 px-4 py-3 text-sm leading-6 text-civic-text">
|
| 110 |
-
{item}
|
| 111 |
-
</div>
|
| 112 |
-
))}
|
| 113 |
-
</div>
|
| 114 |
-
</section>
|
| 115 |
-
|
| 116 |
-
<section className="rounded-[2rem] border border-amber-200 bg-amber-50 p-6 shadow-sm">
|
| 117 |
-
<div className="flex items-start gap-3">
|
| 118 |
-
<AlertTriangle className="mt-0.5 h-5 w-5 text-amber-700" />
|
| 119 |
-
<div>
|
| 120 |
-
<h2 className="text-lg font-semibold text-amber-950">Final checks before submission</h2>
|
| 121 |
-
<p className="mt-2 text-sm leading-6 text-amber-900/85">
|
| 122 |
-
A quick final check helps the city team act faster and reduces follow-up questions.
|
| 123 |
-
</p>
|
| 124 |
-
</div>
|
| 125 |
-
</div>
|
| 126 |
-
|
| 127 |
-
<div className="mt-5 space-y-3">
|
| 128 |
-
{submitChecks.map((item) => (
|
| 129 |
-
<div key={item} className="rounded-2xl bg-white/75 px-4 py-3 text-sm font-medium text-amber-950">
|
| 130 |
-
{item}
|
| 131 |
-
</div>
|
| 132 |
-
))}
|
| 133 |
-
</div>
|
| 134 |
-
</section>
|
| 135 |
-
</div>
|
| 136 |
-
</section>
|
| 137 |
-
|
| 138 |
-
<section className="flex flex-wrap items-center justify-between gap-4 rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
|
| 139 |
-
<div>
|
| 140 |
-
<p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Submit complaint</p>
|
| 141 |
-
<h2 className="mt-2 text-2xl font-semibold text-civic-text">Ready to create the complaint record</h2>
|
| 142 |
-
<p className="mt-2 max-w-2xl text-sm leading-6 text-civic-muted">
|
| 143 |
-
Once submitted, your complaint will receive an ID and move into review, routing, and department assignment.
|
| 144 |
-
</p>
|
| 145 |
-
</div>
|
| 146 |
-
|
| 147 |
-
<div className="flex flex-wrap gap-3">
|
| 148 |
-
<Link
|
| 149 |
-
className="inline-flex rounded-full border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:border-civic-primary hover:text-civic-primary"
|
| 150 |
-
href="/report/location"
|
| 151 |
-
>
|
| 152 |
-
Back to location
|
| 153 |
-
</Link>
|
| 154 |
-
<Link
|
| 155 |
-
className="inline-flex items-center gap-2 rounded-full bg-civic-primary px-5 py-3 text-sm font-semibold text-white transition hover:-translate-y-0.5"
|
| 156 |
-
href="/report/success"
|
| 157 |
-
>
|
| 158 |
-
Submit complaint
|
| 159 |
-
<ChevronRight className="h-4 w-4" />
|
| 160 |
-
</Link>
|
| 161 |
-
</div>
|
| 162 |
-
</section>
|
| 163 |
-
</AppShell>
|
| 164 |
-
);
|
| 165 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/app/report/success/page.tsx
DELETED
|
@@ -1,173 +0,0 @@
|
|
| 1 |
-
import Link from "next/link";
|
| 2 |
-
import { ArrowRight, CheckCircle2, Clock3, Hash, MapPinned, ShieldCheck } from "lucide-react";
|
| 3 |
-
import { AppShell } from "@/components/app-shell";
|
| 4 |
-
import { FlowSteps } from "@/components/flow-steps";
|
| 5 |
-
|
| 6 |
-
const steps = ["Choose domain", "Add evidence", "Confirm location", "Review", "Submit"];
|
| 7 |
-
|
| 8 |
-
const nextActions = [
|
| 9 |
-
"Your report is stored with a complaint ID for tracking.",
|
| 10 |
-
"The first visible status is Submitted.",
|
| 11 |
-
"Routing and priority decide which department picks it up next.",
|
| 12 |
-
"You will see updates when the complaint is assigned, worked on, or resolved.",
|
| 13 |
-
];
|
| 14 |
-
|
| 15 |
-
const summaryCards = [
|
| 16 |
-
{
|
| 17 |
-
icon: Hash,
|
| 18 |
-
label: "Complaint ID",
|
| 19 |
-
value: "CP-2026-00124",
|
| 20 |
-
},
|
| 21 |
-
{
|
| 22 |
-
icon: Clock3,
|
| 23 |
-
label: "Current status",
|
| 24 |
-
value: "Submitted",
|
| 25 |
-
},
|
| 26 |
-
{
|
| 27 |
-
icon: MapPinned,
|
| 28 |
-
label: "Location state",
|
| 29 |
-
value: "Geo-tag confirmed",
|
| 30 |
-
},
|
| 31 |
-
];
|
| 32 |
-
|
| 33 |
-
type ReportSuccessPageProps = {
|
| 34 |
-
searchParams?: Promise<{
|
| 35 |
-
id?: string;
|
| 36 |
-
code?: string;
|
| 37 |
-
media?: string;
|
| 38 |
-
}>;
|
| 39 |
-
};
|
| 40 |
-
|
| 41 |
-
export default async function ReportSuccessPage({ searchParams }: ReportSuccessPageProps) {
|
| 42 |
-
const resolvedSearchParams = searchParams ? await searchParams : undefined;
|
| 43 |
-
const complaintId = resolvedSearchParams?.id ?? "sample-complaint";
|
| 44 |
-
const complaintCode = resolvedSearchParams?.code ?? "CP-2026-00124";
|
| 45 |
-
const mediaStatus = resolvedSearchParams?.media ?? "complete";
|
| 46 |
-
|
| 47 |
-
return (
|
| 48 |
-
<AppShell>
|
| 49 |
-
<section className="space-y-6">
|
| 50 |
-
<div className="space-y-3">
|
| 51 |
-
<p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Report flow</p>
|
| 52 |
-
<h1 className="text-4xl font-semibold tracking-tight text-civic-text">Complaint submitted successfully</h1>
|
| 53 |
-
<p className="max-w-3xl text-sm leading-7 text-civic-muted">
|
| 54 |
-
Your complaint has been recorded. Keep the complaint ID handy and use the tracking page for updates.
|
| 55 |
-
</p>
|
| 56 |
-
</div>
|
| 57 |
-
|
| 58 |
-
<FlowSteps currentStep={5} steps={steps} />
|
| 59 |
-
</section>
|
| 60 |
-
|
| 61 |
-
<section className="rounded-[2rem] bg-civic-primary px-6 py-8 text-white shadow-civic lg:px-8">
|
| 62 |
-
<div className="grid gap-8 lg:grid-cols-[1.15fr_0.85fr]">
|
| 63 |
-
<div className="space-y-5">
|
| 64 |
-
<div className="flex h-16 w-16 items-center justify-center rounded-3xl bg-white text-civic-primary">
|
| 65 |
-
<CheckCircle2 className="h-8 w-8" />
|
| 66 |
-
</div>
|
| 67 |
-
<div className="space-y-3">
|
| 68 |
-
<h2 className="text-3xl font-semibold tracking-tight">Your complaint is now in the system.</h2>
|
| 69 |
-
<p className="max-w-2xl text-sm leading-7 text-white/80">
|
| 70 |
-
It will now move through review, routing, assignment, and resolution. You can track progress from your
|
| 71 |
-
complaints page at any time.
|
| 72 |
-
</p>
|
| 73 |
-
{mediaStatus === "partial" ? (
|
| 74 |
-
<p className="rounded-2xl bg-white/10 px-4 py-3 text-sm text-white/90">
|
| 75 |
-
Your complaint was submitted, but one or more media files could not be uploaded.
|
| 76 |
-
</p>
|
| 77 |
-
) : null}
|
| 78 |
-
</div>
|
| 79 |
-
<div className="flex flex-wrap gap-3">
|
| 80 |
-
<Link
|
| 81 |
-
className="inline-flex items-center gap-2 rounded-full bg-white px-5 py-3 text-sm font-semibold text-civic-primary transition hover:-translate-y-0.5"
|
| 82 |
-
href="/complaints"
|
| 83 |
-
>
|
| 84 |
-
View my complaints
|
| 85 |
-
<ArrowRight className="h-4 w-4" />
|
| 86 |
-
</Link>
|
| 87 |
-
<Link
|
| 88 |
-
className="inline-flex items-center gap-2 rounded-full border border-white/30 px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/10"
|
| 89 |
-
href={`/complaints/${complaintId}`}
|
| 90 |
-
>
|
| 91 |
-
Open complaint detail
|
| 92 |
-
<ArrowRight className="h-4 w-4" />
|
| 93 |
-
</Link>
|
| 94 |
-
</div>
|
| 95 |
-
</div>
|
| 96 |
-
|
| 97 |
-
<div className="grid gap-4">
|
| 98 |
-
{summaryCards.map(({ icon: Icon, label, value }) => (
|
| 99 |
-
<div key={label} className="rounded-3xl border border-white/15 bg-white/10 p-5 backdrop-blur">
|
| 100 |
-
<Icon className="mb-4 h-6 w-6 text-civic-accent" />
|
| 101 |
-
<p className="text-sm font-semibold uppercase tracking-[0.16em] text-white/75">{label}</p>
|
| 102 |
-
<p className="mt-2 text-xl font-semibold text-white">
|
| 103 |
-
{label === "Complaint ID" ? complaintCode : value}
|
| 104 |
-
</p>
|
| 105 |
-
</div>
|
| 106 |
-
))}
|
| 107 |
-
</div>
|
| 108 |
-
</div>
|
| 109 |
-
</section>
|
| 110 |
-
|
| 111 |
-
<section className="grid gap-6 lg:grid-cols-[1fr_1fr]">
|
| 112 |
-
<section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
|
| 113 |
-
<div className="flex items-center gap-3">
|
| 114 |
-
<div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-civic-secondary/15 text-civic-secondary">
|
| 115 |
-
<ShieldCheck className="h-5 w-5" />
|
| 116 |
-
</div>
|
| 117 |
-
<div>
|
| 118 |
-
<p className="text-sm font-semibold uppercase tracking-[0.16em] text-civic-secondary">What happens next</p>
|
| 119 |
-
<h2 className="text-xl font-semibold text-civic-text">Post-submission workflow</h2>
|
| 120 |
-
</div>
|
| 121 |
-
</div>
|
| 122 |
-
|
| 123 |
-
<div className="mt-5 space-y-3">
|
| 124 |
-
{nextActions.map((item) => (
|
| 125 |
-
<div key={item} className="rounded-2xl bg-slate-50 px-4 py-3 text-sm leading-6 text-civic-text">
|
| 126 |
-
{item}
|
| 127 |
-
</div>
|
| 128 |
-
))}
|
| 129 |
-
</div>
|
| 130 |
-
</section>
|
| 131 |
-
|
| 132 |
-
<section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
|
| 133 |
-
<p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Helpful shortcuts</p>
|
| 134 |
-
<h2 className="mt-2 text-2xl font-semibold text-civic-text">What you can do from here</h2>
|
| 135 |
-
<p className="mt-3 text-sm leading-7 text-civic-muted">
|
| 136 |
-
You can report another issue, open the complaint detail page, or switch to emergency reporting if the
|
| 137 |
-
situation has become urgent.
|
| 138 |
-
</p>
|
| 139 |
-
|
| 140 |
-
<div className="mt-6 grid gap-3 sm:grid-cols-2">
|
| 141 |
-
{[
|
| 142 |
-
"Complaint ID",
|
| 143 |
-
"Current status",
|
| 144 |
-
"Expected next step",
|
| 145 |
-
"Tracking shortcut",
|
| 146 |
-
"Submit another issue",
|
| 147 |
-
"Emergency reporting shortcut",
|
| 148 |
-
].map((item) => (
|
| 149 |
-
<div key={item} className="rounded-2xl border border-slate-200 p-4 text-sm font-medium text-civic-text">
|
| 150 |
-
{item}
|
| 151 |
-
</div>
|
| 152 |
-
))}
|
| 153 |
-
</div>
|
| 154 |
-
|
| 155 |
-
<div className="mt-6 flex flex-wrap gap-3">
|
| 156 |
-
<Link
|
| 157 |
-
className="inline-flex rounded-full border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:border-civic-primary hover:text-civic-primary"
|
| 158 |
-
href="/report"
|
| 159 |
-
>
|
| 160 |
-
Submit another issue
|
| 161 |
-
</Link>
|
| 162 |
-
<Link
|
| 163 |
-
className="inline-flex rounded-full bg-civic-danger px-5 py-3 text-sm font-semibold text-white transition hover:-translate-y-0.5"
|
| 164 |
-
href="/emergency"
|
| 165 |
-
>
|
| 166 |
-
Emergency reporting
|
| 167 |
-
</Link>
|
| 168 |
-
</div>
|
| 169 |
-
</section>
|
| 170 |
-
</section>
|
| 171 |
-
</AppShell>
|
| 172 |
-
);
|
| 173 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/app/tasks/page.tsx
DELETED
|
@@ -1,35 +0,0 @@
|
|
| 1 |
-
import { AppShell } from "@/components/app-shell";
|
| 2 |
-
import { FieldTaskBoard } from "@/components/field-task-board";
|
| 3 |
-
import { LiveSyncBadge } from "@/components/live-sync-badge";
|
| 4 |
-
import { requireRole } from "@/lib/auth";
|
| 5 |
-
import { getOfficerAssignments, type ComplaintAssignmentItem } from "@/lib/api";
|
| 6 |
-
|
| 7 |
-
const fallbackAssignments: ComplaintAssignmentItem[] = [];
|
| 8 |
-
|
| 9 |
-
export default async function TasksPage() {
|
| 10 |
-
const user = await requireRole(["field_officer"]);
|
| 11 |
-
let assignments = fallbackAssignments;
|
| 12 |
-
|
| 13 |
-
try {
|
| 14 |
-
assignments = await getOfficerAssignments(user.id);
|
| 15 |
-
} catch {
|
| 16 |
-
assignments = fallbackAssignments;
|
| 17 |
-
}
|
| 18 |
-
|
| 19 |
-
return (
|
| 20 |
-
<AppShell>
|
| 21 |
-
<section className="space-y-3">
|
| 22 |
-
<div className="flex flex-wrap items-center justify-between gap-3">
|
| 23 |
-
<p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Field operations</p>
|
| 24 |
-
<LiveSyncBadge label="Task board live refresh" />
|
| 25 |
-
</div>
|
| 26 |
-
<h1 className="text-4xl font-semibold tracking-tight text-civic-text">Work through assigned on-ground tasks.</h1>
|
| 27 |
-
<p className="max-w-3xl text-sm leading-7 text-civic-muted">
|
| 28 |
-
Accept work, start field action, and mark completion so the complaint lifecycle stays accurate for citizens and operators.
|
| 29 |
-
</p>
|
| 30 |
-
</section>
|
| 31 |
-
|
| 32 |
-
<FieldTaskBoard assignments={assignments} officerId={user.id} />
|
| 33 |
-
</AppShell>
|
| 34 |
-
);
|
| 35 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/app/template.tsx
DELETED
|
@@ -1,16 +0,0 @@
|
|
| 1 |
-
"use client";
|
| 2 |
-
|
| 3 |
-
import { motion } from "framer-motion";
|
| 4 |
-
|
| 5 |
-
export default function Template({ children }: { children: React.ReactNode }) {
|
| 6 |
-
return (
|
| 7 |
-
<motion.div
|
| 8 |
-
initial={{ opacity: 0, y: 15 }}
|
| 9 |
-
animate={{ opacity: 1, y: 0 }}
|
| 10 |
-
exit={{ opacity: 0, y: -15 }}
|
| 11 |
-
transition={{ ease: "circOut", duration: 0.4 }}
|
| 12 |
-
>
|
| 13 |
-
{children}
|
| 14 |
-
</motion.div>
|
| 15 |
-
);
|
| 16 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/backend/.env.example
DELETED
|
@@ -1,12 +0,0 @@
|
|
| 1 |
-
PORT=4000
|
| 2 |
-
NODE_ENV=development
|
| 3 |
-
DB_HOST=localhost
|
| 4 |
-
DB_PORT=5432
|
| 5 |
-
DB_NAME=civicpulse
|
| 6 |
-
DB_USER=postgres
|
| 7 |
-
DB_PASSWORD=your_password_here
|
| 8 |
-
DB_SSL=false
|
| 9 |
-
DEMO_AUTH_PASSWORD=civicpulse123
|
| 10 |
-
CORS_ORIGINS=http://localhost:3000
|
| 11 |
-
RATE_LIMIT_WINDOW_MS=60000
|
| 12 |
-
RATE_LIMIT_MAX_REQUESTS=120
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/backend/README.md
DELETED
|
@@ -1,38 +0,0 @@
|
|
| 1 |
-
# Backend
|
| 2 |
-
|
| 3 |
-
Node.js and TypeScript backend for the CivicPulse project.
|
| 4 |
-
|
| 5 |
-
## Quick start
|
| 6 |
-
|
| 7 |
-
1. Copy `.env.example` to `.env`
|
| 8 |
-
2. Set your PostgreSQL connection values
|
| 9 |
-
3. Create the `civicpulse` database
|
| 10 |
-
4. Apply `sql/001_initial_schema.sql`
|
| 11 |
-
5. Apply `sql/002_seed_core_data.sql`
|
| 12 |
-
6. Run `npm install`
|
| 13 |
-
7. Run `npm run dev`
|
| 14 |
-
|
| 15 |
-
Alternative local setup:
|
| 16 |
-
|
| 17 |
-
- Run `npm run db:init` to apply both SQL files using the configured PostgreSQL connection
|
| 18 |
-
|
| 19 |
-
## Main folders
|
| 20 |
-
|
| 21 |
-
- `src/config` environment config
|
| 22 |
-
- `src/db` PostgreSQL connection
|
| 23 |
-
- `src/routes` shared routes
|
| 24 |
-
- `src/modules/complaints` complaint APIs
|
| 25 |
-
- `sql` database schema files
|
| 26 |
-
|
| 27 |
-
## First routes
|
| 28 |
-
|
| 29 |
-
- `GET /api/health`
|
| 30 |
-
- `GET /api/departments`
|
| 31 |
-
- `GET /api/domains`
|
| 32 |
-
- `GET /api/complaints`
|
| 33 |
-
- `GET /api/complaints/citizen/:citizenId`
|
| 34 |
-
- `GET /api/complaints/:id`
|
| 35 |
-
- `GET /api/complaints/:id/history`
|
| 36 |
-
- `POST /api/complaints`
|
| 37 |
-
- `PATCH /api/complaints/:id/status`
|
| 38 |
-
- `POST /api/complaints/:id/assign`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/backend/backend-dev.log
DELETED
|
@@ -1,5 +0,0 @@
|
|
| 1 |
-
|
| 2 |
-
> civicpulse-backend@0.1.0 dev
|
| 3 |
-
> tsx watch src/server.ts
|
| 4 |
-
|
| 5 |
-
CivicPulse backend listening on port 4000
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/backend/backend-run.log
DELETED
|
@@ -1,2 +0,0 @@
|
|
| 1 |
-
CivicPulse backend listening on port 4000
|
| 2 |
-
^C
|
|
|
|
|
|
|
|
|
civic-platform/backend/backend.crash.log
DELETED
|
Binary file (88 Bytes)
|
|
|
civic-platform/backend/backend.detached.log
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
CivicPulse backend listening on port 4000
|
|
|
|
|
|
civic-platform/backend/backend.dev.log
DELETED
|
Binary file (88 Bytes)
|
|
|
civic-platform/backend/package-lock.json
DELETED
|
@@ -1,1817 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"name": "civicpulse-backend",
|
| 3 |
-
"version": "0.1.0",
|
| 4 |
-
"lockfileVersion": 3,
|
| 5 |
-
"requires": true,
|
| 6 |
-
"packages": {
|
| 7 |
-
"": {
|
| 8 |
-
"name": "civicpulse-backend",
|
| 9 |
-
"version": "0.1.0",
|
| 10 |
-
"dependencies": {
|
| 11 |
-
"cors": "^2.8.5",
|
| 12 |
-
"dotenv": "^16.4.7",
|
| 13 |
-
"express": "^4.21.2",
|
| 14 |
-
"multer": "^2.1.1",
|
| 15 |
-
"pg": "^8.13.3"
|
| 16 |
-
},
|
| 17 |
-
"devDependencies": {
|
| 18 |
-
"@types/cors": "^2.8.17",
|
| 19 |
-
"@types/express": "^5.0.1",
|
| 20 |
-
"@types/multer": "^2.1.0",
|
| 21 |
-
"@types/node": "^22.13.10",
|
| 22 |
-
"@types/pg": "^8.11.11",
|
| 23 |
-
"tsx": "^4.19.3",
|
| 24 |
-
"typescript": "^5.8.2"
|
| 25 |
-
}
|
| 26 |
-
},
|
| 27 |
-
"node_modules/@esbuild/aix-ppc64": {
|
| 28 |
-
"version": "0.27.4",
|
| 29 |
-
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz",
|
| 30 |
-
"integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==",
|
| 31 |
-
"cpu": [
|
| 32 |
-
"ppc64"
|
| 33 |
-
],
|
| 34 |
-
"dev": true,
|
| 35 |
-
"license": "MIT",
|
| 36 |
-
"optional": true,
|
| 37 |
-
"os": [
|
| 38 |
-
"aix"
|
| 39 |
-
],
|
| 40 |
-
"engines": {
|
| 41 |
-
"node": ">=18"
|
| 42 |
-
}
|
| 43 |
-
},
|
| 44 |
-
"node_modules/@esbuild/android-arm": {
|
| 45 |
-
"version": "0.27.4",
|
| 46 |
-
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz",
|
| 47 |
-
"integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==",
|
| 48 |
-
"cpu": [
|
| 49 |
-
"arm"
|
| 50 |
-
],
|
| 51 |
-
"dev": true,
|
| 52 |
-
"license": "MIT",
|
| 53 |
-
"optional": true,
|
| 54 |
-
"os": [
|
| 55 |
-
"android"
|
| 56 |
-
],
|
| 57 |
-
"engines": {
|
| 58 |
-
"node": ">=18"
|
| 59 |
-
}
|
| 60 |
-
},
|
| 61 |
-
"node_modules/@esbuild/android-arm64": {
|
| 62 |
-
"version": "0.27.4",
|
| 63 |
-
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz",
|
| 64 |
-
"integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==",
|
| 65 |
-
"cpu": [
|
| 66 |
-
"arm64"
|
| 67 |
-
],
|
| 68 |
-
"dev": true,
|
| 69 |
-
"license": "MIT",
|
| 70 |
-
"optional": true,
|
| 71 |
-
"os": [
|
| 72 |
-
"android"
|
| 73 |
-
],
|
| 74 |
-
"engines": {
|
| 75 |
-
"node": ">=18"
|
| 76 |
-
}
|
| 77 |
-
},
|
| 78 |
-
"node_modules/@esbuild/android-x64": {
|
| 79 |
-
"version": "0.27.4",
|
| 80 |
-
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz",
|
| 81 |
-
"integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==",
|
| 82 |
-
"cpu": [
|
| 83 |
-
"x64"
|
| 84 |
-
],
|
| 85 |
-
"dev": true,
|
| 86 |
-
"license": "MIT",
|
| 87 |
-
"optional": true,
|
| 88 |
-
"os": [
|
| 89 |
-
"android"
|
| 90 |
-
],
|
| 91 |
-
"engines": {
|
| 92 |
-
"node": ">=18"
|
| 93 |
-
}
|
| 94 |
-
},
|
| 95 |
-
"node_modules/@esbuild/darwin-arm64": {
|
| 96 |
-
"version": "0.27.4",
|
| 97 |
-
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz",
|
| 98 |
-
"integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==",
|
| 99 |
-
"cpu": [
|
| 100 |
-
"arm64"
|
| 101 |
-
],
|
| 102 |
-
"dev": true,
|
| 103 |
-
"license": "MIT",
|
| 104 |
-
"optional": true,
|
| 105 |
-
"os": [
|
| 106 |
-
"darwin"
|
| 107 |
-
],
|
| 108 |
-
"engines": {
|
| 109 |
-
"node": ">=18"
|
| 110 |
-
}
|
| 111 |
-
},
|
| 112 |
-
"node_modules/@esbuild/darwin-x64": {
|
| 113 |
-
"version": "0.27.4",
|
| 114 |
-
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz",
|
| 115 |
-
"integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==",
|
| 116 |
-
"cpu": [
|
| 117 |
-
"x64"
|
| 118 |
-
],
|
| 119 |
-
"dev": true,
|
| 120 |
-
"license": "MIT",
|
| 121 |
-
"optional": true,
|
| 122 |
-
"os": [
|
| 123 |
-
"darwin"
|
| 124 |
-
],
|
| 125 |
-
"engines": {
|
| 126 |
-
"node": ">=18"
|
| 127 |
-
}
|
| 128 |
-
},
|
| 129 |
-
"node_modules/@esbuild/freebsd-arm64": {
|
| 130 |
-
"version": "0.27.4",
|
| 131 |
-
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz",
|
| 132 |
-
"integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==",
|
| 133 |
-
"cpu": [
|
| 134 |
-
"arm64"
|
| 135 |
-
],
|
| 136 |
-
"dev": true,
|
| 137 |
-
"license": "MIT",
|
| 138 |
-
"optional": true,
|
| 139 |
-
"os": [
|
| 140 |
-
"freebsd"
|
| 141 |
-
],
|
| 142 |
-
"engines": {
|
| 143 |
-
"node": ">=18"
|
| 144 |
-
}
|
| 145 |
-
},
|
| 146 |
-
"node_modules/@esbuild/freebsd-x64": {
|
| 147 |
-
"version": "0.27.4",
|
| 148 |
-
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz",
|
| 149 |
-
"integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==",
|
| 150 |
-
"cpu": [
|
| 151 |
-
"x64"
|
| 152 |
-
],
|
| 153 |
-
"dev": true,
|
| 154 |
-
"license": "MIT",
|
| 155 |
-
"optional": true,
|
| 156 |
-
"os": [
|
| 157 |
-
"freebsd"
|
| 158 |
-
],
|
| 159 |
-
"engines": {
|
| 160 |
-
"node": ">=18"
|
| 161 |
-
}
|
| 162 |
-
},
|
| 163 |
-
"node_modules/@esbuild/linux-arm": {
|
| 164 |
-
"version": "0.27.4",
|
| 165 |
-
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz",
|
| 166 |
-
"integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==",
|
| 167 |
-
"cpu": [
|
| 168 |
-
"arm"
|
| 169 |
-
],
|
| 170 |
-
"dev": true,
|
| 171 |
-
"license": "MIT",
|
| 172 |
-
"optional": true,
|
| 173 |
-
"os": [
|
| 174 |
-
"linux"
|
| 175 |
-
],
|
| 176 |
-
"engines": {
|
| 177 |
-
"node": ">=18"
|
| 178 |
-
}
|
| 179 |
-
},
|
| 180 |
-
"node_modules/@esbuild/linux-arm64": {
|
| 181 |
-
"version": "0.27.4",
|
| 182 |
-
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz",
|
| 183 |
-
"integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==",
|
| 184 |
-
"cpu": [
|
| 185 |
-
"arm64"
|
| 186 |
-
],
|
| 187 |
-
"dev": true,
|
| 188 |
-
"license": "MIT",
|
| 189 |
-
"optional": true,
|
| 190 |
-
"os": [
|
| 191 |
-
"linux"
|
| 192 |
-
],
|
| 193 |
-
"engines": {
|
| 194 |
-
"node": ">=18"
|
| 195 |
-
}
|
| 196 |
-
},
|
| 197 |
-
"node_modules/@esbuild/linux-ia32": {
|
| 198 |
-
"version": "0.27.4",
|
| 199 |
-
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz",
|
| 200 |
-
"integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==",
|
| 201 |
-
"cpu": [
|
| 202 |
-
"ia32"
|
| 203 |
-
],
|
| 204 |
-
"dev": true,
|
| 205 |
-
"license": "MIT",
|
| 206 |
-
"optional": true,
|
| 207 |
-
"os": [
|
| 208 |
-
"linux"
|
| 209 |
-
],
|
| 210 |
-
"engines": {
|
| 211 |
-
"node": ">=18"
|
| 212 |
-
}
|
| 213 |
-
},
|
| 214 |
-
"node_modules/@esbuild/linux-loong64": {
|
| 215 |
-
"version": "0.27.4",
|
| 216 |
-
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz",
|
| 217 |
-
"integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==",
|
| 218 |
-
"cpu": [
|
| 219 |
-
"loong64"
|
| 220 |
-
],
|
| 221 |
-
"dev": true,
|
| 222 |
-
"license": "MIT",
|
| 223 |
-
"optional": true,
|
| 224 |
-
"os": [
|
| 225 |
-
"linux"
|
| 226 |
-
],
|
| 227 |
-
"engines": {
|
| 228 |
-
"node": ">=18"
|
| 229 |
-
}
|
| 230 |
-
},
|
| 231 |
-
"node_modules/@esbuild/linux-mips64el": {
|
| 232 |
-
"version": "0.27.4",
|
| 233 |
-
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz",
|
| 234 |
-
"integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==",
|
| 235 |
-
"cpu": [
|
| 236 |
-
"mips64el"
|
| 237 |
-
],
|
| 238 |
-
"dev": true,
|
| 239 |
-
"license": "MIT",
|
| 240 |
-
"optional": true,
|
| 241 |
-
"os": [
|
| 242 |
-
"linux"
|
| 243 |
-
],
|
| 244 |
-
"engines": {
|
| 245 |
-
"node": ">=18"
|
| 246 |
-
}
|
| 247 |
-
},
|
| 248 |
-
"node_modules/@esbuild/linux-ppc64": {
|
| 249 |
-
"version": "0.27.4",
|
| 250 |
-
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz",
|
| 251 |
-
"integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==",
|
| 252 |
-
"cpu": [
|
| 253 |
-
"ppc64"
|
| 254 |
-
],
|
| 255 |
-
"dev": true,
|
| 256 |
-
"license": "MIT",
|
| 257 |
-
"optional": true,
|
| 258 |
-
"os": [
|
| 259 |
-
"linux"
|
| 260 |
-
],
|
| 261 |
-
"engines": {
|
| 262 |
-
"node": ">=18"
|
| 263 |
-
}
|
| 264 |
-
},
|
| 265 |
-
"node_modules/@esbuild/linux-riscv64": {
|
| 266 |
-
"version": "0.27.4",
|
| 267 |
-
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz",
|
| 268 |
-
"integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==",
|
| 269 |
-
"cpu": [
|
| 270 |
-
"riscv64"
|
| 271 |
-
],
|
| 272 |
-
"dev": true,
|
| 273 |
-
"license": "MIT",
|
| 274 |
-
"optional": true,
|
| 275 |
-
"os": [
|
| 276 |
-
"linux"
|
| 277 |
-
],
|
| 278 |
-
"engines": {
|
| 279 |
-
"node": ">=18"
|
| 280 |
-
}
|
| 281 |
-
},
|
| 282 |
-
"node_modules/@esbuild/linux-s390x": {
|
| 283 |
-
"version": "0.27.4",
|
| 284 |
-
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz",
|
| 285 |
-
"integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==",
|
| 286 |
-
"cpu": [
|
| 287 |
-
"s390x"
|
| 288 |
-
],
|
| 289 |
-
"dev": true,
|
| 290 |
-
"license": "MIT",
|
| 291 |
-
"optional": true,
|
| 292 |
-
"os": [
|
| 293 |
-
"linux"
|
| 294 |
-
],
|
| 295 |
-
"engines": {
|
| 296 |
-
"node": ">=18"
|
| 297 |
-
}
|
| 298 |
-
},
|
| 299 |
-
"node_modules/@esbuild/linux-x64": {
|
| 300 |
-
"version": "0.27.4",
|
| 301 |
-
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz",
|
| 302 |
-
"integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==",
|
| 303 |
-
"cpu": [
|
| 304 |
-
"x64"
|
| 305 |
-
],
|
| 306 |
-
"dev": true,
|
| 307 |
-
"license": "MIT",
|
| 308 |
-
"optional": true,
|
| 309 |
-
"os": [
|
| 310 |
-
"linux"
|
| 311 |
-
],
|
| 312 |
-
"engines": {
|
| 313 |
-
"node": ">=18"
|
| 314 |
-
}
|
| 315 |
-
},
|
| 316 |
-
"node_modules/@esbuild/netbsd-arm64": {
|
| 317 |
-
"version": "0.27.4",
|
| 318 |
-
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz",
|
| 319 |
-
"integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==",
|
| 320 |
-
"cpu": [
|
| 321 |
-
"arm64"
|
| 322 |
-
],
|
| 323 |
-
"dev": true,
|
| 324 |
-
"license": "MIT",
|
| 325 |
-
"optional": true,
|
| 326 |
-
"os": [
|
| 327 |
-
"netbsd"
|
| 328 |
-
],
|
| 329 |
-
"engines": {
|
| 330 |
-
"node": ">=18"
|
| 331 |
-
}
|
| 332 |
-
},
|
| 333 |
-
"node_modules/@esbuild/netbsd-x64": {
|
| 334 |
-
"version": "0.27.4",
|
| 335 |
-
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz",
|
| 336 |
-
"integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==",
|
| 337 |
-
"cpu": [
|
| 338 |
-
"x64"
|
| 339 |
-
],
|
| 340 |
-
"dev": true,
|
| 341 |
-
"license": "MIT",
|
| 342 |
-
"optional": true,
|
| 343 |
-
"os": [
|
| 344 |
-
"netbsd"
|
| 345 |
-
],
|
| 346 |
-
"engines": {
|
| 347 |
-
"node": ">=18"
|
| 348 |
-
}
|
| 349 |
-
},
|
| 350 |
-
"node_modules/@esbuild/openbsd-arm64": {
|
| 351 |
-
"version": "0.27.4",
|
| 352 |
-
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz",
|
| 353 |
-
"integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==",
|
| 354 |
-
"cpu": [
|
| 355 |
-
"arm64"
|
| 356 |
-
],
|
| 357 |
-
"dev": true,
|
| 358 |
-
"license": "MIT",
|
| 359 |
-
"optional": true,
|
| 360 |
-
"os": [
|
| 361 |
-
"openbsd"
|
| 362 |
-
],
|
| 363 |
-
"engines": {
|
| 364 |
-
"node": ">=18"
|
| 365 |
-
}
|
| 366 |
-
},
|
| 367 |
-
"node_modules/@esbuild/openbsd-x64": {
|
| 368 |
-
"version": "0.27.4",
|
| 369 |
-
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz",
|
| 370 |
-
"integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==",
|
| 371 |
-
"cpu": [
|
| 372 |
-
"x64"
|
| 373 |
-
],
|
| 374 |
-
"dev": true,
|
| 375 |
-
"license": "MIT",
|
| 376 |
-
"optional": true,
|
| 377 |
-
"os": [
|
| 378 |
-
"openbsd"
|
| 379 |
-
],
|
| 380 |
-
"engines": {
|
| 381 |
-
"node": ">=18"
|
| 382 |
-
}
|
| 383 |
-
},
|
| 384 |
-
"node_modules/@esbuild/openharmony-arm64": {
|
| 385 |
-
"version": "0.27.4",
|
| 386 |
-
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz",
|
| 387 |
-
"integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==",
|
| 388 |
-
"cpu": [
|
| 389 |
-
"arm64"
|
| 390 |
-
],
|
| 391 |
-
"dev": true,
|
| 392 |
-
"license": "MIT",
|
| 393 |
-
"optional": true,
|
| 394 |
-
"os": [
|
| 395 |
-
"openharmony"
|
| 396 |
-
],
|
| 397 |
-
"engines": {
|
| 398 |
-
"node": ">=18"
|
| 399 |
-
}
|
| 400 |
-
},
|
| 401 |
-
"node_modules/@esbuild/sunos-x64": {
|
| 402 |
-
"version": "0.27.4",
|
| 403 |
-
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz",
|
| 404 |
-
"integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==",
|
| 405 |
-
"cpu": [
|
| 406 |
-
"x64"
|
| 407 |
-
],
|
| 408 |
-
"dev": true,
|
| 409 |
-
"license": "MIT",
|
| 410 |
-
"optional": true,
|
| 411 |
-
"os": [
|
| 412 |
-
"sunos"
|
| 413 |
-
],
|
| 414 |
-
"engines": {
|
| 415 |
-
"node": ">=18"
|
| 416 |
-
}
|
| 417 |
-
},
|
| 418 |
-
"node_modules/@esbuild/win32-arm64": {
|
| 419 |
-
"version": "0.27.4",
|
| 420 |
-
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz",
|
| 421 |
-
"integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==",
|
| 422 |
-
"cpu": [
|
| 423 |
-
"arm64"
|
| 424 |
-
],
|
| 425 |
-
"dev": true,
|
| 426 |
-
"license": "MIT",
|
| 427 |
-
"optional": true,
|
| 428 |
-
"os": [
|
| 429 |
-
"win32"
|
| 430 |
-
],
|
| 431 |
-
"engines": {
|
| 432 |
-
"node": ">=18"
|
| 433 |
-
}
|
| 434 |
-
},
|
| 435 |
-
"node_modules/@esbuild/win32-ia32": {
|
| 436 |
-
"version": "0.27.4",
|
| 437 |
-
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz",
|
| 438 |
-
"integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==",
|
| 439 |
-
"cpu": [
|
| 440 |
-
"ia32"
|
| 441 |
-
],
|
| 442 |
-
"dev": true,
|
| 443 |
-
"license": "MIT",
|
| 444 |
-
"optional": true,
|
| 445 |
-
"os": [
|
| 446 |
-
"win32"
|
| 447 |
-
],
|
| 448 |
-
"engines": {
|
| 449 |
-
"node": ">=18"
|
| 450 |
-
}
|
| 451 |
-
},
|
| 452 |
-
"node_modules/@esbuild/win32-x64": {
|
| 453 |
-
"version": "0.27.4",
|
| 454 |
-
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz",
|
| 455 |
-
"integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==",
|
| 456 |
-
"cpu": [
|
| 457 |
-
"x64"
|
| 458 |
-
],
|
| 459 |
-
"dev": true,
|
| 460 |
-
"license": "MIT",
|
| 461 |
-
"optional": true,
|
| 462 |
-
"os": [
|
| 463 |
-
"win32"
|
| 464 |
-
],
|
| 465 |
-
"engines": {
|
| 466 |
-
"node": ">=18"
|
| 467 |
-
}
|
| 468 |
-
},
|
| 469 |
-
"node_modules/@types/body-parser": {
|
| 470 |
-
"version": "1.19.6",
|
| 471 |
-
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
|
| 472 |
-
"integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
|
| 473 |
-
"dev": true,
|
| 474 |
-
"license": "MIT",
|
| 475 |
-
"dependencies": {
|
| 476 |
-
"@types/connect": "*",
|
| 477 |
-
"@types/node": "*"
|
| 478 |
-
}
|
| 479 |
-
},
|
| 480 |
-
"node_modules/@types/connect": {
|
| 481 |
-
"version": "3.4.38",
|
| 482 |
-
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
|
| 483 |
-
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
|
| 484 |
-
"dev": true,
|
| 485 |
-
"license": "MIT",
|
| 486 |
-
"dependencies": {
|
| 487 |
-
"@types/node": "*"
|
| 488 |
-
}
|
| 489 |
-
},
|
| 490 |
-
"node_modules/@types/cors": {
|
| 491 |
-
"version": "2.8.19",
|
| 492 |
-
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
|
| 493 |
-
"integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
|
| 494 |
-
"dev": true,
|
| 495 |
-
"license": "MIT",
|
| 496 |
-
"dependencies": {
|
| 497 |
-
"@types/node": "*"
|
| 498 |
-
}
|
| 499 |
-
},
|
| 500 |
-
"node_modules/@types/express": {
|
| 501 |
-
"version": "5.0.6",
|
| 502 |
-
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
|
| 503 |
-
"integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
|
| 504 |
-
"dev": true,
|
| 505 |
-
"license": "MIT",
|
| 506 |
-
"dependencies": {
|
| 507 |
-
"@types/body-parser": "*",
|
| 508 |
-
"@types/express-serve-static-core": "^5.0.0",
|
| 509 |
-
"@types/serve-static": "^2"
|
| 510 |
-
}
|
| 511 |
-
},
|
| 512 |
-
"node_modules/@types/express-serve-static-core": {
|
| 513 |
-
"version": "5.1.1",
|
| 514 |
-
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz",
|
| 515 |
-
"integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==",
|
| 516 |
-
"dev": true,
|
| 517 |
-
"license": "MIT",
|
| 518 |
-
"dependencies": {
|
| 519 |
-
"@types/node": "*",
|
| 520 |
-
"@types/qs": "*",
|
| 521 |
-
"@types/range-parser": "*",
|
| 522 |
-
"@types/send": "*"
|
| 523 |
-
}
|
| 524 |
-
},
|
| 525 |
-
"node_modules/@types/http-errors": {
|
| 526 |
-
"version": "2.0.5",
|
| 527 |
-
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
|
| 528 |
-
"integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
|
| 529 |
-
"dev": true,
|
| 530 |
-
"license": "MIT"
|
| 531 |
-
},
|
| 532 |
-
"node_modules/@types/multer": {
|
| 533 |
-
"version": "2.1.0",
|
| 534 |
-
"resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.1.0.tgz",
|
| 535 |
-
"integrity": "sha512-zYZb0+nJhOHtPpGDb3vqPjwpdeGlGC157VpkqNQL+UU2qwoacoQ7MpsAmUptI/0Oa127X32JzWDqQVEXp2RcIA==",
|
| 536 |
-
"dev": true,
|
| 537 |
-
"license": "MIT",
|
| 538 |
-
"dependencies": {
|
| 539 |
-
"@types/express": "*"
|
| 540 |
-
}
|
| 541 |
-
},
|
| 542 |
-
"node_modules/@types/node": {
|
| 543 |
-
"version": "22.19.15",
|
| 544 |
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz",
|
| 545 |
-
"integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==",
|
| 546 |
-
"dev": true,
|
| 547 |
-
"license": "MIT",
|
| 548 |
-
"dependencies": {
|
| 549 |
-
"undici-types": "~6.21.0"
|
| 550 |
-
}
|
| 551 |
-
},
|
| 552 |
-
"node_modules/@types/pg": {
|
| 553 |
-
"version": "8.20.0",
|
| 554 |
-
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz",
|
| 555 |
-
"integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==",
|
| 556 |
-
"dev": true,
|
| 557 |
-
"license": "MIT",
|
| 558 |
-
"dependencies": {
|
| 559 |
-
"@types/node": "*",
|
| 560 |
-
"pg-protocol": "*",
|
| 561 |
-
"pg-types": "^2.2.0"
|
| 562 |
-
}
|
| 563 |
-
},
|
| 564 |
-
"node_modules/@types/qs": {
|
| 565 |
-
"version": "6.15.0",
|
| 566 |
-
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz",
|
| 567 |
-
"integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==",
|
| 568 |
-
"dev": true,
|
| 569 |
-
"license": "MIT"
|
| 570 |
-
},
|
| 571 |
-
"node_modules/@types/range-parser": {
|
| 572 |
-
"version": "1.2.7",
|
| 573 |
-
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
|
| 574 |
-
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
|
| 575 |
-
"dev": true,
|
| 576 |
-
"license": "MIT"
|
| 577 |
-
},
|
| 578 |
-
"node_modules/@types/send": {
|
| 579 |
-
"version": "1.2.1",
|
| 580 |
-
"resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
|
| 581 |
-
"integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
|
| 582 |
-
"dev": true,
|
| 583 |
-
"license": "MIT",
|
| 584 |
-
"dependencies": {
|
| 585 |
-
"@types/node": "*"
|
| 586 |
-
}
|
| 587 |
-
},
|
| 588 |
-
"node_modules/@types/serve-static": {
|
| 589 |
-
"version": "2.2.0",
|
| 590 |
-
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
|
| 591 |
-
"integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
|
| 592 |
-
"dev": true,
|
| 593 |
-
"license": "MIT",
|
| 594 |
-
"dependencies": {
|
| 595 |
-
"@types/http-errors": "*",
|
| 596 |
-
"@types/node": "*"
|
| 597 |
-
}
|
| 598 |
-
},
|
| 599 |
-
"node_modules/accepts": {
|
| 600 |
-
"version": "1.3.8",
|
| 601 |
-
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
| 602 |
-
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
| 603 |
-
"license": "MIT",
|
| 604 |
-
"dependencies": {
|
| 605 |
-
"mime-types": "~2.1.34",
|
| 606 |
-
"negotiator": "0.6.3"
|
| 607 |
-
},
|
| 608 |
-
"engines": {
|
| 609 |
-
"node": ">= 0.6"
|
| 610 |
-
}
|
| 611 |
-
},
|
| 612 |
-
"node_modules/append-field": {
|
| 613 |
-
"version": "1.0.0",
|
| 614 |
-
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
|
| 615 |
-
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
|
| 616 |
-
"license": "MIT"
|
| 617 |
-
},
|
| 618 |
-
"node_modules/array-flatten": {
|
| 619 |
-
"version": "1.1.1",
|
| 620 |
-
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
| 621 |
-
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
| 622 |
-
"license": "MIT"
|
| 623 |
-
},
|
| 624 |
-
"node_modules/body-parser": {
|
| 625 |
-
"version": "1.20.4",
|
| 626 |
-
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
|
| 627 |
-
"integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
|
| 628 |
-
"license": "MIT",
|
| 629 |
-
"dependencies": {
|
| 630 |
-
"bytes": "~3.1.2",
|
| 631 |
-
"content-type": "~1.0.5",
|
| 632 |
-
"debug": "2.6.9",
|
| 633 |
-
"depd": "2.0.0",
|
| 634 |
-
"destroy": "~1.2.0",
|
| 635 |
-
"http-errors": "~2.0.1",
|
| 636 |
-
"iconv-lite": "~0.4.24",
|
| 637 |
-
"on-finished": "~2.4.1",
|
| 638 |
-
"qs": "~6.14.0",
|
| 639 |
-
"raw-body": "~2.5.3",
|
| 640 |
-
"type-is": "~1.6.18",
|
| 641 |
-
"unpipe": "~1.0.0"
|
| 642 |
-
},
|
| 643 |
-
"engines": {
|
| 644 |
-
"node": ">= 0.8",
|
| 645 |
-
"npm": "1.2.8000 || >= 1.4.16"
|
| 646 |
-
}
|
| 647 |
-
},
|
| 648 |
-
"node_modules/buffer-from": {
|
| 649 |
-
"version": "1.1.2",
|
| 650 |
-
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
| 651 |
-
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
| 652 |
-
"license": "MIT"
|
| 653 |
-
},
|
| 654 |
-
"node_modules/busboy": {
|
| 655 |
-
"version": "1.6.0",
|
| 656 |
-
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
| 657 |
-
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
|
| 658 |
-
"dependencies": {
|
| 659 |
-
"streamsearch": "^1.1.0"
|
| 660 |
-
},
|
| 661 |
-
"engines": {
|
| 662 |
-
"node": ">=10.16.0"
|
| 663 |
-
}
|
| 664 |
-
},
|
| 665 |
-
"node_modules/bytes": {
|
| 666 |
-
"version": "3.1.2",
|
| 667 |
-
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
| 668 |
-
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
| 669 |
-
"license": "MIT",
|
| 670 |
-
"engines": {
|
| 671 |
-
"node": ">= 0.8"
|
| 672 |
-
}
|
| 673 |
-
},
|
| 674 |
-
"node_modules/call-bind-apply-helpers": {
|
| 675 |
-
"version": "1.0.2",
|
| 676 |
-
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
| 677 |
-
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
| 678 |
-
"license": "MIT",
|
| 679 |
-
"dependencies": {
|
| 680 |
-
"es-errors": "^1.3.0",
|
| 681 |
-
"function-bind": "^1.1.2"
|
| 682 |
-
},
|
| 683 |
-
"engines": {
|
| 684 |
-
"node": ">= 0.4"
|
| 685 |
-
}
|
| 686 |
-
},
|
| 687 |
-
"node_modules/call-bound": {
|
| 688 |
-
"version": "1.0.4",
|
| 689 |
-
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
| 690 |
-
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
| 691 |
-
"license": "MIT",
|
| 692 |
-
"dependencies": {
|
| 693 |
-
"call-bind-apply-helpers": "^1.0.2",
|
| 694 |
-
"get-intrinsic": "^1.3.0"
|
| 695 |
-
},
|
| 696 |
-
"engines": {
|
| 697 |
-
"node": ">= 0.4"
|
| 698 |
-
},
|
| 699 |
-
"funding": {
|
| 700 |
-
"url": "https://github.com/sponsors/ljharb"
|
| 701 |
-
}
|
| 702 |
-
},
|
| 703 |
-
"node_modules/concat-stream": {
|
| 704 |
-
"version": "2.0.0",
|
| 705 |
-
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
|
| 706 |
-
"integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
|
| 707 |
-
"engines": [
|
| 708 |
-
"node >= 6.0"
|
| 709 |
-
],
|
| 710 |
-
"license": "MIT",
|
| 711 |
-
"dependencies": {
|
| 712 |
-
"buffer-from": "^1.0.0",
|
| 713 |
-
"inherits": "^2.0.3",
|
| 714 |
-
"readable-stream": "^3.0.2",
|
| 715 |
-
"typedarray": "^0.0.6"
|
| 716 |
-
}
|
| 717 |
-
},
|
| 718 |
-
"node_modules/content-disposition": {
|
| 719 |
-
"version": "0.5.4",
|
| 720 |
-
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
| 721 |
-
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
| 722 |
-
"license": "MIT",
|
| 723 |
-
"dependencies": {
|
| 724 |
-
"safe-buffer": "5.2.1"
|
| 725 |
-
},
|
| 726 |
-
"engines": {
|
| 727 |
-
"node": ">= 0.6"
|
| 728 |
-
}
|
| 729 |
-
},
|
| 730 |
-
"node_modules/content-type": {
|
| 731 |
-
"version": "1.0.5",
|
| 732 |
-
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
| 733 |
-
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
| 734 |
-
"license": "MIT",
|
| 735 |
-
"engines": {
|
| 736 |
-
"node": ">= 0.6"
|
| 737 |
-
}
|
| 738 |
-
},
|
| 739 |
-
"node_modules/cookie": {
|
| 740 |
-
"version": "0.7.2",
|
| 741 |
-
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
| 742 |
-
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
| 743 |
-
"license": "MIT",
|
| 744 |
-
"engines": {
|
| 745 |
-
"node": ">= 0.6"
|
| 746 |
-
}
|
| 747 |
-
},
|
| 748 |
-
"node_modules/cookie-signature": {
|
| 749 |
-
"version": "1.0.7",
|
| 750 |
-
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
|
| 751 |
-
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
|
| 752 |
-
"license": "MIT"
|
| 753 |
-
},
|
| 754 |
-
"node_modules/cors": {
|
| 755 |
-
"version": "2.8.6",
|
| 756 |
-
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
|
| 757 |
-
"integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
|
| 758 |
-
"license": "MIT",
|
| 759 |
-
"dependencies": {
|
| 760 |
-
"object-assign": "^4",
|
| 761 |
-
"vary": "^1"
|
| 762 |
-
},
|
| 763 |
-
"engines": {
|
| 764 |
-
"node": ">= 0.10"
|
| 765 |
-
},
|
| 766 |
-
"funding": {
|
| 767 |
-
"type": "opencollective",
|
| 768 |
-
"url": "https://opencollective.com/express"
|
| 769 |
-
}
|
| 770 |
-
},
|
| 771 |
-
"node_modules/debug": {
|
| 772 |
-
"version": "2.6.9",
|
| 773 |
-
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
| 774 |
-
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
| 775 |
-
"license": "MIT",
|
| 776 |
-
"dependencies": {
|
| 777 |
-
"ms": "2.0.0"
|
| 778 |
-
}
|
| 779 |
-
},
|
| 780 |
-
"node_modules/depd": {
|
| 781 |
-
"version": "2.0.0",
|
| 782 |
-
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
| 783 |
-
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
| 784 |
-
"license": "MIT",
|
| 785 |
-
"engines": {
|
| 786 |
-
"node": ">= 0.8"
|
| 787 |
-
}
|
| 788 |
-
},
|
| 789 |
-
"node_modules/destroy": {
|
| 790 |
-
"version": "1.2.0",
|
| 791 |
-
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
| 792 |
-
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
|
| 793 |
-
"license": "MIT",
|
| 794 |
-
"engines": {
|
| 795 |
-
"node": ">= 0.8",
|
| 796 |
-
"npm": "1.2.8000 || >= 1.4.16"
|
| 797 |
-
}
|
| 798 |
-
},
|
| 799 |
-
"node_modules/dotenv": {
|
| 800 |
-
"version": "16.6.1",
|
| 801 |
-
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
|
| 802 |
-
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
|
| 803 |
-
"license": "BSD-2-Clause",
|
| 804 |
-
"engines": {
|
| 805 |
-
"node": ">=12"
|
| 806 |
-
},
|
| 807 |
-
"funding": {
|
| 808 |
-
"url": "https://dotenvx.com"
|
| 809 |
-
}
|
| 810 |
-
},
|
| 811 |
-
"node_modules/dunder-proto": {
|
| 812 |
-
"version": "1.0.1",
|
| 813 |
-
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
| 814 |
-
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
| 815 |
-
"license": "MIT",
|
| 816 |
-
"dependencies": {
|
| 817 |
-
"call-bind-apply-helpers": "^1.0.1",
|
| 818 |
-
"es-errors": "^1.3.0",
|
| 819 |
-
"gopd": "^1.2.0"
|
| 820 |
-
},
|
| 821 |
-
"engines": {
|
| 822 |
-
"node": ">= 0.4"
|
| 823 |
-
}
|
| 824 |
-
},
|
| 825 |
-
"node_modules/ee-first": {
|
| 826 |
-
"version": "1.1.1",
|
| 827 |
-
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
| 828 |
-
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
| 829 |
-
"license": "MIT"
|
| 830 |
-
},
|
| 831 |
-
"node_modules/encodeurl": {
|
| 832 |
-
"version": "2.0.0",
|
| 833 |
-
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
| 834 |
-
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
|
| 835 |
-
"license": "MIT",
|
| 836 |
-
"engines": {
|
| 837 |
-
"node": ">= 0.8"
|
| 838 |
-
}
|
| 839 |
-
},
|
| 840 |
-
"node_modules/es-define-property": {
|
| 841 |
-
"version": "1.0.1",
|
| 842 |
-
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
| 843 |
-
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
| 844 |
-
"license": "MIT",
|
| 845 |
-
"engines": {
|
| 846 |
-
"node": ">= 0.4"
|
| 847 |
-
}
|
| 848 |
-
},
|
| 849 |
-
"node_modules/es-errors": {
|
| 850 |
-
"version": "1.3.0",
|
| 851 |
-
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
| 852 |
-
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
| 853 |
-
"license": "MIT",
|
| 854 |
-
"engines": {
|
| 855 |
-
"node": ">= 0.4"
|
| 856 |
-
}
|
| 857 |
-
},
|
| 858 |
-
"node_modules/es-object-atoms": {
|
| 859 |
-
"version": "1.1.1",
|
| 860 |
-
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
| 861 |
-
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
| 862 |
-
"license": "MIT",
|
| 863 |
-
"dependencies": {
|
| 864 |
-
"es-errors": "^1.3.0"
|
| 865 |
-
},
|
| 866 |
-
"engines": {
|
| 867 |
-
"node": ">= 0.4"
|
| 868 |
-
}
|
| 869 |
-
},
|
| 870 |
-
"node_modules/esbuild": {
|
| 871 |
-
"version": "0.27.4",
|
| 872 |
-
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz",
|
| 873 |
-
"integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==",
|
| 874 |
-
"dev": true,
|
| 875 |
-
"hasInstallScript": true,
|
| 876 |
-
"license": "MIT",
|
| 877 |
-
"bin": {
|
| 878 |
-
"esbuild": "bin/esbuild"
|
| 879 |
-
},
|
| 880 |
-
"engines": {
|
| 881 |
-
"node": ">=18"
|
| 882 |
-
},
|
| 883 |
-
"optionalDependencies": {
|
| 884 |
-
"@esbuild/aix-ppc64": "0.27.4",
|
| 885 |
-
"@esbuild/android-arm": "0.27.4",
|
| 886 |
-
"@esbuild/android-arm64": "0.27.4",
|
| 887 |
-
"@esbuild/android-x64": "0.27.4",
|
| 888 |
-
"@esbuild/darwin-arm64": "0.27.4",
|
| 889 |
-
"@esbuild/darwin-x64": "0.27.4",
|
| 890 |
-
"@esbuild/freebsd-arm64": "0.27.4",
|
| 891 |
-
"@esbuild/freebsd-x64": "0.27.4",
|
| 892 |
-
"@esbuild/linux-arm": "0.27.4",
|
| 893 |
-
"@esbuild/linux-arm64": "0.27.4",
|
| 894 |
-
"@esbuild/linux-ia32": "0.27.4",
|
| 895 |
-
"@esbuild/linux-loong64": "0.27.4",
|
| 896 |
-
"@esbuild/linux-mips64el": "0.27.4",
|
| 897 |
-
"@esbuild/linux-ppc64": "0.27.4",
|
| 898 |
-
"@esbuild/linux-riscv64": "0.27.4",
|
| 899 |
-
"@esbuild/linux-s390x": "0.27.4",
|
| 900 |
-
"@esbuild/linux-x64": "0.27.4",
|
| 901 |
-
"@esbuild/netbsd-arm64": "0.27.4",
|
| 902 |
-
"@esbuild/netbsd-x64": "0.27.4",
|
| 903 |
-
"@esbuild/openbsd-arm64": "0.27.4",
|
| 904 |
-
"@esbuild/openbsd-x64": "0.27.4",
|
| 905 |
-
"@esbuild/openharmony-arm64": "0.27.4",
|
| 906 |
-
"@esbuild/sunos-x64": "0.27.4",
|
| 907 |
-
"@esbuild/win32-arm64": "0.27.4",
|
| 908 |
-
"@esbuild/win32-ia32": "0.27.4",
|
| 909 |
-
"@esbuild/win32-x64": "0.27.4"
|
| 910 |
-
}
|
| 911 |
-
},
|
| 912 |
-
"node_modules/escape-html": {
|
| 913 |
-
"version": "1.0.3",
|
| 914 |
-
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
| 915 |
-
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
| 916 |
-
"license": "MIT"
|
| 917 |
-
},
|
| 918 |
-
"node_modules/etag": {
|
| 919 |
-
"version": "1.8.1",
|
| 920 |
-
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
| 921 |
-
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
| 922 |
-
"license": "MIT",
|
| 923 |
-
"engines": {
|
| 924 |
-
"node": ">= 0.6"
|
| 925 |
-
}
|
| 926 |
-
},
|
| 927 |
-
"node_modules/express": {
|
| 928 |
-
"version": "4.22.1",
|
| 929 |
-
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
|
| 930 |
-
"integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
|
| 931 |
-
"license": "MIT",
|
| 932 |
-
"dependencies": {
|
| 933 |
-
"accepts": "~1.3.8",
|
| 934 |
-
"array-flatten": "1.1.1",
|
| 935 |
-
"body-parser": "~1.20.3",
|
| 936 |
-
"content-disposition": "~0.5.4",
|
| 937 |
-
"content-type": "~1.0.4",
|
| 938 |
-
"cookie": "~0.7.1",
|
| 939 |
-
"cookie-signature": "~1.0.6",
|
| 940 |
-
"debug": "2.6.9",
|
| 941 |
-
"depd": "2.0.0",
|
| 942 |
-
"encodeurl": "~2.0.0",
|
| 943 |
-
"escape-html": "~1.0.3",
|
| 944 |
-
"etag": "~1.8.1",
|
| 945 |
-
"finalhandler": "~1.3.1",
|
| 946 |
-
"fresh": "~0.5.2",
|
| 947 |
-
"http-errors": "~2.0.0",
|
| 948 |
-
"merge-descriptors": "1.0.3",
|
| 949 |
-
"methods": "~1.1.2",
|
| 950 |
-
"on-finished": "~2.4.1",
|
| 951 |
-
"parseurl": "~1.3.3",
|
| 952 |
-
"path-to-regexp": "~0.1.12",
|
| 953 |
-
"proxy-addr": "~2.0.7",
|
| 954 |
-
"qs": "~6.14.0",
|
| 955 |
-
"range-parser": "~1.2.1",
|
| 956 |
-
"safe-buffer": "5.2.1",
|
| 957 |
-
"send": "~0.19.0",
|
| 958 |
-
"serve-static": "~1.16.2",
|
| 959 |
-
"setprototypeof": "1.2.0",
|
| 960 |
-
"statuses": "~2.0.1",
|
| 961 |
-
"type-is": "~1.6.18",
|
| 962 |
-
"utils-merge": "1.0.1",
|
| 963 |
-
"vary": "~1.1.2"
|
| 964 |
-
},
|
| 965 |
-
"engines": {
|
| 966 |
-
"node": ">= 0.10.0"
|
| 967 |
-
},
|
| 968 |
-
"funding": {
|
| 969 |
-
"type": "opencollective",
|
| 970 |
-
"url": "https://opencollective.com/express"
|
| 971 |
-
}
|
| 972 |
-
},
|
| 973 |
-
"node_modules/finalhandler": {
|
| 974 |
-
"version": "1.3.2",
|
| 975 |
-
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
|
| 976 |
-
"integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
|
| 977 |
-
"license": "MIT",
|
| 978 |
-
"dependencies": {
|
| 979 |
-
"debug": "2.6.9",
|
| 980 |
-
"encodeurl": "~2.0.0",
|
| 981 |
-
"escape-html": "~1.0.3",
|
| 982 |
-
"on-finished": "~2.4.1",
|
| 983 |
-
"parseurl": "~1.3.3",
|
| 984 |
-
"statuses": "~2.0.2",
|
| 985 |
-
"unpipe": "~1.0.0"
|
| 986 |
-
},
|
| 987 |
-
"engines": {
|
| 988 |
-
"node": ">= 0.8"
|
| 989 |
-
}
|
| 990 |
-
},
|
| 991 |
-
"node_modules/forwarded": {
|
| 992 |
-
"version": "0.2.0",
|
| 993 |
-
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
| 994 |
-
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
|
| 995 |
-
"license": "MIT",
|
| 996 |
-
"engines": {
|
| 997 |
-
"node": ">= 0.6"
|
| 998 |
-
}
|
| 999 |
-
},
|
| 1000 |
-
"node_modules/fresh": {
|
| 1001 |
-
"version": "0.5.2",
|
| 1002 |
-
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
| 1003 |
-
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
| 1004 |
-
"license": "MIT",
|
| 1005 |
-
"engines": {
|
| 1006 |
-
"node": ">= 0.6"
|
| 1007 |
-
}
|
| 1008 |
-
},
|
| 1009 |
-
"node_modules/fsevents": {
|
| 1010 |
-
"version": "2.3.3",
|
| 1011 |
-
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
| 1012 |
-
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
| 1013 |
-
"dev": true,
|
| 1014 |
-
"hasInstallScript": true,
|
| 1015 |
-
"license": "MIT",
|
| 1016 |
-
"optional": true,
|
| 1017 |
-
"os": [
|
| 1018 |
-
"darwin"
|
| 1019 |
-
],
|
| 1020 |
-
"engines": {
|
| 1021 |
-
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
| 1022 |
-
}
|
| 1023 |
-
},
|
| 1024 |
-
"node_modules/function-bind": {
|
| 1025 |
-
"version": "1.1.2",
|
| 1026 |
-
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
| 1027 |
-
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
| 1028 |
-
"license": "MIT",
|
| 1029 |
-
"funding": {
|
| 1030 |
-
"url": "https://github.com/sponsors/ljharb"
|
| 1031 |
-
}
|
| 1032 |
-
},
|
| 1033 |
-
"node_modules/get-intrinsic": {
|
| 1034 |
-
"version": "1.3.0",
|
| 1035 |
-
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
| 1036 |
-
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
| 1037 |
-
"license": "MIT",
|
| 1038 |
-
"dependencies": {
|
| 1039 |
-
"call-bind-apply-helpers": "^1.0.2",
|
| 1040 |
-
"es-define-property": "^1.0.1",
|
| 1041 |
-
"es-errors": "^1.3.0",
|
| 1042 |
-
"es-object-atoms": "^1.1.1",
|
| 1043 |
-
"function-bind": "^1.1.2",
|
| 1044 |
-
"get-proto": "^1.0.1",
|
| 1045 |
-
"gopd": "^1.2.0",
|
| 1046 |
-
"has-symbols": "^1.1.0",
|
| 1047 |
-
"hasown": "^2.0.2",
|
| 1048 |
-
"math-intrinsics": "^1.1.0"
|
| 1049 |
-
},
|
| 1050 |
-
"engines": {
|
| 1051 |
-
"node": ">= 0.4"
|
| 1052 |
-
},
|
| 1053 |
-
"funding": {
|
| 1054 |
-
"url": "https://github.com/sponsors/ljharb"
|
| 1055 |
-
}
|
| 1056 |
-
},
|
| 1057 |
-
"node_modules/get-proto": {
|
| 1058 |
-
"version": "1.0.1",
|
| 1059 |
-
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
| 1060 |
-
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
| 1061 |
-
"license": "MIT",
|
| 1062 |
-
"dependencies": {
|
| 1063 |
-
"dunder-proto": "^1.0.1",
|
| 1064 |
-
"es-object-atoms": "^1.0.0"
|
| 1065 |
-
},
|
| 1066 |
-
"engines": {
|
| 1067 |
-
"node": ">= 0.4"
|
| 1068 |
-
}
|
| 1069 |
-
},
|
| 1070 |
-
"node_modules/get-tsconfig": {
|
| 1071 |
-
"version": "4.13.7",
|
| 1072 |
-
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz",
|
| 1073 |
-
"integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==",
|
| 1074 |
-
"dev": true,
|
| 1075 |
-
"license": "MIT",
|
| 1076 |
-
"dependencies": {
|
| 1077 |
-
"resolve-pkg-maps": "^1.0.0"
|
| 1078 |
-
},
|
| 1079 |
-
"funding": {
|
| 1080 |
-
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
| 1081 |
-
}
|
| 1082 |
-
},
|
| 1083 |
-
"node_modules/gopd": {
|
| 1084 |
-
"version": "1.2.0",
|
| 1085 |
-
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
| 1086 |
-
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
| 1087 |
-
"license": "MIT",
|
| 1088 |
-
"engines": {
|
| 1089 |
-
"node": ">= 0.4"
|
| 1090 |
-
},
|
| 1091 |
-
"funding": {
|
| 1092 |
-
"url": "https://github.com/sponsors/ljharb"
|
| 1093 |
-
}
|
| 1094 |
-
},
|
| 1095 |
-
"node_modules/has-symbols": {
|
| 1096 |
-
"version": "1.1.0",
|
| 1097 |
-
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
| 1098 |
-
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
| 1099 |
-
"license": "MIT",
|
| 1100 |
-
"engines": {
|
| 1101 |
-
"node": ">= 0.4"
|
| 1102 |
-
},
|
| 1103 |
-
"funding": {
|
| 1104 |
-
"url": "https://github.com/sponsors/ljharb"
|
| 1105 |
-
}
|
| 1106 |
-
},
|
| 1107 |
-
"node_modules/hasown": {
|
| 1108 |
-
"version": "2.0.2",
|
| 1109 |
-
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
| 1110 |
-
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
| 1111 |
-
"license": "MIT",
|
| 1112 |
-
"dependencies": {
|
| 1113 |
-
"function-bind": "^1.1.2"
|
| 1114 |
-
},
|
| 1115 |
-
"engines": {
|
| 1116 |
-
"node": ">= 0.4"
|
| 1117 |
-
}
|
| 1118 |
-
},
|
| 1119 |
-
"node_modules/http-errors": {
|
| 1120 |
-
"version": "2.0.1",
|
| 1121 |
-
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
| 1122 |
-
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
|
| 1123 |
-
"license": "MIT",
|
| 1124 |
-
"dependencies": {
|
| 1125 |
-
"depd": "~2.0.0",
|
| 1126 |
-
"inherits": "~2.0.4",
|
| 1127 |
-
"setprototypeof": "~1.2.0",
|
| 1128 |
-
"statuses": "~2.0.2",
|
| 1129 |
-
"toidentifier": "~1.0.1"
|
| 1130 |
-
},
|
| 1131 |
-
"engines": {
|
| 1132 |
-
"node": ">= 0.8"
|
| 1133 |
-
},
|
| 1134 |
-
"funding": {
|
| 1135 |
-
"type": "opencollective",
|
| 1136 |
-
"url": "https://opencollective.com/express"
|
| 1137 |
-
}
|
| 1138 |
-
},
|
| 1139 |
-
"node_modules/iconv-lite": {
|
| 1140 |
-
"version": "0.4.24",
|
| 1141 |
-
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
| 1142 |
-
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
| 1143 |
-
"license": "MIT",
|
| 1144 |
-
"dependencies": {
|
| 1145 |
-
"safer-buffer": ">= 2.1.2 < 3"
|
| 1146 |
-
},
|
| 1147 |
-
"engines": {
|
| 1148 |
-
"node": ">=0.10.0"
|
| 1149 |
-
}
|
| 1150 |
-
},
|
| 1151 |
-
"node_modules/inherits": {
|
| 1152 |
-
"version": "2.0.4",
|
| 1153 |
-
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
| 1154 |
-
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
| 1155 |
-
"license": "ISC"
|
| 1156 |
-
},
|
| 1157 |
-
"node_modules/ipaddr.js": {
|
| 1158 |
-
"version": "1.9.1",
|
| 1159 |
-
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
| 1160 |
-
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
|
| 1161 |
-
"license": "MIT",
|
| 1162 |
-
"engines": {
|
| 1163 |
-
"node": ">= 0.10"
|
| 1164 |
-
}
|
| 1165 |
-
},
|
| 1166 |
-
"node_modules/math-intrinsics": {
|
| 1167 |
-
"version": "1.1.0",
|
| 1168 |
-
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
| 1169 |
-
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
| 1170 |
-
"license": "MIT",
|
| 1171 |
-
"engines": {
|
| 1172 |
-
"node": ">= 0.4"
|
| 1173 |
-
}
|
| 1174 |
-
},
|
| 1175 |
-
"node_modules/media-typer": {
|
| 1176 |
-
"version": "0.3.0",
|
| 1177 |
-
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
| 1178 |
-
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
| 1179 |
-
"license": "MIT",
|
| 1180 |
-
"engines": {
|
| 1181 |
-
"node": ">= 0.6"
|
| 1182 |
-
}
|
| 1183 |
-
},
|
| 1184 |
-
"node_modules/merge-descriptors": {
|
| 1185 |
-
"version": "1.0.3",
|
| 1186 |
-
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
| 1187 |
-
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
|
| 1188 |
-
"license": "MIT",
|
| 1189 |
-
"funding": {
|
| 1190 |
-
"url": "https://github.com/sponsors/sindresorhus"
|
| 1191 |
-
}
|
| 1192 |
-
},
|
| 1193 |
-
"node_modules/methods": {
|
| 1194 |
-
"version": "1.1.2",
|
| 1195 |
-
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
| 1196 |
-
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
| 1197 |
-
"license": "MIT",
|
| 1198 |
-
"engines": {
|
| 1199 |
-
"node": ">= 0.6"
|
| 1200 |
-
}
|
| 1201 |
-
},
|
| 1202 |
-
"node_modules/mime": {
|
| 1203 |
-
"version": "1.6.0",
|
| 1204 |
-
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
| 1205 |
-
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
| 1206 |
-
"license": "MIT",
|
| 1207 |
-
"bin": {
|
| 1208 |
-
"mime": "cli.js"
|
| 1209 |
-
},
|
| 1210 |
-
"engines": {
|
| 1211 |
-
"node": ">=4"
|
| 1212 |
-
}
|
| 1213 |
-
},
|
| 1214 |
-
"node_modules/mime-db": {
|
| 1215 |
-
"version": "1.52.0",
|
| 1216 |
-
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
| 1217 |
-
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
| 1218 |
-
"license": "MIT",
|
| 1219 |
-
"engines": {
|
| 1220 |
-
"node": ">= 0.6"
|
| 1221 |
-
}
|
| 1222 |
-
},
|
| 1223 |
-
"node_modules/mime-types": {
|
| 1224 |
-
"version": "2.1.35",
|
| 1225 |
-
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
| 1226 |
-
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
| 1227 |
-
"license": "MIT",
|
| 1228 |
-
"dependencies": {
|
| 1229 |
-
"mime-db": "1.52.0"
|
| 1230 |
-
},
|
| 1231 |
-
"engines": {
|
| 1232 |
-
"node": ">= 0.6"
|
| 1233 |
-
}
|
| 1234 |
-
},
|
| 1235 |
-
"node_modules/ms": {
|
| 1236 |
-
"version": "2.0.0",
|
| 1237 |
-
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
| 1238 |
-
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
| 1239 |
-
"license": "MIT"
|
| 1240 |
-
},
|
| 1241 |
-
"node_modules/multer": {
|
| 1242 |
-
"version": "2.1.1",
|
| 1243 |
-
"resolved": "https://registry.npmjs.org/multer/-/multer-2.1.1.tgz",
|
| 1244 |
-
"integrity": "sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==",
|
| 1245 |
-
"license": "MIT",
|
| 1246 |
-
"dependencies": {
|
| 1247 |
-
"append-field": "^1.0.0",
|
| 1248 |
-
"busboy": "^1.6.0",
|
| 1249 |
-
"concat-stream": "^2.0.0",
|
| 1250 |
-
"type-is": "^1.6.18"
|
| 1251 |
-
},
|
| 1252 |
-
"engines": {
|
| 1253 |
-
"node": ">= 10.16.0"
|
| 1254 |
-
},
|
| 1255 |
-
"funding": {
|
| 1256 |
-
"type": "opencollective",
|
| 1257 |
-
"url": "https://opencollective.com/express"
|
| 1258 |
-
}
|
| 1259 |
-
},
|
| 1260 |
-
"node_modules/negotiator": {
|
| 1261 |
-
"version": "0.6.3",
|
| 1262 |
-
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
| 1263 |
-
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
| 1264 |
-
"license": "MIT",
|
| 1265 |
-
"engines": {
|
| 1266 |
-
"node": ">= 0.6"
|
| 1267 |
-
}
|
| 1268 |
-
},
|
| 1269 |
-
"node_modules/object-assign": {
|
| 1270 |
-
"version": "4.1.1",
|
| 1271 |
-
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
| 1272 |
-
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
| 1273 |
-
"license": "MIT",
|
| 1274 |
-
"engines": {
|
| 1275 |
-
"node": ">=0.10.0"
|
| 1276 |
-
}
|
| 1277 |
-
},
|
| 1278 |
-
"node_modules/object-inspect": {
|
| 1279 |
-
"version": "1.13.4",
|
| 1280 |
-
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
| 1281 |
-
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
| 1282 |
-
"license": "MIT",
|
| 1283 |
-
"engines": {
|
| 1284 |
-
"node": ">= 0.4"
|
| 1285 |
-
},
|
| 1286 |
-
"funding": {
|
| 1287 |
-
"url": "https://github.com/sponsors/ljharb"
|
| 1288 |
-
}
|
| 1289 |
-
},
|
| 1290 |
-
"node_modules/on-finished": {
|
| 1291 |
-
"version": "2.4.1",
|
| 1292 |
-
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
| 1293 |
-
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
| 1294 |
-
"license": "MIT",
|
| 1295 |
-
"dependencies": {
|
| 1296 |
-
"ee-first": "1.1.1"
|
| 1297 |
-
},
|
| 1298 |
-
"engines": {
|
| 1299 |
-
"node": ">= 0.8"
|
| 1300 |
-
}
|
| 1301 |
-
},
|
| 1302 |
-
"node_modules/parseurl": {
|
| 1303 |
-
"version": "1.3.3",
|
| 1304 |
-
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
| 1305 |
-
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
| 1306 |
-
"license": "MIT",
|
| 1307 |
-
"engines": {
|
| 1308 |
-
"node": ">= 0.8"
|
| 1309 |
-
}
|
| 1310 |
-
},
|
| 1311 |
-
"node_modules/path-to-regexp": {
|
| 1312 |
-
"version": "0.1.13",
|
| 1313 |
-
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
|
| 1314 |
-
"integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
|
| 1315 |
-
"license": "MIT"
|
| 1316 |
-
},
|
| 1317 |
-
"node_modules/pg": {
|
| 1318 |
-
"version": "8.20.0",
|
| 1319 |
-
"resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz",
|
| 1320 |
-
"integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==",
|
| 1321 |
-
"license": "MIT",
|
| 1322 |
-
"dependencies": {
|
| 1323 |
-
"pg-connection-string": "^2.12.0",
|
| 1324 |
-
"pg-pool": "^3.13.0",
|
| 1325 |
-
"pg-protocol": "^1.13.0",
|
| 1326 |
-
"pg-types": "2.2.0",
|
| 1327 |
-
"pgpass": "1.0.5"
|
| 1328 |
-
},
|
| 1329 |
-
"engines": {
|
| 1330 |
-
"node": ">= 16.0.0"
|
| 1331 |
-
},
|
| 1332 |
-
"optionalDependencies": {
|
| 1333 |
-
"pg-cloudflare": "^1.3.0"
|
| 1334 |
-
},
|
| 1335 |
-
"peerDependencies": {
|
| 1336 |
-
"pg-native": ">=3.0.1"
|
| 1337 |
-
},
|
| 1338 |
-
"peerDependenciesMeta": {
|
| 1339 |
-
"pg-native": {
|
| 1340 |
-
"optional": true
|
| 1341 |
-
}
|
| 1342 |
-
}
|
| 1343 |
-
},
|
| 1344 |
-
"node_modules/pg-cloudflare": {
|
| 1345 |
-
"version": "1.3.0",
|
| 1346 |
-
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz",
|
| 1347 |
-
"integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==",
|
| 1348 |
-
"license": "MIT",
|
| 1349 |
-
"optional": true
|
| 1350 |
-
},
|
| 1351 |
-
"node_modules/pg-connection-string": {
|
| 1352 |
-
"version": "2.12.0",
|
| 1353 |
-
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz",
|
| 1354 |
-
"integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==",
|
| 1355 |
-
"license": "MIT"
|
| 1356 |
-
},
|
| 1357 |
-
"node_modules/pg-int8": {
|
| 1358 |
-
"version": "1.0.1",
|
| 1359 |
-
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
| 1360 |
-
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
|
| 1361 |
-
"license": "ISC",
|
| 1362 |
-
"engines": {
|
| 1363 |
-
"node": ">=4.0.0"
|
| 1364 |
-
}
|
| 1365 |
-
},
|
| 1366 |
-
"node_modules/pg-pool": {
|
| 1367 |
-
"version": "3.13.0",
|
| 1368 |
-
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz",
|
| 1369 |
-
"integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==",
|
| 1370 |
-
"license": "MIT",
|
| 1371 |
-
"peerDependencies": {
|
| 1372 |
-
"pg": ">=8.0"
|
| 1373 |
-
}
|
| 1374 |
-
},
|
| 1375 |
-
"node_modules/pg-protocol": {
|
| 1376 |
-
"version": "1.13.0",
|
| 1377 |
-
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz",
|
| 1378 |
-
"integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==",
|
| 1379 |
-
"license": "MIT"
|
| 1380 |
-
},
|
| 1381 |
-
"node_modules/pg-types": {
|
| 1382 |
-
"version": "2.2.0",
|
| 1383 |
-
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
| 1384 |
-
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
| 1385 |
-
"license": "MIT",
|
| 1386 |
-
"dependencies": {
|
| 1387 |
-
"pg-int8": "1.0.1",
|
| 1388 |
-
"postgres-array": "~2.0.0",
|
| 1389 |
-
"postgres-bytea": "~1.0.0",
|
| 1390 |
-
"postgres-date": "~1.0.4",
|
| 1391 |
-
"postgres-interval": "^1.1.0"
|
| 1392 |
-
},
|
| 1393 |
-
"engines": {
|
| 1394 |
-
"node": ">=4"
|
| 1395 |
-
}
|
| 1396 |
-
},
|
| 1397 |
-
"node_modules/pgpass": {
|
| 1398 |
-
"version": "1.0.5",
|
| 1399 |
-
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
| 1400 |
-
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
| 1401 |
-
"license": "MIT",
|
| 1402 |
-
"dependencies": {
|
| 1403 |
-
"split2": "^4.1.0"
|
| 1404 |
-
}
|
| 1405 |
-
},
|
| 1406 |
-
"node_modules/postgres-array": {
|
| 1407 |
-
"version": "2.0.0",
|
| 1408 |
-
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
| 1409 |
-
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
| 1410 |
-
"license": "MIT",
|
| 1411 |
-
"engines": {
|
| 1412 |
-
"node": ">=4"
|
| 1413 |
-
}
|
| 1414 |
-
},
|
| 1415 |
-
"node_modules/postgres-bytea": {
|
| 1416 |
-
"version": "1.0.1",
|
| 1417 |
-
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
|
| 1418 |
-
"integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
|
| 1419 |
-
"license": "MIT",
|
| 1420 |
-
"engines": {
|
| 1421 |
-
"node": ">=0.10.0"
|
| 1422 |
-
}
|
| 1423 |
-
},
|
| 1424 |
-
"node_modules/postgres-date": {
|
| 1425 |
-
"version": "1.0.7",
|
| 1426 |
-
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
| 1427 |
-
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
| 1428 |
-
"license": "MIT",
|
| 1429 |
-
"engines": {
|
| 1430 |
-
"node": ">=0.10.0"
|
| 1431 |
-
}
|
| 1432 |
-
},
|
| 1433 |
-
"node_modules/postgres-interval": {
|
| 1434 |
-
"version": "1.2.0",
|
| 1435 |
-
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
| 1436 |
-
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
| 1437 |
-
"license": "MIT",
|
| 1438 |
-
"dependencies": {
|
| 1439 |
-
"xtend": "^4.0.0"
|
| 1440 |
-
},
|
| 1441 |
-
"engines": {
|
| 1442 |
-
"node": ">=0.10.0"
|
| 1443 |
-
}
|
| 1444 |
-
},
|
| 1445 |
-
"node_modules/proxy-addr": {
|
| 1446 |
-
"version": "2.0.7",
|
| 1447 |
-
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
| 1448 |
-
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
|
| 1449 |
-
"license": "MIT",
|
| 1450 |
-
"dependencies": {
|
| 1451 |
-
"forwarded": "0.2.0",
|
| 1452 |
-
"ipaddr.js": "1.9.1"
|
| 1453 |
-
},
|
| 1454 |
-
"engines": {
|
| 1455 |
-
"node": ">= 0.10"
|
| 1456 |
-
}
|
| 1457 |
-
},
|
| 1458 |
-
"node_modules/qs": {
|
| 1459 |
-
"version": "6.14.2",
|
| 1460 |
-
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
|
| 1461 |
-
"integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
|
| 1462 |
-
"license": "BSD-3-Clause",
|
| 1463 |
-
"dependencies": {
|
| 1464 |
-
"side-channel": "^1.1.0"
|
| 1465 |
-
},
|
| 1466 |
-
"engines": {
|
| 1467 |
-
"node": ">=0.6"
|
| 1468 |
-
},
|
| 1469 |
-
"funding": {
|
| 1470 |
-
"url": "https://github.com/sponsors/ljharb"
|
| 1471 |
-
}
|
| 1472 |
-
},
|
| 1473 |
-
"node_modules/range-parser": {
|
| 1474 |
-
"version": "1.2.1",
|
| 1475 |
-
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
| 1476 |
-
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
| 1477 |
-
"license": "MIT",
|
| 1478 |
-
"engines": {
|
| 1479 |
-
"node": ">= 0.6"
|
| 1480 |
-
}
|
| 1481 |
-
},
|
| 1482 |
-
"node_modules/raw-body": {
|
| 1483 |
-
"version": "2.5.3",
|
| 1484 |
-
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
|
| 1485 |
-
"integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
|
| 1486 |
-
"license": "MIT",
|
| 1487 |
-
"dependencies": {
|
| 1488 |
-
"bytes": "~3.1.2",
|
| 1489 |
-
"http-errors": "~2.0.1",
|
| 1490 |
-
"iconv-lite": "~0.4.24",
|
| 1491 |
-
"unpipe": "~1.0.0"
|
| 1492 |
-
},
|
| 1493 |
-
"engines": {
|
| 1494 |
-
"node": ">= 0.8"
|
| 1495 |
-
}
|
| 1496 |
-
},
|
| 1497 |
-
"node_modules/readable-stream": {
|
| 1498 |
-
"version": "3.6.2",
|
| 1499 |
-
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
| 1500 |
-
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
| 1501 |
-
"license": "MIT",
|
| 1502 |
-
"dependencies": {
|
| 1503 |
-
"inherits": "^2.0.3",
|
| 1504 |
-
"string_decoder": "^1.1.1",
|
| 1505 |
-
"util-deprecate": "^1.0.1"
|
| 1506 |
-
},
|
| 1507 |
-
"engines": {
|
| 1508 |
-
"node": ">= 6"
|
| 1509 |
-
}
|
| 1510 |
-
},
|
| 1511 |
-
"node_modules/resolve-pkg-maps": {
|
| 1512 |
-
"version": "1.0.0",
|
| 1513 |
-
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
| 1514 |
-
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
| 1515 |
-
"dev": true,
|
| 1516 |
-
"license": "MIT",
|
| 1517 |
-
"funding": {
|
| 1518 |
-
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
| 1519 |
-
}
|
| 1520 |
-
},
|
| 1521 |
-
"node_modules/safe-buffer": {
|
| 1522 |
-
"version": "5.2.1",
|
| 1523 |
-
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
| 1524 |
-
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
| 1525 |
-
"funding": [
|
| 1526 |
-
{
|
| 1527 |
-
"type": "github",
|
| 1528 |
-
"url": "https://github.com/sponsors/feross"
|
| 1529 |
-
},
|
| 1530 |
-
{
|
| 1531 |
-
"type": "patreon",
|
| 1532 |
-
"url": "https://www.patreon.com/feross"
|
| 1533 |
-
},
|
| 1534 |
-
{
|
| 1535 |
-
"type": "consulting",
|
| 1536 |
-
"url": "https://feross.org/support"
|
| 1537 |
-
}
|
| 1538 |
-
],
|
| 1539 |
-
"license": "MIT"
|
| 1540 |
-
},
|
| 1541 |
-
"node_modules/safer-buffer": {
|
| 1542 |
-
"version": "2.1.2",
|
| 1543 |
-
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
| 1544 |
-
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
| 1545 |
-
"license": "MIT"
|
| 1546 |
-
},
|
| 1547 |
-
"node_modules/send": {
|
| 1548 |
-
"version": "0.19.2",
|
| 1549 |
-
"resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
|
| 1550 |
-
"integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
|
| 1551 |
-
"license": "MIT",
|
| 1552 |
-
"dependencies": {
|
| 1553 |
-
"debug": "2.6.9",
|
| 1554 |
-
"depd": "2.0.0",
|
| 1555 |
-
"destroy": "1.2.0",
|
| 1556 |
-
"encodeurl": "~2.0.0",
|
| 1557 |
-
"escape-html": "~1.0.3",
|
| 1558 |
-
"etag": "~1.8.1",
|
| 1559 |
-
"fresh": "~0.5.2",
|
| 1560 |
-
"http-errors": "~2.0.1",
|
| 1561 |
-
"mime": "1.6.0",
|
| 1562 |
-
"ms": "2.1.3",
|
| 1563 |
-
"on-finished": "~2.4.1",
|
| 1564 |
-
"range-parser": "~1.2.1",
|
| 1565 |
-
"statuses": "~2.0.2"
|
| 1566 |
-
},
|
| 1567 |
-
"engines": {
|
| 1568 |
-
"node": ">= 0.8.0"
|
| 1569 |
-
}
|
| 1570 |
-
},
|
| 1571 |
-
"node_modules/send/node_modules/ms": {
|
| 1572 |
-
"version": "2.1.3",
|
| 1573 |
-
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 1574 |
-
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 1575 |
-
"license": "MIT"
|
| 1576 |
-
},
|
| 1577 |
-
"node_modules/serve-static": {
|
| 1578 |
-
"version": "1.16.3",
|
| 1579 |
-
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
|
| 1580 |
-
"integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
|
| 1581 |
-
"license": "MIT",
|
| 1582 |
-
"dependencies": {
|
| 1583 |
-
"encodeurl": "~2.0.0",
|
| 1584 |
-
"escape-html": "~1.0.3",
|
| 1585 |
-
"parseurl": "~1.3.3",
|
| 1586 |
-
"send": "~0.19.1"
|
| 1587 |
-
},
|
| 1588 |
-
"engines": {
|
| 1589 |
-
"node": ">= 0.8.0"
|
| 1590 |
-
}
|
| 1591 |
-
},
|
| 1592 |
-
"node_modules/setprototypeof": {
|
| 1593 |
-
"version": "1.2.0",
|
| 1594 |
-
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
| 1595 |
-
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
|
| 1596 |
-
"license": "ISC"
|
| 1597 |
-
},
|
| 1598 |
-
"node_modules/side-channel": {
|
| 1599 |
-
"version": "1.1.0",
|
| 1600 |
-
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
| 1601 |
-
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
|
| 1602 |
-
"license": "MIT",
|
| 1603 |
-
"dependencies": {
|
| 1604 |
-
"es-errors": "^1.3.0",
|
| 1605 |
-
"object-inspect": "^1.13.3",
|
| 1606 |
-
"side-channel-list": "^1.0.0",
|
| 1607 |
-
"side-channel-map": "^1.0.1",
|
| 1608 |
-
"side-channel-weakmap": "^1.0.2"
|
| 1609 |
-
},
|
| 1610 |
-
"engines": {
|
| 1611 |
-
"node": ">= 0.4"
|
| 1612 |
-
},
|
| 1613 |
-
"funding": {
|
| 1614 |
-
"url": "https://github.com/sponsors/ljharb"
|
| 1615 |
-
}
|
| 1616 |
-
},
|
| 1617 |
-
"node_modules/side-channel-list": {
|
| 1618 |
-
"version": "1.0.0",
|
| 1619 |
-
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
|
| 1620 |
-
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
|
| 1621 |
-
"license": "MIT",
|
| 1622 |
-
"dependencies": {
|
| 1623 |
-
"es-errors": "^1.3.0",
|
| 1624 |
-
"object-inspect": "^1.13.3"
|
| 1625 |
-
},
|
| 1626 |
-
"engines": {
|
| 1627 |
-
"node": ">= 0.4"
|
| 1628 |
-
},
|
| 1629 |
-
"funding": {
|
| 1630 |
-
"url": "https://github.com/sponsors/ljharb"
|
| 1631 |
-
}
|
| 1632 |
-
},
|
| 1633 |
-
"node_modules/side-channel-map": {
|
| 1634 |
-
"version": "1.0.1",
|
| 1635 |
-
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
| 1636 |
-
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
| 1637 |
-
"license": "MIT",
|
| 1638 |
-
"dependencies": {
|
| 1639 |
-
"call-bound": "^1.0.2",
|
| 1640 |
-
"es-errors": "^1.3.0",
|
| 1641 |
-
"get-intrinsic": "^1.2.5",
|
| 1642 |
-
"object-inspect": "^1.13.3"
|
| 1643 |
-
},
|
| 1644 |
-
"engines": {
|
| 1645 |
-
"node": ">= 0.4"
|
| 1646 |
-
},
|
| 1647 |
-
"funding": {
|
| 1648 |
-
"url": "https://github.com/sponsors/ljharb"
|
| 1649 |
-
}
|
| 1650 |
-
},
|
| 1651 |
-
"node_modules/side-channel-weakmap": {
|
| 1652 |
-
"version": "1.0.2",
|
| 1653 |
-
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
| 1654 |
-
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
| 1655 |
-
"license": "MIT",
|
| 1656 |
-
"dependencies": {
|
| 1657 |
-
"call-bound": "^1.0.2",
|
| 1658 |
-
"es-errors": "^1.3.0",
|
| 1659 |
-
"get-intrinsic": "^1.2.5",
|
| 1660 |
-
"object-inspect": "^1.13.3",
|
| 1661 |
-
"side-channel-map": "^1.0.1"
|
| 1662 |
-
},
|
| 1663 |
-
"engines": {
|
| 1664 |
-
"node": ">= 0.4"
|
| 1665 |
-
},
|
| 1666 |
-
"funding": {
|
| 1667 |
-
"url": "https://github.com/sponsors/ljharb"
|
| 1668 |
-
}
|
| 1669 |
-
},
|
| 1670 |
-
"node_modules/split2": {
|
| 1671 |
-
"version": "4.2.0",
|
| 1672 |
-
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
| 1673 |
-
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
| 1674 |
-
"license": "ISC",
|
| 1675 |
-
"engines": {
|
| 1676 |
-
"node": ">= 10.x"
|
| 1677 |
-
}
|
| 1678 |
-
},
|
| 1679 |
-
"node_modules/statuses": {
|
| 1680 |
-
"version": "2.0.2",
|
| 1681 |
-
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
| 1682 |
-
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
|
| 1683 |
-
"license": "MIT",
|
| 1684 |
-
"engines": {
|
| 1685 |
-
"node": ">= 0.8"
|
| 1686 |
-
}
|
| 1687 |
-
},
|
| 1688 |
-
"node_modules/streamsearch": {
|
| 1689 |
-
"version": "1.1.0",
|
| 1690 |
-
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
| 1691 |
-
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
|
| 1692 |
-
"engines": {
|
| 1693 |
-
"node": ">=10.0.0"
|
| 1694 |
-
}
|
| 1695 |
-
},
|
| 1696 |
-
"node_modules/string_decoder": {
|
| 1697 |
-
"version": "1.3.0",
|
| 1698 |
-
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
| 1699 |
-
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
| 1700 |
-
"license": "MIT",
|
| 1701 |
-
"dependencies": {
|
| 1702 |
-
"safe-buffer": "~5.2.0"
|
| 1703 |
-
}
|
| 1704 |
-
},
|
| 1705 |
-
"node_modules/toidentifier": {
|
| 1706 |
-
"version": "1.0.1",
|
| 1707 |
-
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
| 1708 |
-
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
| 1709 |
-
"license": "MIT",
|
| 1710 |
-
"engines": {
|
| 1711 |
-
"node": ">=0.6"
|
| 1712 |
-
}
|
| 1713 |
-
},
|
| 1714 |
-
"node_modules/tsx": {
|
| 1715 |
-
"version": "4.21.0",
|
| 1716 |
-
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
|
| 1717 |
-
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
| 1718 |
-
"dev": true,
|
| 1719 |
-
"license": "MIT",
|
| 1720 |
-
"dependencies": {
|
| 1721 |
-
"esbuild": "~0.27.0",
|
| 1722 |
-
"get-tsconfig": "^4.7.5"
|
| 1723 |
-
},
|
| 1724 |
-
"bin": {
|
| 1725 |
-
"tsx": "dist/cli.mjs"
|
| 1726 |
-
},
|
| 1727 |
-
"engines": {
|
| 1728 |
-
"node": ">=18.0.0"
|
| 1729 |
-
},
|
| 1730 |
-
"optionalDependencies": {
|
| 1731 |
-
"fsevents": "~2.3.3"
|
| 1732 |
-
}
|
| 1733 |
-
},
|
| 1734 |
-
"node_modules/type-is": {
|
| 1735 |
-
"version": "1.6.18",
|
| 1736 |
-
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
| 1737 |
-
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
| 1738 |
-
"license": "MIT",
|
| 1739 |
-
"dependencies": {
|
| 1740 |
-
"media-typer": "0.3.0",
|
| 1741 |
-
"mime-types": "~2.1.24"
|
| 1742 |
-
},
|
| 1743 |
-
"engines": {
|
| 1744 |
-
"node": ">= 0.6"
|
| 1745 |
-
}
|
| 1746 |
-
},
|
| 1747 |
-
"node_modules/typedarray": {
|
| 1748 |
-
"version": "0.0.6",
|
| 1749 |
-
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
| 1750 |
-
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
|
| 1751 |
-
"license": "MIT"
|
| 1752 |
-
},
|
| 1753 |
-
"node_modules/typescript": {
|
| 1754 |
-
"version": "5.9.3",
|
| 1755 |
-
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
| 1756 |
-
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
| 1757 |
-
"dev": true,
|
| 1758 |
-
"license": "Apache-2.0",
|
| 1759 |
-
"bin": {
|
| 1760 |
-
"tsc": "bin/tsc",
|
| 1761 |
-
"tsserver": "bin/tsserver"
|
| 1762 |
-
},
|
| 1763 |
-
"engines": {
|
| 1764 |
-
"node": ">=14.17"
|
| 1765 |
-
}
|
| 1766 |
-
},
|
| 1767 |
-
"node_modules/undici-types": {
|
| 1768 |
-
"version": "6.21.0",
|
| 1769 |
-
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
| 1770 |
-
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
| 1771 |
-
"dev": true,
|
| 1772 |
-
"license": "MIT"
|
| 1773 |
-
},
|
| 1774 |
-
"node_modules/unpipe": {
|
| 1775 |
-
"version": "1.0.0",
|
| 1776 |
-
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
| 1777 |
-
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
|
| 1778 |
-
"license": "MIT",
|
| 1779 |
-
"engines": {
|
| 1780 |
-
"node": ">= 0.8"
|
| 1781 |
-
}
|
| 1782 |
-
},
|
| 1783 |
-
"node_modules/util-deprecate": {
|
| 1784 |
-
"version": "1.0.2",
|
| 1785 |
-
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
| 1786 |
-
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
| 1787 |
-
"license": "MIT"
|
| 1788 |
-
},
|
| 1789 |
-
"node_modules/utils-merge": {
|
| 1790 |
-
"version": "1.0.1",
|
| 1791 |
-
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
| 1792 |
-
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
| 1793 |
-
"license": "MIT",
|
| 1794 |
-
"engines": {
|
| 1795 |
-
"node": ">= 0.4.0"
|
| 1796 |
-
}
|
| 1797 |
-
},
|
| 1798 |
-
"node_modules/vary": {
|
| 1799 |
-
"version": "1.1.2",
|
| 1800 |
-
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
| 1801 |
-
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
| 1802 |
-
"license": "MIT",
|
| 1803 |
-
"engines": {
|
| 1804 |
-
"node": ">= 0.8"
|
| 1805 |
-
}
|
| 1806 |
-
},
|
| 1807 |
-
"node_modules/xtend": {
|
| 1808 |
-
"version": "4.0.2",
|
| 1809 |
-
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
| 1810 |
-
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
| 1811 |
-
"license": "MIT",
|
| 1812 |
-
"engines": {
|
| 1813 |
-
"node": ">=0.4"
|
| 1814 |
-
}
|
| 1815 |
-
}
|
| 1816 |
-
}
|
| 1817 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/backend/package.json
DELETED
|
@@ -1,30 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"name": "civicpulse-backend",
|
| 3 |
-
"version": "0.1.0",
|
| 4 |
-
"private": true,
|
| 5 |
-
"type": "module",
|
| 6 |
-
"scripts": {
|
| 7 |
-
"dev": "tsx watch src/server.ts",
|
| 8 |
-
"build": "tsc -p tsconfig.json",
|
| 9 |
-
"start": "node dist/server.js",
|
| 10 |
-
"db:init": "tsx src/scripts/init-db.ts",
|
| 11 |
-
"db:seed": "tsx src/scripts/seed-db.ts",
|
| 12 |
-
"smoke": "tsx src/scripts/smoke-test.ts"
|
| 13 |
-
},
|
| 14 |
-
"dependencies": {
|
| 15 |
-
"cors": "^2.8.5",
|
| 16 |
-
"dotenv": "^16.4.7",
|
| 17 |
-
"express": "^4.21.2",
|
| 18 |
-
"multer": "^2.1.1",
|
| 19 |
-
"pg": "^8.13.3"
|
| 20 |
-
},
|
| 21 |
-
"devDependencies": {
|
| 22 |
-
"@types/cors": "^2.8.17",
|
| 23 |
-
"@types/express": "^5.0.1",
|
| 24 |
-
"@types/multer": "^2.1.0",
|
| 25 |
-
"@types/node": "^22.13.10",
|
| 26 |
-
"@types/pg": "^8.11.11",
|
| 27 |
-
"tsx": "^4.19.3",
|
| 28 |
-
"typescript": "^5.8.2"
|
| 29 |
-
}
|
| 30 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/backend/server.err.log
DELETED
|
File without changes
|
civic-platform/backend/server.out.log
DELETED
|
File without changes
|
civic-platform/backend/sql/001_initial_schema.sql
DELETED
|
@@ -1,246 +0,0 @@
|
|
| 1 |
-
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
| 2 |
-
|
| 3 |
-
CREATE TABLE roles (
|
| 4 |
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 5 |
-
name VARCHAR(50) NOT NULL UNIQUE,
|
| 6 |
-
description TEXT,
|
| 7 |
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
| 8 |
-
);
|
| 9 |
-
|
| 10 |
-
CREATE TABLE departments (
|
| 11 |
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 12 |
-
name VARCHAR(120) NOT NULL UNIQUE,
|
| 13 |
-
code VARCHAR(40) NOT NULL UNIQUE,
|
| 14 |
-
description TEXT,
|
| 15 |
-
is_emergency BOOLEAN NOT NULL DEFAULT FALSE,
|
| 16 |
-
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
| 17 |
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
| 18 |
-
);
|
| 19 |
-
|
| 20 |
-
CREATE TABLE wards (
|
| 21 |
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 22 |
-
name VARCHAR(120) NOT NULL,
|
| 23 |
-
code VARCHAR(40) NOT NULL UNIQUE,
|
| 24 |
-
city_name VARCHAR(120) NOT NULL,
|
| 25 |
-
state_name VARCHAR(120),
|
| 26 |
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
| 27 |
-
);
|
| 28 |
-
|
| 29 |
-
CREATE TABLE users (
|
| 30 |
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 31 |
-
full_name VARCHAR(160) NOT NULL,
|
| 32 |
-
email VARCHAR(160) UNIQUE,
|
| 33 |
-
phone VARCHAR(20) UNIQUE,
|
| 34 |
-
password_hash TEXT,
|
| 35 |
-
role_id UUID NOT NULL REFERENCES roles(id),
|
| 36 |
-
department_id UUID REFERENCES departments(id),
|
| 37 |
-
ward_id UUID REFERENCES wards(id),
|
| 38 |
-
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
| 39 |
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
| 40 |
-
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
| 41 |
-
);
|
| 42 |
-
|
| 43 |
-
CREATE TABLE domains (
|
| 44 |
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 45 |
-
name VARCHAR(120) NOT NULL UNIQUE,
|
| 46 |
-
description TEXT,
|
| 47 |
-
is_emergency BOOLEAN NOT NULL DEFAULT FALSE,
|
| 48 |
-
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
| 49 |
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
| 50 |
-
);
|
| 51 |
-
|
| 52 |
-
CREATE TABLE sub_problems (
|
| 53 |
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 54 |
-
domain_id UUID NOT NULL REFERENCES domains(id) ON DELETE CASCADE,
|
| 55 |
-
name VARCHAR(120) NOT NULL,
|
| 56 |
-
description TEXT,
|
| 57 |
-
severity_hint VARCHAR(20),
|
| 58 |
-
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
| 59 |
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
| 60 |
-
CONSTRAINT unique_sub_problem_per_domain UNIQUE (domain_id, name)
|
| 61 |
-
);
|
| 62 |
-
|
| 63 |
-
CREATE TABLE complaints (
|
| 64 |
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 65 |
-
complaint_code VARCHAR(40) NOT NULL UNIQUE,
|
| 66 |
-
citizen_id UUID NOT NULL REFERENCES users(id),
|
| 67 |
-
domain_id UUID REFERENCES domains(id),
|
| 68 |
-
sub_problem_id UUID REFERENCES sub_problems(id),
|
| 69 |
-
title VARCHAR(220) NOT NULL,
|
| 70 |
-
description TEXT,
|
| 71 |
-
status VARCHAR(32) NOT NULL,
|
| 72 |
-
priority_level VARCHAR(2) NOT NULL,
|
| 73 |
-
is_emergency BOOLEAN NOT NULL DEFAULT FALSE,
|
| 74 |
-
department_id UUID REFERENCES departments(id),
|
| 75 |
-
ward_id UUID REFERENCES wards(id),
|
| 76 |
-
submitted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
| 77 |
-
resolved_at TIMESTAMPTZ,
|
| 78 |
-
closed_at TIMESTAMPTZ,
|
| 79 |
-
reopened_count INTEGER NOT NULL DEFAULT 0,
|
| 80 |
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
| 81 |
-
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
| 82 |
-
CONSTRAINT complaints_status_check CHECK (
|
| 83 |
-
status IN (
|
| 84 |
-
'submitted',
|
| 85 |
-
'validated',
|
| 86 |
-
'classified',
|
| 87 |
-
'prioritized',
|
| 88 |
-
'assigned',
|
| 89 |
-
'accepted',
|
| 90 |
-
'in_progress',
|
| 91 |
-
'resolved',
|
| 92 |
-
'citizen_verified',
|
| 93 |
-
'closed',
|
| 94 |
-
'escalated',
|
| 95 |
-
'reopened',
|
| 96 |
-
'duplicate',
|
| 97 |
-
'rejected',
|
| 98 |
-
'on_hold'
|
| 99 |
-
)
|
| 100 |
-
),
|
| 101 |
-
CONSTRAINT complaints_priority_check CHECK (priority_level IN ('P1', 'P2', 'P3', 'P4'))
|
| 102 |
-
);
|
| 103 |
-
|
| 104 |
-
CREATE TABLE complaint_locations (
|
| 105 |
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 106 |
-
complaint_id UUID NOT NULL UNIQUE REFERENCES complaints(id) ON DELETE CASCADE,
|
| 107 |
-
latitude NUMERIC(10, 7) NOT NULL,
|
| 108 |
-
longitude NUMERIC(10, 7) NOT NULL,
|
| 109 |
-
address_line TEXT,
|
| 110 |
-
landmark VARCHAR(220),
|
| 111 |
-
city_name VARCHAR(120),
|
| 112 |
-
state_name VARCHAR(120),
|
| 113 |
-
postal_code VARCHAR(20),
|
| 114 |
-
ward_id UUID REFERENCES wards(id),
|
| 115 |
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
| 116 |
-
);
|
| 117 |
-
|
| 118 |
-
CREATE TABLE complaint_media (
|
| 119 |
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 120 |
-
complaint_id UUID NOT NULL REFERENCES complaints(id) ON DELETE CASCADE,
|
| 121 |
-
uploaded_by UUID REFERENCES users(id),
|
| 122 |
-
media_type VARCHAR(20) NOT NULL,
|
| 123 |
-
file_path TEXT NOT NULL,
|
| 124 |
-
file_url TEXT,
|
| 125 |
-
mime_type VARCHAR(120),
|
| 126 |
-
is_resolution_proof BOOLEAN NOT NULL DEFAULT FALSE,
|
| 127 |
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
| 128 |
-
CONSTRAINT complaint_media_type_check CHECK (media_type IN ('image', 'video', 'audio'))
|
| 129 |
-
);
|
| 130 |
-
|
| 131 |
-
CREATE TABLE complaint_status_history (
|
| 132 |
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 133 |
-
complaint_id UUID NOT NULL REFERENCES complaints(id) ON DELETE CASCADE,
|
| 134 |
-
old_status VARCHAR(32),
|
| 135 |
-
new_status VARCHAR(32) NOT NULL,
|
| 136 |
-
changed_by UUID REFERENCES users(id),
|
| 137 |
-
change_reason TEXT,
|
| 138 |
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
| 139 |
-
);
|
| 140 |
-
|
| 141 |
-
CREATE TABLE assignments (
|
| 142 |
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 143 |
-
complaint_id UUID NOT NULL REFERENCES complaints(id) ON DELETE CASCADE,
|
| 144 |
-
department_id UUID NOT NULL REFERENCES departments(id),
|
| 145 |
-
assigned_to_user_id UUID REFERENCES users(id),
|
| 146 |
-
assigned_by_user_id UUID REFERENCES users(id),
|
| 147 |
-
assignment_status VARCHAR(20) NOT NULL,
|
| 148 |
-
assigned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
| 149 |
-
accepted_at TIMESTAMPTZ,
|
| 150 |
-
completed_at TIMESTAMPTZ,
|
| 151 |
-
notes TEXT,
|
| 152 |
-
CONSTRAINT assignments_status_check CHECK (
|
| 153 |
-
assignment_status IN ('assigned', 'accepted', 'in_progress', 'completed', 'reassigned')
|
| 154 |
-
)
|
| 155 |
-
);
|
| 156 |
-
|
| 157 |
-
CREATE TABLE routing_rules (
|
| 158 |
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 159 |
-
domain_id UUID REFERENCES domains(id),
|
| 160 |
-
sub_problem_id UUID REFERENCES sub_problems(id),
|
| 161 |
-
ward_id UUID REFERENCES wards(id),
|
| 162 |
-
department_id UUID NOT NULL REFERENCES departments(id),
|
| 163 |
-
priority_override VARCHAR(2),
|
| 164 |
-
is_emergency_route BOOLEAN NOT NULL DEFAULT FALSE,
|
| 165 |
-
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
| 166 |
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
| 167 |
-
CONSTRAINT routing_rules_priority_check CHECK (priority_override IS NULL OR priority_override IN ('P1', 'P2', 'P3', 'P4'))
|
| 168 |
-
);
|
| 169 |
-
|
| 170 |
-
CREATE TABLE priority_rules (
|
| 171 |
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 172 |
-
domain_id UUID REFERENCES domains(id),
|
| 173 |
-
sub_problem_id UUID REFERENCES sub_problems(id),
|
| 174 |
-
base_priority VARCHAR(2) NOT NULL,
|
| 175 |
-
near_sensitive_zone_boost INTEGER NOT NULL DEFAULT 0,
|
| 176 |
-
repeat_complaint_boost INTEGER NOT NULL DEFAULT 0,
|
| 177 |
-
reopened_boost INTEGER NOT NULL DEFAULT 0,
|
| 178 |
-
sla_breach_boost INTEGER NOT NULL DEFAULT 0,
|
| 179 |
-
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
| 180 |
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
| 181 |
-
CONSTRAINT priority_rules_base_check CHECK (base_priority IN ('P1', 'P2', 'P3', 'P4'))
|
| 182 |
-
);
|
| 183 |
-
|
| 184 |
-
CREATE TABLE complaint_notes (
|
| 185 |
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 186 |
-
complaint_id UUID NOT NULL REFERENCES complaints(id) ON DELETE CASCADE,
|
| 187 |
-
author_id UUID REFERENCES users(id),
|
| 188 |
-
note_type VARCHAR(20) NOT NULL,
|
| 189 |
-
note_text TEXT NOT NULL,
|
| 190 |
-
is_internal BOOLEAN NOT NULL DEFAULT TRUE,
|
| 191 |
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
| 192 |
-
CONSTRAINT complaint_notes_type_check CHECK (
|
| 193 |
-
note_type IN ('operator_note', 'field_note', 'citizen_note', 'system_note')
|
| 194 |
-
)
|
| 195 |
-
);
|
| 196 |
-
|
| 197 |
-
CREATE TABLE notifications (
|
| 198 |
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 199 |
-
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
| 200 |
-
complaint_id UUID REFERENCES complaints(id) ON DELETE CASCADE,
|
| 201 |
-
title VARCHAR(220) NOT NULL,
|
| 202 |
-
message TEXT NOT NULL,
|
| 203 |
-
notification_type VARCHAR(50) NOT NULL,
|
| 204 |
-
is_read BOOLEAN NOT NULL DEFAULT FALSE,
|
| 205 |
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
| 206 |
-
);
|
| 207 |
-
|
| 208 |
-
CREATE TABLE feedback (
|
| 209 |
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 210 |
-
complaint_id UUID NOT NULL REFERENCES complaints(id) ON DELETE CASCADE,
|
| 211 |
-
citizen_id UUID NOT NULL REFERENCES users(id),
|
| 212 |
-
rating INTEGER CHECK (rating BETWEEN 1 AND 5),
|
| 213 |
-
comment TEXT,
|
| 214 |
-
reopen_requested BOOLEAN NOT NULL DEFAULT FALSE,
|
| 215 |
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
| 216 |
-
);
|
| 217 |
-
|
| 218 |
-
CREATE TABLE audit_logs (
|
| 219 |
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 220 |
-
actor_user_id UUID REFERENCES users(id),
|
| 221 |
-
entity_type VARCHAR(50) NOT NULL,
|
| 222 |
-
entity_id UUID,
|
| 223 |
-
action VARCHAR(120) NOT NULL,
|
| 224 |
-
details JSONB,
|
| 225 |
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
| 226 |
-
);
|
| 227 |
-
|
| 228 |
-
CREATE INDEX idx_users_role_id ON users(role_id);
|
| 229 |
-
CREATE INDEX idx_users_department_id ON users(department_id);
|
| 230 |
-
CREATE INDEX idx_users_ward_id ON users(ward_id);
|
| 231 |
-
CREATE INDEX idx_sub_problems_domain_id ON sub_problems(domain_id);
|
| 232 |
-
CREATE INDEX idx_complaints_citizen_id ON complaints(citizen_id);
|
| 233 |
-
CREATE INDEX idx_complaints_domain_id ON complaints(domain_id);
|
| 234 |
-
CREATE INDEX idx_complaints_department_id ON complaints(department_id);
|
| 235 |
-
CREATE INDEX idx_complaints_ward_id ON complaints(ward_id);
|
| 236 |
-
CREATE INDEX idx_complaints_status ON complaints(status);
|
| 237 |
-
CREATE INDEX idx_complaints_priority_level ON complaints(priority_level);
|
| 238 |
-
CREATE INDEX idx_complaints_submitted_at ON complaints(submitted_at);
|
| 239 |
-
CREATE INDEX idx_complaint_media_complaint_id ON complaint_media(complaint_id);
|
| 240 |
-
CREATE INDEX idx_complaint_status_history_complaint_id ON complaint_status_history(complaint_id);
|
| 241 |
-
CREATE INDEX idx_assignments_complaint_id ON assignments(complaint_id);
|
| 242 |
-
CREATE INDEX idx_assignments_department_id ON assignments(department_id);
|
| 243 |
-
CREATE INDEX idx_routing_rules_department_id ON routing_rules(department_id);
|
| 244 |
-
CREATE INDEX idx_notifications_user_id ON notifications(user_id);
|
| 245 |
-
CREATE INDEX idx_feedback_complaint_id ON feedback(complaint_id);
|
| 246 |
-
CREATE INDEX idx_audit_logs_actor_user_id ON audit_logs(actor_user_id);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/backend/sql/002_seed_core_data.sql
DELETED
|
@@ -1,133 +0,0 @@
|
|
| 1 |
-
INSERT INTO roles (id, name, description)
|
| 2 |
-
VALUES
|
| 3 |
-
('10000000-0000-0000-0000-000000000001', 'citizen', 'Citizen user who reports and tracks complaints'),
|
| 4 |
-
('10000000-0000-0000-0000-000000000002', 'department_operator', 'Department operator who validates and assigns complaints'),
|
| 5 |
-
('10000000-0000-0000-0000-000000000003', 'municipal_admin', 'Municipal administrator with cross-department access'),
|
| 6 |
-
('10000000-0000-0000-0000-000000000004', 'field_officer', 'Field officer who executes assigned work on the ground')
|
| 7 |
-
ON CONFLICT (name) DO NOTHING;
|
| 8 |
-
|
| 9 |
-
INSERT INTO departments (id, name, code, description, is_emergency)
|
| 10 |
-
VALUES
|
| 11 |
-
('20000000-0000-0000-0000-000000000001', 'Roads and Public Works', 'roads-public-works', 'Handles roads, potholes, and transport infrastructure issues', FALSE),
|
| 12 |
-
('20000000-0000-0000-0000-000000000002', 'Sanitation and Solid Waste', 'sanitation-solid-waste', 'Handles waste collection, garbage overflow, and sanitation complaints', FALSE),
|
| 13 |
-
('20000000-0000-0000-0000-000000000003', 'Water and Sewerage', 'water-sewerage', 'Handles water leakage, sewage, and drainage issues', FALSE),
|
| 14 |
-
('20000000-0000-0000-0000-000000000004', 'Electrical and Street Lighting', 'electrical-street-lighting', 'Handles streetlight and public electrical complaints', FALSE),
|
| 15 |
-
('20000000-0000-0000-0000-000000000005', 'Municipal Maintenance', 'municipal-maintenance', 'Handles open manholes and civic infrastructure maintenance', FALSE),
|
| 16 |
-
('20000000-0000-0000-0000-000000000006', 'Disaster Management Authority', 'disaster-management', 'Handles flood, collapse, rescue, and emergency coordination', TRUE)
|
| 17 |
-
ON CONFLICT (code) DO NOTHING;
|
| 18 |
-
|
| 19 |
-
INSERT INTO wards (id, name, code, city_name, state_name)
|
| 20 |
-
VALUES
|
| 21 |
-
('30000000-0000-0000-0000-000000000001', 'Ward 4', 'ward-4', 'Sample City', 'Sample State'),
|
| 22 |
-
('30000000-0000-0000-0000-000000000002', 'Ward 8', 'ward-8', 'Sample City', 'Sample State'),
|
| 23 |
-
('30000000-0000-0000-0000-000000000003', 'Ward 12', 'ward-12', 'Sample City', 'Sample State')
|
| 24 |
-
ON CONFLICT (code) DO NOTHING;
|
| 25 |
-
|
| 26 |
-
INSERT INTO domains (id, name, description, is_emergency)
|
| 27 |
-
VALUES
|
| 28 |
-
('40000000-0000-0000-0000-000000000001', 'Roads and Transportation', 'Road safety, traffic support, and mobility complaints', FALSE),
|
| 29 |
-
('40000000-0000-0000-0000-000000000002', 'Sanitation and Waste Management', 'Waste collection, cleanliness, and sanitation complaints', FALSE),
|
| 30 |
-
('40000000-0000-0000-0000-000000000003', 'Water Supply, Sewerage, and Drainage', 'Water access, sewage, and drainage complaints', FALSE),
|
| 31 |
-
('40000000-0000-0000-0000-000000000004', 'Street Lighting and Electrical Infrastructure', 'Public lighting and electrical safety complaints', FALSE),
|
| 32 |
-
('40000000-0000-0000-0000-000000000005', 'Public Infrastructure and Amenities', 'Maintenance needs for public assets and shared facilities', FALSE),
|
| 33 |
-
('40000000-0000-0000-0000-000000000006', 'Environment and Public Health', 'Public hygiene and environmental safety concerns', FALSE),
|
| 34 |
-
('40000000-0000-0000-0000-000000000007', 'Fire Emergencies', 'Emergency fire and smoke incidents', TRUE),
|
| 35 |
-
('40000000-0000-0000-0000-000000000008', 'Flood and Water Disaster', 'Critical flood and water disaster events', TRUE)
|
| 36 |
-
ON CONFLICT (name) DO NOTHING;
|
| 37 |
-
|
| 38 |
-
INSERT INTO users (id, full_name, email, phone, password_hash, role_id, department_id, ward_id)
|
| 39 |
-
VALUES
|
| 40 |
-
(
|
| 41 |
-
'00000000-0000-0000-0000-000000000001',
|
| 42 |
-
'Sample Citizen',
|
| 43 |
-
'citizen@example.com',
|
| 44 |
-
'9000000001',
|
| 45 |
-
'dev-placeholder-hash',
|
| 46 |
-
'10000000-0000-0000-0000-000000000001',
|
| 47 |
-
NULL,
|
| 48 |
-
'30000000-0000-0000-0000-000000000003'
|
| 49 |
-
),
|
| 50 |
-
(
|
| 51 |
-
'11111111-1111-1111-1111-111111111111',
|
| 52 |
-
'Sample Operator',
|
| 53 |
-
'operator@example.com',
|
| 54 |
-
'9000000002',
|
| 55 |
-
'dev-placeholder-hash',
|
| 56 |
-
'10000000-0000-0000-0000-000000000002',
|
| 57 |
-
'20000000-0000-0000-0000-000000000005',
|
| 58 |
-
'30000000-0000-0000-0000-000000000001'
|
| 59 |
-
),
|
| 60 |
-
(
|
| 61 |
-
'22222222-2222-2222-2222-222222222222',
|
| 62 |
-
'Sample Field Officer',
|
| 63 |
-
'officer@example.com',
|
| 64 |
-
'9000000003',
|
| 65 |
-
'dev-placeholder-hash',
|
| 66 |
-
'10000000-0000-0000-0000-000000000004',
|
| 67 |
-
'20000000-0000-0000-0000-000000000005',
|
| 68 |
-
'30000000-0000-0000-0000-000000000001'
|
| 69 |
-
),
|
| 70 |
-
(
|
| 71 |
-
'22222222-2222-2222-2222-222222222223',
|
| 72 |
-
'Road Field Officer',
|
| 73 |
-
'roads.officer@example.com',
|
| 74 |
-
'9000000004',
|
| 75 |
-
'dev-placeholder-hash',
|
| 76 |
-
'10000000-0000-0000-0000-000000000004',
|
| 77 |
-
'20000000-0000-0000-0000-000000000001',
|
| 78 |
-
'30000000-0000-0000-0000-000000000001'
|
| 79 |
-
),
|
| 80 |
-
(
|
| 81 |
-
'22222222-2222-2222-2222-222222222224',
|
| 82 |
-
'Sanitation Field Officer',
|
| 83 |
-
'sanitation.officer@example.com',
|
| 84 |
-
'9000000005',
|
| 85 |
-
'dev-placeholder-hash',
|
| 86 |
-
'10000000-0000-0000-0000-000000000004',
|
| 87 |
-
'20000000-0000-0000-0000-000000000002',
|
| 88 |
-
'30000000-0000-0000-0000-000000000002'
|
| 89 |
-
),
|
| 90 |
-
(
|
| 91 |
-
'22222222-2222-2222-2222-222222222225',
|
| 92 |
-
'Water Field Officer',
|
| 93 |
-
'water.officer@example.com',
|
| 94 |
-
'9000000006',
|
| 95 |
-
'dev-placeholder-hash',
|
| 96 |
-
'10000000-0000-0000-0000-000000000004',
|
| 97 |
-
'20000000-0000-0000-0000-000000000003',
|
| 98 |
-
'30000000-0000-0000-0000-000000000002'
|
| 99 |
-
),
|
| 100 |
-
(
|
| 101 |
-
'22222222-2222-2222-2222-222222222226',
|
| 102 |
-
'Electrical Field Officer',
|
| 103 |
-
'electrical.officer@example.com',
|
| 104 |
-
'9000000007',
|
| 105 |
-
'dev-placeholder-hash',
|
| 106 |
-
'10000000-0000-0000-0000-000000000004',
|
| 107 |
-
'20000000-0000-0000-0000-000000000004',
|
| 108 |
-
'30000000-0000-0000-0000-000000000003'
|
| 109 |
-
)
|
| 110 |
-
ON CONFLICT (email) DO NOTHING;
|
| 111 |
-
|
| 112 |
-
INSERT INTO routing_rules (domain_id, sub_problem_id, ward_id, department_id, priority_override, is_emergency_route)
|
| 113 |
-
VALUES
|
| 114 |
-
('40000000-0000-0000-0000-000000000001', NULL, NULL, '20000000-0000-0000-0000-000000000001', NULL, FALSE),
|
| 115 |
-
('40000000-0000-0000-0000-000000000002', NULL, NULL, '20000000-0000-0000-0000-000000000002', NULL, FALSE),
|
| 116 |
-
('40000000-0000-0000-0000-000000000003', NULL, NULL, '20000000-0000-0000-0000-000000000003', NULL, FALSE),
|
| 117 |
-
('40000000-0000-0000-0000-000000000004', NULL, NULL, '20000000-0000-0000-0000-000000000004', NULL, FALSE),
|
| 118 |
-
('40000000-0000-0000-0000-000000000005', NULL, NULL, '20000000-0000-0000-0000-000000000005', 'P1', FALSE),
|
| 119 |
-
('40000000-0000-0000-0000-000000000006', NULL, NULL, '20000000-0000-0000-0000-000000000002', NULL, FALSE),
|
| 120 |
-
('40000000-0000-0000-0000-000000000007', NULL, NULL, '20000000-0000-0000-0000-000000000006', 'P1', TRUE),
|
| 121 |
-
('40000000-0000-0000-0000-000000000008', NULL, NULL, '20000000-0000-0000-0000-000000000006', 'P1', TRUE);
|
| 122 |
-
|
| 123 |
-
INSERT INTO priority_rules (domain_id, sub_problem_id, base_priority)
|
| 124 |
-
VALUES
|
| 125 |
-
('40000000-0000-0000-0000-000000000001', NULL, 'P3'),
|
| 126 |
-
('40000000-0000-0000-0000-000000000002', NULL, 'P3'),
|
| 127 |
-
('40000000-0000-0000-0000-000000000003', NULL, 'P2'),
|
| 128 |
-
('40000000-0000-0000-0000-000000000004', NULL, 'P3'),
|
| 129 |
-
('40000000-0000-0000-0000-000000000005', NULL, 'P2'),
|
| 130 |
-
('40000000-0000-0000-0000-000000000006', NULL, 'P3'),
|
| 131 |
-
('40000000-0000-0000-0000-000000000007', NULL, 'P1'),
|
| 132 |
-
('40000000-0000-0000-0000-000000000008', NULL, 'P1')
|
| 133 |
-
ON CONFLICT DO NOTHING;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/backend/sql/003_performance_indexes.sql
DELETED
|
@@ -1,32 +0,0 @@
|
|
| 1 |
-
CREATE INDEX IF NOT EXISTS idx_complaints_status_submitted_at
|
| 2 |
-
ON complaints (status, submitted_at DESC);
|
| 3 |
-
|
| 4 |
-
CREATE INDEX IF NOT EXISTS idx_complaints_department_status
|
| 5 |
-
ON complaints (department_id, status, submitted_at DESC);
|
| 6 |
-
|
| 7 |
-
CREATE INDEX IF NOT EXISTS idx_complaints_domain_submitted_at
|
| 8 |
-
ON complaints (domain_id, submitted_at DESC);
|
| 9 |
-
|
| 10 |
-
CREATE INDEX IF NOT EXISTS idx_complaints_priority_status
|
| 11 |
-
ON complaints (priority_level, status, submitted_at DESC);
|
| 12 |
-
|
| 13 |
-
CREATE INDEX IF NOT EXISTS idx_assignments_assigned_to_status
|
| 14 |
-
ON assignments (assigned_to_user_id, assignment_status, assigned_at DESC);
|
| 15 |
-
|
| 16 |
-
CREATE INDEX IF NOT EXISTS idx_assignments_complaint_assigned_at
|
| 17 |
-
ON assignments (complaint_id, assigned_at DESC);
|
| 18 |
-
|
| 19 |
-
CREATE INDEX IF NOT EXISTS idx_notifications_user_read_created
|
| 20 |
-
ON notifications (user_id, is_read, created_at DESC);
|
| 21 |
-
|
| 22 |
-
CREATE INDEX IF NOT EXISTS idx_status_history_complaint_created
|
| 23 |
-
ON complaint_status_history (complaint_id, created_at DESC);
|
| 24 |
-
|
| 25 |
-
CREATE INDEX IF NOT EXISTS idx_feedback_complaint_created
|
| 26 |
-
ON feedback (complaint_id, created_at DESC);
|
| 27 |
-
|
| 28 |
-
CREATE INDEX IF NOT EXISTS idx_notes_complaint_created
|
| 29 |
-
ON complaint_notes (complaint_id, created_at DESC);
|
| 30 |
-
|
| 31 |
-
CREATE INDEX IF NOT EXISTS idx_locations_lat_lng
|
| 32 |
-
ON complaint_locations (latitude, longitude);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/backend/sql/README.md
DELETED
|
@@ -1,22 +0,0 @@
|
|
| 1 |
-
# SQL Files
|
| 2 |
-
|
| 3 |
-
## Current files
|
| 4 |
-
|
| 5 |
-
- `001_initial_schema.sql`
|
| 6 |
-
- `002_seed_core_data.sql`
|
| 7 |
-
|
| 8 |
-
## Usage
|
| 9 |
-
|
| 10 |
-
Apply the schema file to the `civicpulse` PostgreSQL database before running the complaint APIs.
|
| 11 |
-
|
| 12 |
-
Recommended order:
|
| 13 |
-
|
| 14 |
-
1. create database
|
| 15 |
-
2. apply `001_initial_schema.sql`
|
| 16 |
-
3. apply `002_seed_core_data.sql`
|
| 17 |
-
4. start backend
|
| 18 |
-
|
| 19 |
-
Future migrations should continue with:
|
| 20 |
-
|
| 21 |
-
- `003_...`
|
| 22 |
-
- `004_...`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/backend/src/app.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
| 1 |
-
import fs from "node:fs";
|
| 2 |
-
import path from "node:path";
|
| 3 |
-
import cors from "cors";
|
| 4 |
-
import express from "express";
|
| 5 |
-
import { aiRouter } from "./modules/ai/ai.routes.js";
|
| 6 |
-
import { authRouter } from "./modules/auth/auth.routes.js";
|
| 7 |
-
import { env } from "./config/env.js";
|
| 8 |
-
import { rateLimitMiddleware, securityHeaders } from "./lib/security.js";
|
| 9 |
-
import { complaintRouter } from "./modules/complaints/complaint.routes.js";
|
| 10 |
-
import { departmentRouter } from "./modules/departments/department.routes.js";
|
| 11 |
-
import { domainRouter } from "./modules/domains/domain.routes.js";
|
| 12 |
-
import { userRouter } from "./modules/users/user.routes.js";
|
| 13 |
-
import { healthRouter } from "./routes/health.js";
|
| 14 |
-
|
| 15 |
-
export function createApp() {
|
| 16 |
-
const app = express();
|
| 17 |
-
const uploadsDir = path.resolve(process.cwd(), "uploads");
|
| 18 |
-
|
| 19 |
-
fs.mkdirSync(path.join(uploadsDir, "complaints"), { recursive: true });
|
| 20 |
-
|
| 21 |
-
app.set("trust proxy", 1);
|
| 22 |
-
app.use(
|
| 23 |
-
cors({
|
| 24 |
-
origin: env.corsOrigins.split(",").map((value) => value.trim()),
|
| 25 |
-
}),
|
| 26 |
-
);
|
| 27 |
-
app.use(securityHeaders);
|
| 28 |
-
app.use(rateLimitMiddleware);
|
| 29 |
-
app.use(express.json({ limit: "10mb" }));
|
| 30 |
-
app.use("/uploads", express.static(uploadsDir));
|
| 31 |
-
|
| 32 |
-
app.get("/", (_req, res) => {
|
| 33 |
-
res.json({
|
| 34 |
-
name: "civicpulse-backend",
|
| 35 |
-
message: "CivicPulse backend is running",
|
| 36 |
-
});
|
| 37 |
-
});
|
| 38 |
-
|
| 39 |
-
app.use("/api/health", healthRouter);
|
| 40 |
-
app.use("/api/ai", aiRouter);
|
| 41 |
-
app.use("/api/auth", authRouter);
|
| 42 |
-
app.use("/api/departments", departmentRouter);
|
| 43 |
-
app.use("/api/domains", domainRouter);
|
| 44 |
-
app.use("/api/users", userRouter);
|
| 45 |
-
app.use("/api/complaints", complaintRouter);
|
| 46 |
-
|
| 47 |
-
return app;
|
| 48 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/backend/src/config/env.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
| 1 |
-
import dotenv from "dotenv";
|
| 2 |
-
|
| 3 |
-
dotenv.config();
|
| 4 |
-
|
| 5 |
-
function requireEnv(name: string, fallback?: string): string {
|
| 6 |
-
const value = process.env[name] ?? fallback;
|
| 7 |
-
|
| 8 |
-
if (!value) {
|
| 9 |
-
throw new Error(`Missing required environment variable: ${name}`);
|
| 10 |
-
}
|
| 11 |
-
|
| 12 |
-
return value;
|
| 13 |
-
}
|
| 14 |
-
|
| 15 |
-
export const env = {
|
| 16 |
-
nodeEnv: requireEnv("NODE_ENV", "development"),
|
| 17 |
-
port: Number(requireEnv("PORT", "4000")),
|
| 18 |
-
corsOrigins: requireEnv("CORS_ORIGINS", "http://localhost:3000"),
|
| 19 |
-
dbHost: requireEnv("DB_HOST", "localhost"),
|
| 20 |
-
dbPort: Number(requireEnv("DB_PORT", "5432")),
|
| 21 |
-
dbName: requireEnv("DB_NAME", "civicpulse"),
|
| 22 |
-
dbUser: requireEnv("DB_USER", "postgres"),
|
| 23 |
-
dbPassword: requireEnv("DB_PASSWORD"),
|
| 24 |
-
dbSsl: requireEnv("DB_SSL", "false") === "true",
|
| 25 |
-
demoAuthPassword: requireEnv("DEMO_AUTH_PASSWORD", "civicpulse123"),
|
| 26 |
-
rateLimitWindowMs: Number(requireEnv("RATE_LIMIT_WINDOW_MS", "60000")),
|
| 27 |
-
rateLimitMaxRequests: Number(requireEnv("RATE_LIMIT_MAX_REQUESTS", "120")),
|
| 28 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/backend/src/db/pool.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
| 1 |
-
import { Pool } from "pg";
|
| 2 |
-
import { env } from "../config/env.js";
|
| 3 |
-
|
| 4 |
-
export const db = new Pool({
|
| 5 |
-
host: env.dbHost,
|
| 6 |
-
port: env.dbPort,
|
| 7 |
-
database: env.dbName,
|
| 8 |
-
user: env.dbUser,
|
| 9 |
-
password: env.dbPassword,
|
| 10 |
-
ssl: env.dbSsl ? { rejectUnauthorized: false } : false,
|
| 11 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
civic-platform/backend/src/lib/audit.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
| 1 |
-
import { db } from "../db/pool.js";
|
| 2 |
-
|
| 3 |
-
export async function writeAuditLog(input: {
|
| 4 |
-
actorUserId?: string | null;
|
| 5 |
-
entityType: string;
|
| 6 |
-
entityId?: string | null;
|
| 7 |
-
action: string;
|
| 8 |
-
details?: Record<string, unknown>;
|
| 9 |
-
}) {
|
| 10 |
-
await db.query(
|
| 11 |
-
`
|
| 12 |
-
INSERT INTO audit_logs (
|
| 13 |
-
actor_user_id,
|
| 14 |
-
entity_type,
|
| 15 |
-
entity_id,
|
| 16 |
-
action,
|
| 17 |
-
details
|
| 18 |
-
)
|
| 19 |
-
VALUES ($1, $2, $3, $4, $5::jsonb)
|
| 20 |
-
`,
|
| 21 |
-
[
|
| 22 |
-
input.actorUserId ?? null,
|
| 23 |
-
input.entityType,
|
| 24 |
-
input.entityId ?? null,
|
| 25 |
-
input.action,
|
| 26 |
-
JSON.stringify(input.details ?? {}),
|
| 27 |
-
],
|
| 28 |
-
);
|
| 29 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|