From 657191ce2bf6259f626c26457903ab48455df552 Mon Sep 17 00:00:00 2001 From: Arjun Mehar Date: Fri, 17 Apr 2026 19:58:45 +0530 Subject: [PATCH 1/4] Implemented CPC-CSD OCR --- .env.docker | 5 + ...rkflow_CPC_CDC_API.postman_collection.json | 890 +++++++++++++ ...kflow_CPC_CDC_API.postman_environment.json | 219 ++++ ..._OCR_SingleRequest.postman_collection.json | 90 ++ ...OCR_SingleRequest.postman_environment.json | 65 + ...eld_API_Collection.postman_collection.json | 33 +- docs/CPC-CDC.md | 68 + package-lock.json | 795 +++++++++++- package.json | 6 + set-admin.ts | 43 + src/app.ts | 22 +- src/controllers/CpcCdcController.ts | 1147 +++++++++++++++++ src/controllers/CpcReportController.ts | 204 +++ src/controllers/admin.controller.ts | 96 ++ src/controllers/cpcPermission.controller.ts | 36 + src/middlewares/cors.middleware.ts | 26 +- src/middlewares/cpcPermission.middleware.ts | 34 + .../2026041300-create-cpc-cdc-tables.ts | 130 ++ ...60414100000-ensure-cpc-cdc-tables-exist.ts | 50 + ...5120000-cpc-documents-unique-by-booking.ts | 26 + ...6120000-rename-cpc-cdc-admin-config-key.ts | 26 + src/models/CpcAuditLog.ts | 89 ++ src/models/CpcDocument.ts | 143 ++ src/models/index.ts | 14 +- src/routes/admin.routes.ts | 18 + src/routes/cpc-cdc.routes.ts | 60 + src/routes/cpc-csd-compat.mount.ts | 89 ++ src/routes/cpc-permission.routes.ts | 16 + src/routes/index.ts | 7 + src/scripts/migrate-cpc-csd-to-cpc-tables.ts | 163 +++ src/scripts/migrate.ts | 7 + src/server.ts | 9 +- src/services/auth.service.ts | 184 +-- src/services/cpc-cdc/CpcGcsService.ts | 71 + src/services/cpc-cdc/CpcHistoryService.ts | 301 +++++ src/services/cpc-cdc/CpcOcrService.ts | 44 + src/services/cpc-cdc/CpcRuleExtractService.ts | 371 ++++++ src/services/cpc-cdc/CpcValidationService.ts | 802 ++++++++++++ src/services/cpc-cdc/ensureCpcCdcSchema.ts | 55 + src/services/cpc-cdc/extractPdfText.ts | 22 + src/services/cpc-cdc/utils.ts | 195 +++ src/services/cpcPermission.service.ts | 54 + src/services/gcsStorage.service.ts | 152 +++ src/services/notification.service.ts | 8 +- src/utils/cpcCsdAdminConfigDb.ts | 25 + src/validators/admin.validator.ts | 5 + 46 files changed, 6702 insertions(+), 213 deletions(-) create mode 100644 .env.docker create mode 100644 RE-CPC-CDC_POSTMAN_COLLECTION/RE_Workflow_CPC_CDC_API.postman_collection.json create mode 100644 RE-CPC-CDC_POSTMAN_COLLECTION/RE_Workflow_CPC_CDC_API.postman_environment.json create mode 100644 RE-CPC-CDC_POSTMAN_COLLECTION/RE_Workflow_CPC_CDC_OCR_SingleRequest.postman_collection.json create mode 100644 RE-CPC-CDC_POSTMAN_COLLECTION/RE_Workflow_CPC_CDC_OCR_SingleRequest.postman_environment.json create mode 100644 docs/CPC-CDC.md create mode 100644 set-admin.ts create mode 100644 src/controllers/CpcCdcController.ts create mode 100644 src/controllers/CpcReportController.ts create mode 100644 src/controllers/cpcPermission.controller.ts create mode 100644 src/middlewares/cpcPermission.middleware.ts create mode 100644 src/migrations/2026041300-create-cpc-cdc-tables.ts create mode 100644 src/migrations/20260414100000-ensure-cpc-cdc-tables-exist.ts create mode 100644 src/migrations/20260415120000-cpc-documents-unique-by-booking.ts create mode 100644 src/migrations/20260416120000-rename-cpc-cdc-admin-config-key.ts create mode 100644 src/models/CpcAuditLog.ts create mode 100644 src/models/CpcDocument.ts create mode 100644 src/routes/cpc-cdc.routes.ts create mode 100644 src/routes/cpc-csd-compat.mount.ts create mode 100644 src/routes/cpc-permission.routes.ts create mode 100644 src/scripts/migrate-cpc-csd-to-cpc-tables.ts create mode 100644 src/services/cpc-cdc/CpcGcsService.ts create mode 100644 src/services/cpc-cdc/CpcHistoryService.ts create mode 100644 src/services/cpc-cdc/CpcOcrService.ts create mode 100644 src/services/cpc-cdc/CpcRuleExtractService.ts create mode 100644 src/services/cpc-cdc/CpcValidationService.ts create mode 100644 src/services/cpc-cdc/ensureCpcCdcSchema.ts create mode 100644 src/services/cpc-cdc/extractPdfText.ts create mode 100644 src/services/cpc-cdc/utils.ts create mode 100644 src/services/cpcPermission.service.ts create mode 100644 src/utils/cpcCsdAdminConfigDb.ts diff --git a/.env.docker b/.env.docker new file mode 100644 index 0000000..f19698f --- /dev/null +++ b/.env.docker @@ -0,0 +1,5 @@ +# Auto-loaded by `docker compose` for ${VAR} substitution in docker-compose.yml (not passed into every container). +# Okta SPA values (public) — same preview tenant as CPC-CSD client dev. +VITE_OKTA_DOMAIN=https://dev-830839.oktapreview.com +VITE_OKTA_CLIENT_ID=0oa2jgzvrpdwx2iqd0h8 + \ No newline at end of file diff --git a/RE-CPC-CDC_POSTMAN_COLLECTION/RE_Workflow_CPC_CDC_API.postman_collection.json b/RE-CPC-CDC_POSTMAN_COLLECTION/RE_Workflow_CPC_CDC_API.postman_collection.json new file mode 100644 index 0000000..b612df1 --- /dev/null +++ b/RE-CPC-CDC_POSTMAN_COLLECTION/RE_Workflow_CPC_CDC_API.postman_collection.json @@ -0,0 +1,890 @@ +{ + "info": { + "_postman_id": "re-workflow-cpc-csd-complete-2026", + "name": "RE Workflow — CPC-CSD API (complete)", + "description": "## Purpose\nCovers **all CPC-CSD HTTP APIs** used by the browser (Dashboard, History, reports) so Postman can replace manual UI testing once tokens and URLs are set.\n\n## Authentication\n1. Import **RE_Workflow_CPC_CDC_API** environment and select it.\n2. Paste JWT into **accessToken** (no `Bearer ` prefix).\n3. Run **01_Session_and_health → GET Auth me** — expect **200**. Then use **02_CPC_CSD_API** and onward.\n4. User must be **ADMIN** or listed in **CPC_CSD_ADMIN_CONFIG** viewer emails.\n\n## URL base\n- **apiRoot** = `{hostUrl}/api/v1` — all CPC-CSD requests in this collection use **`{apiRoot}/cpc-csd/...`** (canonical API; legacy **`{apiRoot}/cpc-cdc/...`** is the same).\n- The SPA may still call **`{hostUrl}/api/documents/...`** (legacy layout); behaviour is the same — see `docs/CPC-CDC.md` if you need those paths.\n- **Bare GCS staging** (no metadata): `POST {hostUrl}/api/upload` — single multipart field **`file`**.\n\n## Multipart (same as `Dashboard.jsx`)\n| Operation | Text fields | Files |\n|-----------|-------------|-------|\n| `POST .../v1/ocr/upload` | `claim_id`, `booking_id`, `booking_type` (CPC or CSD), `provider`, **`metadata_queue`** (JSON **string** of array) | **`files`** — repeat field name; order matches `metadata_queue` |\n| `POST .../v1/ocr/validate-upload` | `claim_id`, `booking_id`, `booking_type`, `document_type`, `provider`, **`msd_payload`** (JSON string), optional `skip_min_attachment_check=true` | **`file`** (single) |\n| `POST /api/upload` | — | **`file`** |\n\nEach `metadata_queue[]` item: `document_type`, `msd_payload` (object), `expected_field_keys` (unique list of keys to run rules on).\n\n## Metadata — business names vs JSON keys (`metadata_queue` / `msd_payload`)\nUse **these JSON property names** in each `msd_payload` and the same names in `expected_field_keys` (see env `metadataQueueJsonCsdPo`, `metadataQueueJsonCpcTwoFiles`). **Legacy keys** from older integrations are still accepted (`order_or_authorisation_number`, `invoice_value`, `govt_signatory_and_stamp_present`, `authorized_person_name`, `name`, `aadhaar_number`).\n\n### 1. CSD claim (1 document) — Purchase Order (PO)\n| Business name | JSON key | Rule |\n|----------------|----------|------|\n| Customer Name | `customer_name` | Accuracy between 90% – 100% |\n| PO Number | `po_number` | 100% accuracy required |\n| PO Amount | `po_amount` | Tolerance of ±5 rupees |\n| Signature & Stamp | `signature_and_stamp` | Binary check (Available / Not Available) |\n\n### 2. CPC claim (2 documents)\n**Document 1 — Authorization Letter**\n| Customer Name | `customer_name` | 90% – 100% |\n| Letter Number | `letter_number` | 90% – 100% |\n| Letter Amount | `letter_amount` | ±5 rupees |\n| Signature & Stamp | `signature_and_stamp` | Binary (Available / Not Available) |\n\n**Document 2 — Aadhaar card**\n| Customer Name | `customer_name` | 90% – 100% |\n| Aadhar Number | `aadhar_number` | 100% accuracy required |\n\n## Provider vs model\n- **ocrProvider** in env: pipeline mode (`GEMINI_VERTEX_DIRECT`, `GEMINI_VERTEX`, `RULES`).\n- **Gemini model id** (e.g. `gemini-2.0-flash-lite`) is **server** `GEMINI_MODEL` in `re-workflow-be/.env`, not Postman.\n\n## Limits\n- 15 MB per file; ZIP not allowed; max 20 files on bulk upload.\n\n## Reference\n- Repo: `re-workflow-be/docs/CPC-CDC.md`", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + }, + "variable": [ + { + "key": "hostUrl", + "value": "http://localhost:5000" + }, + { + "key": "apiRoot", + "value": "http://localhost:5000/api/v1" + } + ], + "item": [ + { + "name": "01_Session_and_health", + "description": "Verify connectivity and JWT before CPC calls.", + "item": [ + { + "name": "GET Health (no auth)", + "request": { + "method": "GET", + "header": [], + "url": "{{hostUrl}}/health", + "description": "Public liveness. No Bearer.", + "auth": { + "type": "noauth" + } + } + }, + { + "name": "GET Auth me", + "request": { + "method": "GET", + "header": [], + "url": "{{apiRoot}}/auth/me", + "description": "Confirms accessToken is accepted.", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + } + } + } + ] + }, + { + "name": "02_CPC_CSD_API", + "description": "Canonical routes: `{{apiRoot}}/cpc-csd/...` plus bare `POST /api/upload`.", + "item": [ + { + "name": "GET Permissions", + "request": { + "method": "GET", + "header": [], + "url": "{{apiRoot}}/cpc-csd/permissions", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "GET Documents analytics", + "request": { + "method": "GET", + "header": [], + "url": "{{apiRoot}}/cpc-csd/documents/analytics", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "GET Documents history by claim", + "request": { + "method": "GET", + "header": [], + "url": "{{apiRoot}}/cpc-csd/documents/history?claimId={{claimIdCpc}}", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "GET Documents recent (paginated)", + "request": { + "method": "GET", + "header": [], + "url": "{{apiRoot}}/cpc-csd/documents/recent?page={{recentPage}}&limit={{recentLimit}}&search={{recentSearch}}&status={{recentStatus}}&type={{recentType}}&sortBy={{recentSortBy}}&order={{recentOrder}}", + "description": "Optional: Test script saves first item id into **cpcDocumentId** for follow-up GET/PUT/DELETE.", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + } + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "if (pm.response.code === 200) {", + " try {", + " const j = pm.response.json();", + " const items = j.items || [];", + " if (items.length && items[0].id != null) {", + " pm.environment.set('cpcDocumentId', String(items[0].id));", + " }", + " } catch (e) { /* ignore */ }", + "}" + ], + "type": "text/javascript" + } + } + ] + }, + { + "name": "GET Document by id", + "request": { + "method": "GET", + "header": [], + "url": "{{apiRoot}}/cpc-csd/documents/{{cpcDocumentId}}", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "GET Document file binary", + "request": { + "method": "GET", + "header": [], + "url": "{{apiRoot}}/cpc-csd/documents/{{cpcDocumentId}}/file", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "PUT Document status", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{putStatusBodyJson}}" + }, + "url": "{{apiRoot}}/cpc-csd/documents/{{cpcDocumentId}}/status", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "DELETE Document", + "request": { + "method": "DELETE", + "header": [], + "url": "{{apiRoot}}/cpc-csd/documents/{{cpcDocumentId}}", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "GET Report Excel per claim", + "request": { + "method": "GET", + "header": [], + "url": "{{apiRoot}}/cpc-csd/v1/ocr/report/{{claimIdCpc}}/download{{reportAttemptQuery}}", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "GET Report Excel master", + "request": { + "method": "GET", + "header": [], + "url": "{{apiRoot}}/cpc-csd/v1/ocr/report/all/download?search={{masterReportSearch}}&status={{masterReportStatus}}&type={{masterReportType}}", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "GET Report per claim (alt path)", + "request": { + "method": "GET", + "header": [], + "url": "{{apiRoot}}/cpc-csd/report/{{claimIdCpc}}/download{{reportAttemptQuery}}", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "GET Report master (alt path)", + "request": { + "method": "GET", + "header": [], + "url": "{{apiRoot}}/cpc-csd/report/all/download", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "POST Bare file upload (GCS)", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": [] + } + ] + }, + "url": "{{hostUrl}}/api/upload", + "description": "Returns `{ gcsUrl }`. Same as compat route; not under /cpc-csd prefix.", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + } + } + } + ] + }, + { + "name": "03_Upload_CSD_PO", + "description": "**CSD — 1 document — Purchase Order (PO).** One `files` part. `metadata_queue` = JSON array with one object: `document_type` CSD_PO, `msd_payload` + `expected_field_keys` using `customer_name`, `po_number`, `po_amount`, `signature_and_stamp` (see collection root).", + "item": [ + { + "name": "POST Upload CSD", + "description": "**CSD PO upload.** Form-data `metadata_queue` must be a **stringified JSON** array (see env `metadataQueueJsonCsdPo`). Keys: `customer_name`, `po_number`, `po_amount`, `signature_and_stamp`.", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "claim_id", + "value": "{{claimIdCsd}}", + "type": "text" + }, + { + "key": "booking_id", + "value": "{{claimIdCsd}}", + "type": "text" + }, + { + "key": "booking_type", + "value": "CSD", + "type": "text" + }, + { + "key": "provider", + "value": "{{ocrProvider}}", + "type": "text" + }, + { + "key": "metadata_queue", + "value": "{{metadataQueueJsonCsdPo}}", + "type": "text", + "description": "JSON string: 1× CSD_PO. Keys = `customer_name`, `po_number`, `po_amount`, `signature_and_stamp` (rules in collection description)." + }, + { + "key": "files", + "type": "file", + "src": [] + } + ] + }, + "url": "{{apiRoot}}/cpc-csd/v1/ocr/upload", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + } + } + } + ] + }, + { + "name": "04_Upload_CPC_two_files", + "description": "**CPC — 2 documents.** Two `files` parts (same field name `files` twice). `metadata_queue` array order must match file order: **[1] Authorization letter (CPC_AUTH)** then **[2] Aadhaar (AADHAAR)** — field names per collection root description.", + "item": [ + { + "name": "POST Upload CPC (2 files)", + "description": "**CPC two-file upload.** `metadata_queue` = env `metadataQueueJsonCpcTwoFiles`. Doc1: `customer_name`, `letter_number`, `letter_amount`, `signature_and_stamp`. Doc2: `customer_name`, `aadhar_number`.", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "claim_id", + "value": "{{claimIdCpc}}", + "type": "text" + }, + { + "key": "booking_id", + "value": "{{claimIdCpc}}", + "type": "text" + }, + { + "key": "booking_type", + "value": "CPC", + "type": "text" + }, + { + "key": "provider", + "value": "{{ocrProvider}}", + "type": "text" + }, + { + "key": "metadata_queue", + "value": "{{metadataQueueJsonCpcTwoFiles}}", + "type": "text", + "description": "JSON string: [0]=CPC_AUTH (`customer_name`, `letter_number`, `letter_amount`, `signature_and_stamp`), [1]=AADHAAR (`customer_name`, `aadhar_number`). Order matches the two `files` rows." + }, + { + "key": "files", + "type": "file", + "src": [] + }, + { + "key": "files", + "type": "file", + "src": [] + } + ] + }, + "url": "{{apiRoot}}/cpc-csd/v1/ocr/upload", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + } + } + } + ] + }, + { + "name": "05_Validate_upload_single_file", + "description": "Single `file`; `document_type` + `msd_payload` JSON string.", + "item": [ + { + "name": "validate-upload CPC_AUTH", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "claim_id", + "value": "{{claimIdCpc}}", + "type": "text" + }, + { + "key": "booking_id", + "value": "{{claimIdCpc}}", + "type": "text" + }, + { + "key": "booking_type", + "value": "CPC", + "type": "text" + }, + { + "key": "document_type", + "value": "CPC_AUTH", + "type": "text" + }, + { + "key": "provider", + "value": "{{ocrProvider}}", + "type": "text" + }, + { + "key": "msd_payload", + "value": "{{msdPayloadCpcAuth}}", + "type": "text" + }, + { + "key": "skip_min_attachment_check", + "value": "true", + "type": "text" + }, + { + "key": "file", + "type": "file", + "src": [] + } + ] + }, + "url": "{{apiRoot}}/cpc-csd/v1/ocr/validate-upload", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "validate-upload AADHAAR", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "claim_id", + "value": "{{claimIdCpc}}", + "type": "text" + }, + { + "key": "booking_id", + "value": "{{claimIdCpc}}", + "type": "text" + }, + { + "key": "booking_type", + "value": "CPC", + "type": "text" + }, + { + "key": "document_type", + "value": "AADHAAR", + "type": "text" + }, + { + "key": "provider", + "value": "{{ocrProvider}}", + "type": "text" + }, + { + "key": "msd_payload", + "value": "{{msdPayloadAadhaar}}", + "type": "text" + }, + { + "key": "skip_min_attachment_check", + "value": "true", + "type": "text" + }, + { + "key": "file", + "type": "file", + "src": [] + } + ] + }, + "url": "{{apiRoot}}/cpc-csd/v1/ocr/validate-upload", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "validate-upload CSD_PO", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "claim_id", + "value": "{{claimIdCsd}}", + "type": "text" + }, + { + "key": "booking_id", + "value": "{{claimIdCsd}}", + "type": "text" + }, + { + "key": "booking_type", + "value": "CSD", + "type": "text" + }, + { + "key": "document_type", + "value": "CSD_PO", + "type": "text" + }, + { + "key": "provider", + "value": "{{ocrProvider}}", + "type": "text" + }, + { + "key": "msd_payload", + "value": "{{msdPayloadCsdPo}}", + "type": "text" + }, + { + "key": "skip_min_attachment_check", + "value": "true", + "type": "text" + }, + { + "key": "file", + "type": "file", + "src": [] + } + ] + }, + "url": "{{apiRoot}}/cpc-csd/v1/ocr/validate-upload", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "validate-upload RETAIL_INVOICE", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "claim_id", + "value": "{{claimIdRetail}}", + "type": "text" + }, + { + "key": "booking_id", + "value": "{{claimIdRetail}}", + "type": "text" + }, + { + "key": "booking_type", + "value": "CSD", + "type": "text" + }, + { + "key": "document_type", + "value": "RETAIL_INVOICE", + "type": "text" + }, + { + "key": "provider", + "value": "{{ocrProvider}}", + "type": "text" + }, + { + "key": "msd_payload", + "value": "{{msdPayloadRetailInvoice}}", + "type": "text" + }, + { + "key": "skip_min_attachment_check", + "value": "true", + "type": "text" + }, + { + "key": "file", + "type": "file", + "src": [] + } + ] + }, + "url": "{{apiRoot}}/cpc-csd/v1/ocr/validate-upload", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + } + } + } + ] + }, + { + "name": "06_Upload_CPC_single_skip_min", + "description": "`skip_min_attachment_check=true` + one `files` part.", + "item": [ + { + "name": "POST CPC one file skip min", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "claim_id", + "value": "{{claimIdCpc}}", + "type": "text" + }, + { + "key": "booking_id", + "value": "{{claimIdCpc}}", + "type": "text" + }, + { + "key": "booking_type", + "value": "CPC", + "type": "text" + }, + { + "key": "provider", + "value": "{{ocrProvider}}", + "type": "text" + }, + { + "key": "metadata_queue", + "value": "{{metadataQueueJsonCpcAuthOnly}}", + "type": "text", + "description": "JSON string: single CPC_AUTH — same keys as CPC doc1 (`customer_name`, `letter_number`, `letter_amount`, `signature_and_stamp`)." + }, + { + "key": "skip_min_attachment_check", + "value": "true", + "type": "text" + }, + { + "key": "files", + "type": "file", + "src": [] + } + ] + }, + "url": "{{apiRoot}}/cpc-csd/v1/ocr/upload", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + } + } + } + ] + }, + { + "name": "07_Upload_retail_invoice", + "description": "Single retail invoice file.", + "item": [ + { + "name": "POST Upload RETAIL_INVOICE", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "claim_id", + "value": "{{claimIdRetail}}", + "type": "text" + }, + { + "key": "booking_id", + "value": "{{claimIdRetail}}", + "type": "text" + }, + { + "key": "booking_type", + "value": "CSD", + "type": "text" + }, + { + "key": "provider", + "value": "{{ocrProvider}}", + "type": "text" + }, + { + "key": "metadata_queue", + "value": "{{metadataQueueJsonRetailInvoice}}", + "type": "text", + "description": "JSON string: RETAIL_INVOICE (not CSD/CPC PO flow — see env `metadataQueueJsonRetailInvoice`)." + }, + { + "key": "files", + "type": "file", + "src": [] + } + ] + }, + "url": "{{apiRoot}}/cpc-csd/v1/ocr/upload", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + } + } + } + ] + }, + { + "name": "08_JSON_validate_GCS_URL", + "description": "Requires real `gs://` object.", + "item": [ + { + "name": "POST validate JSON (GCS URL)", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"claim_id\": \"{{claimIdCpc}}\",\n \"document_type\": \"CPC_AUTH\",\n \"document_gcp_url\": \"{{documentGcpUrl}}\",\n \"msd_payload\": {\n \"customer_name\": \"Amit Kumar\",\n \"letter_number\": \"AUTH-1\",\n \"letter_amount\": \"45000\",\n \"signature_and_stamp\": \"yes\"\n },\n \"provider\": \"{{ocrProvider}}\"\n}" + }, + "url": "{{apiRoot}}/cpc-csd/v1/ocr/validate", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + } + } + } + ] + } + ] +} \ No newline at end of file diff --git a/RE-CPC-CDC_POSTMAN_COLLECTION/RE_Workflow_CPC_CDC_API.postman_environment.json b/RE-CPC-CDC_POSTMAN_COLLECTION/RE_Workflow_CPC_CDC_API.postman_environment.json new file mode 100644 index 0000000..8fd58e1 --- /dev/null +++ b/RE-CPC-CDC_POSTMAN_COLLECTION/RE_Workflow_CPC_CDC_API.postman_environment.json @@ -0,0 +1,219 @@ +{ + "id": "re-workflow-cpc-csd-api-env", + "name": "RE Workflow — CPC-CSD API (complete)", + "values": [ + { + "key": "hostUrl", + "value": "http://localhost:5000", + "type": "default", + "enabled": true, + "description": "API origin only (scheme + host + port). No path. Node dev: 5000. Docker host-mapped API: often 5004. Nginx all-in-one: use 8080 only if you proxy everything through it." + }, + { + "key": "apiRoot", + "value": "http://localhost:5000/api/v1", + "type": "default", + "enabled": true, + "description": "Must equal {{hostUrl}}/api/v1. All CPC-CSD REST calls in the bundled collection use {{apiRoot}}/cpc-csd/... Bare GCS upload uses POST {{hostUrl}}/api/upload (see collection folder 02). The SPA may still use {{hostUrl}}/api/documents/* — same handlers; see docs/CPC-CDC.md if you need those URLs." + }, + { + "key": "accessToken", + "value": "", + "type": "secret", + "enabled": true, + "description": "JWT only (no Bearer prefix). From browser: DevTools → Application → Local Storage → access_token, or Network → Authorization header value after Bearer. Required for all CPC-CSD routes except GET /health." + }, + { + "key": "refreshToken", + "value": "", + "type": "secret", + "enabled": true, + "description": "Optional. Only if you chain POST /auth/refresh from another collection." + }, + { + "key": "ocrProvider", + "value": "GEMINI_VERTEX_DIRECT", + "type": "default", + "enabled": true, + "description": "Multipart field provider. GEMINI_VERTEX_DIRECT = Gemini on document bytes; skips Document AI OCR even if configured. GEMINI_VERTEX = optional Document AI then Gemini. RULES = rules engine on OCR text only, no Vertex." + }, + { + "key": "claimIdCpc", + "value": "CPC-POSTMAN-0001", + "type": "default", + "enabled": true, + "description": "claim_id and booking_id for CPC runs (same pattern as Dashboard finalBookingId: CPC-{suffix}). Must be unique enough for your DB rules." + }, + { + "key": "claimIdCsd", + "value": "CSD-POSTMAN-0001", + "type": "default", + "enabled": true, + "description": "claim_id and booking_id for CSD (PO) runs: CSD-{suffix}." + }, + { + "key": "claimIdRetail", + "value": "CSD-RETAIL-0001", + "type": "default", + "enabled": true, + "description": "Optional booking/claim id for RETAIL_INVOICE tests (any string; booking_type often CSD in samples)." + }, + { + "key": "cpcDocumentId", + "value": "", + "type": "default", + "enabled": true, + "description": "UUID from GET .../documents/recent (or history). Required for GET by id, GET file, PUT status, DELETE. Optional test script on recent can set this." + }, + { + "key": "documentGcpUrl", + "value": "gs://your-bucket/path/document.pdf", + "type": "default", + "enabled": true, + "description": "For POST .../v1/ocr/validate JSON only. File must already exist in GCS." + }, + { + "key": "reportAttemptQuery", + "value": "", + "type": "default", + "enabled": true, + "description": "Per-claim Excel: append empty or ?attempt=2 (full query string including ?)." + }, + { + "key": "recentPage", + "value": "1", + "type": "default", + "enabled": true, + "description": "GET documents/recent — page (1-based)." + }, + { + "key": "recentLimit", + "value": "30", + "type": "default", + "enabled": true, + "description": "GET documents/recent — page size (max sensible for UI parity)." + }, + { + "key": "recentSearch", + "value": "", + "type": "default", + "enabled": true, + "description": "Optional: filter by booking/claim/type text and id (when API supports searchIncludeId)." + }, + { + "key": "recentStatus", + "value": "", + "type": "default", + "enabled": true, + "description": "Leave empty for no filter. Set SUCCESSFUL or UNSUCCESSFUL to match History page filters (backend maps to validation_status sets)." + }, + { + "key": "recentType", + "value": "", + "type": "default", + "enabled": true, + "description": "Leave empty for no filter. Else: AADHAAR | CPC_AUTH | CSD_PO | RETAIL_INVOICE | AUTHORITY_LETTER (see appendCpcDocumentFilters)." + }, + { + "key": "recentSortBy", + "value": "createdAt", + "type": "default", + "enabled": true, + "description": "Sort field: id | bookingId | createdAt | documentType | validationStatus | claimId | matchPercentage." + }, + { + "key": "recentOrder", + "value": "DESC", + "type": "default", + "enabled": true, + "description": "ASC or DESC." + }, + { + "key": "masterReportSearch", + "value": "", + "type": "default", + "enabled": true, + "description": "GET .../report/all/download optional search query param." + }, + { + "key": "masterReportStatus", + "value": "", + "type": "default", + "enabled": true, + "description": "Optional validation_status filter for master Excel." + }, + { + "key": "masterReportType", + "value": "", + "type": "default", + "enabled": true, + "description": "Optional document_type filter for master Excel." + }, + { + "key": "putStatusBodyJson", + "value": "{\n \"status\": \"APPROVED\",\n \"remarks\": \"Manual review via Postman\"\n}", + "type": "default", + "enabled": true, + "description": "Body for PUT .../documents/:id/status. Adjust status, remarks, optional correctedFields per API contract." + }, + { + "key": "metadataQueueJsonCsdPo", + "value": "[{\"document_type\":\"CSD_PO\",\"msd_payload\":{\"customer_name\":\"Rahul Verma\",\"po_number\":\"PO-2024-001\",\"po_amount\":\"25000\",\"signature_and_stamp\":\"yes\"},\"expected_field_keys\":[\"customer_name\",\"po_number\",\"po_amount\",\"signature_and_stamp\"]}]", + "type": "default", + "enabled": true, + "description": "CSD (1 doc) PO — Purchase Order. JSON keys: `customer_name`, `po_number`, `po_amount`, `signature_and_stamp` (yes/no). Legacy keys still work. Stringify for `metadata_queue`." + }, + { + "key": "metadataQueueJsonCpcTwoFiles", + "value": "[{\"document_type\":\"CPC_AUTH\",\"msd_payload\":{\"customer_name\":\"Amit Kumar\",\"letter_number\":\"AUTH-2024-77\",\"letter_amount\":\"45000\",\"signature_and_stamp\":\"yes\"},\"expected_field_keys\":[\"customer_name\",\"letter_number\",\"letter_amount\",\"signature_and_stamp\"]},{\"document_type\":\"AADHAAR\",\"msd_payload\":{\"customer_name\":\"Amit Kumar\",\"aadhar_number\":\"123412341234\"},\"expected_field_keys\":[\"customer_name\",\"aadhar_number\"]}]", + "type": "default", + "enabled": true, + "description": "CPC (2 docs), order = file order. Doc1: `customer_name`, `letter_number`, `letter_amount`, `signature_and_stamp`. Doc2: `customer_name`, `aadhar_number`. Legacy keys still work." + }, + { + "key": "metadataQueueJsonCpcAuthOnly", + "value": "[{\"document_type\":\"CPC_AUTH\",\"msd_payload\":{\"customer_name\":\"Amit Kumar\",\"letter_number\":\"AUTH-99\",\"letter_amount\":\"10000\",\"signature_and_stamp\":\"yes\"},\"expected_field_keys\":[\"customer_name\",\"letter_number\",\"letter_amount\",\"signature_and_stamp\"]}]", + "type": "default", + "enabled": true, + "description": "Single CPC_AUTH upload (skip_min). Same keys as CPC doc1 (`customer_name`, `letter_number`, `letter_amount`, `signature_and_stamp`)." + }, + { + "key": "metadataQueueJsonRetailInvoice", + "value": "[{\"document_type\":\"RETAIL_INVOICE\",\"msd_payload\":{\"vendor_name\":\"Royal Enfield Store\",\"order_or_authorisation_number\":\"INV-2024-1001\",\"invoice_value\":\"185000\",\"invoice_date\":\"15-01-2024\"},\"expected_field_keys\":[\"vendor_name\",\"order_or_authorisation_number\",\"invoice_value\",\"invoice_date\"]}]", + "type": "default", + "enabled": true, + "description": "Retail invoice: vendor, order, amount, and invoice date compared to the reference payload per validation policy." + }, + { + "key": "msdPayloadCpcAuth", + "value": "{\"customer_name\":\"Amit Kumar\",\"letter_number\":\"AUTH-1\",\"letter_amount\":\"45000\",\"signature_and_stamp\":\"yes\"}", + "type": "default", + "enabled": true, + "description": "validate-upload: Authorization letter — `customer_name`, `letter_number`, `letter_amount`, `signature_and_stamp`." + }, + { + "key": "msdPayloadAadhaar", + "value": "{\"customer_name\":\"Amit Kumar\",\"aadhar_number\":\"123412341234\"}", + "type": "default", + "enabled": true, + "description": "validate-upload: Aadhaar — `customer_name`, `aadhar_number` (12 digits)." + }, + { + "key": "msdPayloadCsdPo", + "value": "{\"customer_name\":\"Rahul Verma\",\"po_number\":\"PO-001\",\"po_amount\":\"12000\",\"signature_and_stamp\":\"yes\"}", + "type": "default", + "enabled": true, + "description": "validate-upload: CSD PO — same keys as `metadataQueueJsonCsdPo`." + }, + { + "key": "msdPayloadRetailInvoice", + "value": "{\"vendor_name\":\"RE Store\",\"order_or_authorisation_number\":\"INV-99\",\"invoice_value\":\"50000\",\"invoice_date\":\"01-04-2024\"}", + "type": "default", + "enabled": true, + "description": "validate-upload: msd_payload for RETAIL_INVOICE." + } + ], + "_postman_variable_scope": "environment", + "_postman_exported_at": "2026-04-15T12:00:00.000Z", + "_postman_exported_using": "RE Workflow CPC-CSD bundle" +} diff --git a/RE-CPC-CDC_POSTMAN_COLLECTION/RE_Workflow_CPC_CDC_OCR_SingleRequest.postman_collection.json b/RE-CPC-CDC_POSTMAN_COLLECTION/RE_Workflow_CPC_CDC_OCR_SingleRequest.postman_collection.json new file mode 100644 index 0000000..01488a8 --- /dev/null +++ b/RE-CPC-CDC_POSTMAN_COLLECTION/RE_Workflow_CPC_CDC_OCR_SingleRequest.postman_collection.json @@ -0,0 +1,90 @@ +{ + "info": { + "_postman_id": "re-workflow-cpc-csd-ocr-single-2026", + "name": "RE Workflow — CPC-CSD OCR (single POST)", + "description": "## What this collection is\nOne **multipart** request that runs the **full CPC-CSD OCR pipeline** used by the app: optional OCR text → Vertex/Gemini extraction → validation → **persist** `cpc_documents` rows.\n\nThis is **not** a different backend route — it is exactly:\n`POST {{apiRoot}}/cpc-csd/v1/ocr/upload`\n\n## Import\n1. Import **RE_Workflow_CPC_CDC_OCR_SingleRequest.postman_environment.json** (or merge variables into your existing env).\n2. Set **accessToken** (JWT, no `Bearer ` prefix).\n3. Select this environment in the dropdown.\n4. Open **POST Full OCR pipeline**, attach file(s), Send.\n\n## Auth\n- Collection **Bearer**: `{{accessToken}}`\n- User must be allowed for CPC-CSD (same as main RE Workflow collection).\n\n## Request (exact)\n| Item | Value |\n|------|--------|\n| Method | **POST** |\n| URL | `{{apiRoot}}/cpc-csd/v1/ocr/upload` |\n| Body mode | **form-data** (multipart) |\n| Content-Type | Let Postman set **multipart boundary** (do not set `application/json` on this request). |\n\n### Multipart text fields (always these keys)\n| Field name | Type | Required | Description |\n|------------|------|----------|-------------|\n| `claim_id` | text | yes | Claim id string; same family as Dashboard (`CPC-…` / `CSD-…`). |\n| `booking_id` | text | yes | In samples same as `claim_id`; backend accepts booking id pattern. |\n| `booking_type` | text | yes | **`CSD`** = one PO file. **`CPC`** = two files (auth + Aadhaar). |\n| `provider` | text | yes | e.g. `GEMINI_VERTEX_DIRECT` (see env `ocrProvider`). |\n| `metadata_queue` | text | yes | **Stringified JSON array** (not a Postman JSON body). Each element describes one uploaded file in order. |\n\n### Multipart file field(s)\n| Field name | Type | Count | Rule |\n|------------|------|-------|------|\n| `files` | file | **1** for CSD | One PO PDF/image. |\n| `files` | file | **2** for CPC | **Duplicate** the key `files` in Postman (two rows, same key `files`): first row = authorization letter, second = Aadhaar. Order **must** match `metadata_queue` array order. |\n\n### `metadata_queue` JSON shape (per array element)\nEach object **must** include:\n- `document_type`: `CSD_PO` | `CPC_AUTH` | `AADHAAR` | `RETAIL_INVOICE` (this collection documents CSD + CPC).\n- `msd_payload`: object — MSD/reference values for that file.\n- `expected_field_keys`: string array — **same keys** as in `msd_payload` you want validated (order preserved).\n\n**CSD_PO** keys (current canonical): `customer_name`, `po_number`, `po_amount`, `signature_and_stamp` (`yes`/`no`).\n\n**CPC_AUTH** (doc 1): `customer_name`, `letter_number`, `letter_amount`, `signature_and_stamp`.\n\n**AADHAAR** (doc 2): `customer_name`, `aadhar_number` (12 digits).\n\nUse env **`metadata_queue_json`** for CSD default, **`metadata_queue_json_cpc`** for CPC (set the `metadata_queue` field value to that variable when testing CPC).\n\n## Limits (server)\n- Max **20** `files` parts; **15 MB** per file; ZIP not allowed (same as main API).\n\n## Response\n- **200** JSON: per-file results with `document_id`, `validation_status`, `field_results`, etc. (same contract as main collection folder `03`/`04`).\n\n## Optional (not in this one-request collection)\n- `POST .../ocr/validate-upload` — single file validate without persisting as the same dashboard flow.\n- `POST .../ocr/validate` — JSON body + GCS URL.\n- `POST {{hostUrl}}/api/upload` — bare GCS staging without CPC metadata.", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + }, + "variable": [ + { + "key": "hostUrl", + "value": "http://localhost:5000" + }, + { + "key": "apiRoot", + "value": "http://localhost:5000/api/v1" + }, + { + "key": "accessToken", + "value": "" + }, + { + "key": "ocrProvider", + "value": "GEMINI_VERTEX_DIRECT" + } + ], + "item": [ + { + "name": "POST Full OCR pipeline (multipart upload)", + "description": "**Single API** for end-to-end OCR on CPC-CSD: `POST {{apiRoot}}/cpc-csd/v1/ocr/upload`.\n\n**CSD (1 file):** `ocr_booking_type=CSD`, attach **one** `files` part, `metadata_queue` = `{{metadata_queue_json}}` (default CSD_PO).\n\n**CPC (2 files):** Set `ocr_booking_type` to `CPC`, set `metadata_queue` to `{{metadata_queue_json_cpc}}`, **add a second form row** with key `files` (duplicate key), attach auth PDF then Aadhaar PDF in that order.\n\n**claim_id / booking_id:** both use `{{ocr_claim_id}}` — change env when switching CSD vs CPC claim ids.", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "claim_id", + "value": "{{ocr_claim_id}}", + "type": "text", + "description": "Same as Dashboard claim id string." + }, + { + "key": "booking_id", + "value": "{{ocr_claim_id}}", + "type": "text", + "description": "Samples use same value as claim_id; must match your booking/claim convention." + }, + { + "key": "booking_type", + "value": "{{ocr_booking_type}}", + "type": "text", + "description": "CSD = 1 file. CPC = 2 files + CPC metadata array." + }, + { + "key": "provider", + "value": "{{ocrProvider}}", + "type": "text", + "description": "Vertex/Rules mode; see env ocrProvider." + }, + { + "key": "metadata_queue", + "value": "{{metadata_queue_json}}", + "type": "text", + "description": "Stringified JSON array. CSD default from env `metadata_queue_json`. For CPC switch value to {{metadata_queue_json_cpc}} in this field (or paste)." + }, + { + "key": "files", + "type": "file", + "src": [], + "description": "CSD: attach PO here only. CPC: first file = authorization letter; add another `files` row below for Aadhaar." + } + ] + }, + "url": "{{apiRoot}}/cpc-csd/v1/ocr/upload", + "description": "Multipart form-data only. Do not set Content-Type manually." + }, + "response": [] + } + ] +} diff --git a/RE-CPC-CDC_POSTMAN_COLLECTION/RE_Workflow_CPC_CDC_OCR_SingleRequest.postman_environment.json b/RE-CPC-CDC_POSTMAN_COLLECTION/RE_Workflow_CPC_CDC_OCR_SingleRequest.postman_environment.json new file mode 100644 index 0000000..de171a9 --- /dev/null +++ b/RE-CPC-CDC_POSTMAN_COLLECTION/RE_Workflow_CPC_CDC_OCR_SingleRequest.postman_environment.json @@ -0,0 +1,65 @@ +{ + "id": "re-workflow-cpc-csd-ocr-single-env", + "name": "RE Workflow — CPC-CSD OCR (single POST)", + "values": [ + { + "key": "hostUrl", + "value": "http://localhost:5000", + "type": "default", + "enabled": true, + "description": "API origin (scheme + host + port). Docker: often http://localhost:5004. No trailing slash." + }, + { + "key": "apiRoot", + "value": "http://localhost:5000/api/v1", + "type": "default", + "enabled": true, + "description": "Must be {{hostUrl}}/api/v1. Used only by the OCR collection URL." + }, + { + "key": "accessToken", + "value": "", + "type": "secret", + "enabled": true, + "description": "JWT only (no 'Bearer ' prefix). Required: collection uses Bearer auth with this variable." + }, + { + "key": "ocrProvider", + "value": "GEMINI_VERTEX_DIRECT", + "type": "default", + "enabled": true, + "description": "Multipart text field `provider`. GEMINI_VERTEX_DIRECT = Gemini on file bytes. GEMINI_VERTEX = optional Document AI then Gemini. RULES = regex/rules on OCR text only (no Vertex)." + }, + { + "key": "ocr_claim_id", + "value": "CSD-OCR-0001", + "type": "default", + "enabled": true, + "description": "Used for BOTH `claim_id` and `booking_id` form fields (same as Dashboard). For CPC use e.g. CPC-OCR-0001 and set ocr_booking_type=CPC." + }, + { + "key": "ocr_booking_type", + "value": "CSD", + "type": "default", + "enabled": true, + "description": "Multipart `booking_type`: CSD (1 file, PO) or CPC (2 files: auth letter + Aadhaar)." + }, + { + "key": "metadata_queue_json", + "value": "[{\"document_type\":\"CSD_PO\",\"msd_payload\":{\"customer_name\":\"Rahul Verma\",\"po_number\":\"PO-2024-001\",\"po_amount\":\"25000\",\"signature_and_stamp\":\"yes\"},\"expected_field_keys\":[\"customer_name\",\"po_number\",\"po_amount\",\"signature_and_stamp\"]}]", + "type": "default", + "enabled": true, + "description": "Default for CSD. Single-line JSON STRING for form field `metadata_queue`. For CPC: set Body `metadata_queue` to {{metadata_queue_json_cpc}} (or paste that value) and add a second `files` row." + }, + { + "key": "metadata_queue_json_cpc", + "value": "[{\"document_type\":\"CPC_AUTH\",\"msd_payload\":{\"customer_name\":\"Amit Kumar\",\"letter_number\":\"AUTH-2024-77\",\"letter_amount\":\"45000\",\"signature_and_stamp\":\"yes\"},\"expected_field_keys\":[\"customer_name\",\"letter_number\",\"letter_amount\",\"signature_and_stamp\"]},{\"document_type\":\"AADHAAR\",\"msd_payload\":{\"customer_name\":\"Amit Kumar\",\"aadhar_number\":\"123412341234\"},\"expected_field_keys\":[\"customer_name\",\"aadhar_number\"]}]", + "type": "default", + "enabled": true, + "description": "CPC 2-file metadata_queue. Array order MUST match file order: [0]=first `files` part (auth letter), [1]=second `files` part (Aadhaar)." + } + ], + "_postman_variable_scope": "environment", + "_postman_exported_at": "2026-04-15T15:00:00.000Z", + "_postman_exported_using": "RE Workflow CPC-CSD OCR single-request bundle" +} diff --git a/Royal_Enfield_API_Collection.postman_collection.json b/Royal_Enfield_API_Collection.postman_collection.json index e2eb6ea..c2d9143 100644 --- a/Royal_Enfield_API_Collection.postman_collection.json +++ b/Royal_Enfield_API_Collection.postman_collection.json @@ -19,7 +19,12 @@ "variable": [ { "key": "baseUrl", - "value": "http://localhost:3000/api/v1", + "value": "http://localhost:5000/api/v1", + "type": "string" + }, + { + "key": "healthUrl", + "value": "http://localhost:5000/health", "type": "string" }, { @@ -101,7 +106,31 @@ ] }, { - "name": "Token Exchange (Development)", + "name": "Token Exchange (Okta authorization code)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "if (pm.response.code === 200) {", + " const jsonData = pm.response.json();", + " const data = jsonData.data || jsonData;", + " const token = data && (data.accessToken || data.access_token);", + " const refresh = data && (data.refreshToken || data.refresh_token);", + " if (token) {", + " pm.collectionVariables.set('accessToken', token);", + " pm.environment.set('accessToken', token);", + " }", + " if (refresh) {", + " pm.collectionVariables.set('refreshToken', refresh);", + " pm.environment.set('refreshToken', refresh);", + " }", + "}" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ diff --git a/docs/CPC-CDC.md b/docs/CPC-CDC.md new file mode 100644 index 0000000..322215d --- /dev/null +++ b/docs/CPC-CDC.md @@ -0,0 +1,68 @@ +# CPC-CSD module (re-workflow) + +This module (formerly referred to as CPC-CDC in code comments) covers **CPC/CSD document upload, OCR/extraction, validation against MSD payloads, audit history, dashboards, and Excel reports**. It was consolidated from the standalone **CPC-CSD** app into this backend. + +## HTTP API + +**CPC-CSD-compatible URLs** (same as `CPC-CSD/server/src/routes/index.js` + Postman `CPC-CSD-Full-Flow`): `POST /api/upload`, `GET /api/documents/*`, `POST /api/v1/ocr/validate`, `POST /api/v1/ocr/validate-upload` (field **`file`**), `POST /api/v1/ocr/upload` (field **`files`**, max 20), report downloads under `/api/v1/ocr/report/...`. Registered from `src/routes/cpc-csd-compat.mount.ts` before `/api/v1`; disable with **`CPC_LEGACY_COMPAT_ROUTES=false`**. + +**Namespaced API** — canonical prefix **`/api/v1/cpc-csd`**; legacy alias **`/api/v1/cpc-cdc`** (`src/routes/cpc-cdc.routes.ts`) mounts the same handlers and auth. + +| Method | Path (prefix **`/api`** or **`/api/v1/cpc-csd`** or legacy **`/api/v1/cpc-cdc`**) | Purpose | +|--------|------|---------| +| POST | `/upload` | GCS-only: multipart field **`file`** → `{ gcsUrl }` (compat: **`/api/upload`**) | +| POST | `/v1/ocr/validate` | JSON URL mode — returns **400** with legacy message (use validate-upload) | +| POST | `/v1/ocr/validate-upload` | Single file field **`file`** + `claim_id` / `msd_payload` / … | +| POST | `/v1/ocr/upload` | Bulk: field **`files`** (max 20) + `metadata_queue` or `msd_payload` / `document_type` | +| GET | `/documents/analytics` | Totals, pass rate, distribution, `dailyVolume`, `topMismatchFields` | +| GET | `/documents/history` | `claimId` query — attempts grouped | +| GET | `/documents/recent` | Paginated list; query: `page`, `limit`, `search`, `status`, `type`, `sortBy`, `order` | +| GET | `/documents/:id/file` | Authenticated file bytes for preview (browser cannot use `gs://` directly) | +| GET | `/documents/:id` | Document + audit logs + `field_results` | +| PUT | `/documents/:id/status` | Manual status / corrected fields | +| DELETE | `/documents/:id` | Remove document row | +| GET | `/v1/ocr/report/:claimId/download` | Per-claim Excel | +| GET | `/v1/ocr/report/all/download` | Master Excel (supports `search`, `status`, `type`) | + +Compat paths are under **`/api/...`**; namespaced routes are **`/api/v1/cpc-csd/...`** with **`/api/v1/cpc-cdc/...`** as an alias (same path suffixes as in the table’s second column). + +## Database + +Sequelize models: **`CpcDocument`** (`cpc_documents`), **`CpcAuditLog`** (`cpc_audit_logs`). Migration: `src/migrations/2026041300-create-cpc-cdc-tables.ts`. + +**Admin viewer list** is stored under `admin_configurations.config_key = CPC_CSD_ADMIN_CONFIG` (migration `20260416120000-rename-cpc-cdc-admin-config-key.ts` renames the legacy `CPC_CDC_ADMIN_CONFIG` row when applied). + +On **application startup**, `ensureCpcCdcSchema()` runs after DB connect (`src/services/cpc-cdc/ensureCpcCdcSchema.ts`) so `CREATE TABLE IF NOT EXISTS` applies if migrations were skipped; still run `npm run migrate` for a full schema history. + +Notable columns on `cpc_documents`: `booking_id`, `claim_id`, `attempt_no`, `document_type`, `document_gcp_url`, `provider`, JSONB `msd_payload`, `extracted_fields`, `field_confidence`, `validation_status`, `match_percentage`, `mismatch_reasons`, `field_results`, `ip_address`. + +Unique index: `(claim_id, attempt_no, document_type)` — important when migrating legacy data with duplicates. + +## Environment variables + +Copy **`re-workflow-be/.env.example`** to `.env` and adjust. Typical keys (see `CpcCdcController` and `src/services/cpc-cdc/*`): + +- **`GCP_PROJECT_ID`** — GCP project for Vertex / optional Document AI. +- **`VERTEX_AI_LOCATION`** — Vertex region (e.g. `asia-south1`). +- **`DOC_AI_PROCESSOR_ID`** — Optional; when set and valid, Document AI OCR may run before Gemini. +- **`GCP_LOCATION_DOC_AI`** — Document AI region (default `us`). +- **GCS** — Bucket/credentials as required by `CpcGcsService` (service account via `GOOGLE_APPLICATION_CREDENTIALS` or workload identity). +- **`CPC_ALLOW_DEGRADED_SAVE_WITHOUT_AI`** — **`true`**: always allow saving after failed/missing Vertex. **`false`**: in **production** only, disallow degraded saves. **Omitted in non-production**: degraded saves are **allowed** so local CPC works without GCP; set to **`false`** in dev to force strict Vertex. **Omitted in production**: strict (Vertex required unless `RULES` provider). + +**Extraction behaviour (upload response):** + +- **`extraction_source`: `vertex_gemini`** — Fields came from the Vertex Gemini API (document bytes + optional Document AI OCR text). +- **`extraction_source`: `rules_engine`** — Provider was **`RULES`**; fields come from `CpcRuleExtractService` on OCR text only (no Gemini). +- **`extraction_source`: `degraded_empty`** — Extraction was skipped, failed, or (in **non-production**) hit a **Vertex auth / ADC** problem; the row is still stored with empty `extracted_fields` so you can test DB/history. In production this only happens when **`CPC_ALLOW_DEGRADED_SAVE_WITHOUT_AI=true`** or missing `GCP_PROJECT_ID` with degraded policy. + +## One-off data migration from legacy Prisma DB + +If you still have the old **`Document`** / **`AuditLog`** tables (CPC-CSD Prisma schema) in PostgreSQL, run: + +```bash +npm run migrate:cpc-csd +``` + +Optional **`CPC_CSD_DATABASE_URL`**: if set, rows are read from that database and written to the database in **`DATABASE_URL`** (re-workflow). If unset, both read and write use **`DATABASE_URL`** (same cluster; both table sets must exist). + +After migration, spot-check history, document detail, and Excel downloads, then decommission the legacy app. diff --git a/package-lock.json b/package-lock.json index 2d6bf21..2384758 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "re-workflow-backend", "version": "1.0.0", "dependencies": { + "@google-cloud/documentai": "^9.6.0", "@google-cloud/secret-manager": "^6.1.1", "@google-cloud/storage": "^7.18.0", "@google-cloud/vertexai": "^1.10.0", @@ -22,6 +23,7 @@ "cors": "^2.8.5", "dayjs": "^1.11.19", "dotenv": "^16.4.7", + "exceljs": "^4.4.0", "express": "^4.21.2", "express-rate-limit": "^7.5.0", "fast-xml-parser": "^5.3.3", @@ -43,6 +45,7 @@ "sanitize-html": "^2.17.1", "sequelize": "^6.37.5", "socket.io": "^4.8.1", + "string-similarity": "^4.0.4", "uuid": "^8.3.2", "web-push": "^3.6.7", "winston": "^3.17.0", @@ -64,10 +67,12 @@ "@types/passport-jwt": "^4.0.1", "@types/pg": "^8.15.6", "@types/sanitize-html": "^2.16.0", + "@types/string-similarity": "^4.0.2", "@types/supertest": "^6.0.2", "@types/web-push": "^3.6.4", "@typescript-eslint/eslint-plugin": "^8.19.1", "@typescript-eslint/parser": "^8.19.1", + "concurrently": "^9.1.2", "eslint": "^9.17.0", "jest": "^29.7.0", "nodemon": "^3.1.9", @@ -1589,6 +1594,59 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@fast-csv/format": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz", + "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==", + "license": "MIT", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isboolean": "^3.0.3", + "lodash.isequal": "^4.5.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0" + } + }, + "node_modules/@fast-csv/format/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" + }, + "node_modules/@fast-csv/parse": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz", + "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==", + "license": "MIT", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.groupby": "^4.6.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0", + "lodash.isundefined": "^3.0.1", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/@fast-csv/parse/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" + }, + "node_modules/@google-cloud/documentai": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@google-cloud/documentai/-/documentai-9.6.0.tgz", + "integrity": "sha512-isNYeE+1KoVaA5JKQsFmj6s7/bTAZYeDmkx+t9qjQrNkk+So7b/OJsUtMQiZLATTlwyU5gCNDir5cMasbtzPPQ==", + "license": "Apache-2.0", + "dependencies": { + "google-gax": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@google-cloud/paginator": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", @@ -4188,6 +4246,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/string-similarity": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/string-similarity/-/string-similarity-4.0.2.tgz", + "integrity": "sha512-LkJQ/jsXtCVMK+sKYAmX/8zEq+/46f1PTQw7YtmQwb74jemS1SlNLmARM2Zml9DgdDTWKAtc5L13WorpHPDjDA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", @@ -4679,6 +4744,91 @@ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", "license": "MIT" }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "license": "MIT", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/archiver/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -5108,6 +5258,15 @@ "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", "license": "MIT" }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } + }, "node_modules/bignumber.js": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", @@ -5117,6 +5276,19 @@ "node": "*" } }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "license": "MIT", + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -5136,6 +5308,17 @@ "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==", "license": "MIT" }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -5285,6 +5468,30 @@ "node": ">= 0.4.0" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -5306,6 +5513,23 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, + "node_modules/buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "engines": { + "node": ">=0.2.0" + } + }, "node_modules/bullmq": { "version": "5.63.0", "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.63.0.tgz", @@ -5423,6 +5647,18 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "license": "MIT/X11", + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5679,11 +5915,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, "license": "MIT" }, "node_modules/concat-stream": { @@ -5731,6 +5981,47 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/config-chain": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", @@ -5859,6 +6150,31 @@ } } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -6191,6 +6507,45 @@ "node": ">= 0.4" } }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/duplexify": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", @@ -6770,6 +7125,26 @@ "bare-events": "^2.7.0" } }, + "node_modules/exceljs": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.4.0.tgz", + "integrity": "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==", + "license": "MIT", + "dependencies": { + "archiver": "^5.0.0", + "dayjs": "^1.8.34", + "fast-csv": "^4.3.1", + "jszip": "^3.10.1", + "readable-stream": "^3.6.0", + "saxes": "^5.0.1", + "tmp": "^0.2.0", + "unzipper": "^0.10.11", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=8.3.0" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -6937,6 +7312,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/fast-csv": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz", + "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==", + "license": "MIT", + "dependencies": { + "@fast-csv/format": "4.3.5", + "@fast-csv/parse": "4.3.6" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -7292,6 +7680,12 @@ "node": ">= 0.6" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, "node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -7312,7 +7706,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, "license": "ISC" }, "node_modules/fsevents": { @@ -7330,6 +7723,22 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -7502,7 +7911,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -7536,7 +7944,6 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -7547,7 +7954,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -7839,7 +8245,6 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, "license": "ISC" }, "node_modules/graphemer": { @@ -8070,6 +8475,26 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", @@ -8087,6 +8512,12 @@ "dev": true, "license": "ISC" }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -8147,7 +8578,6 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -9187,6 +9617,48 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/jwa": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", @@ -9234,6 +9706,48 @@ "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", "license": "MIT" }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -9258,12 +9772,27 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", + "license": "ISC" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -9298,6 +9827,30 @@ "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", "license": "MIT" }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "license": "MIT" + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "license": "MIT" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "license": "MIT" + }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==", + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -9316,12 +9869,31 @@ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", "license": "MIT" }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, + "node_modules/lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", + "license": "MIT" + }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", "license": "MIT" }, + "node_modules/lodash.isnil": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", + "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==", + "license": "MIT" + }, "node_modules/lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", @@ -9340,6 +9912,12 @@ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", "license": "MIT" }, + "node_modules/lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==", + "license": "MIT" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -9360,6 +9938,18 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "license": "MIT" }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" + }, "node_modules/logform": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", @@ -9978,7 +10568,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -10213,6 +10802,12 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -10308,7 +10903,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -11063,6 +11657,27 @@ "node": ">= 6" } }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -11224,7 +11839,6 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, "license": "ISC", "dependencies": { "glob": "^7.1.3" @@ -11257,6 +11871,16 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -11306,6 +11930,18 @@ "postcss": "^8.3.11" } }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/semver": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", @@ -11528,6 +12164,12 @@ "node": ">= 0.8.0" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -11555,6 +12197,19 @@ "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -11999,6 +12654,13 @@ "node": ">=10" } }, + "node_modules/string-similarity": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz", + "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "ISC" + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -12320,6 +12982,15 @@ "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", "license": "MIT" }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -12371,6 +13042,15 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "license": "MIT/X11", + "engines": { + "node": "*" + } + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -12797,6 +13477,60 @@ "node": ">= 0.8" } }, + "node_modules/unzipper": { + "version": "0.10.14", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", + "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "license": "MIT", + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, + "node_modules/unzipper/node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "license": "MIT" + }, + "node_modules/unzipper/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/unzipper/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/unzipper/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", @@ -13137,6 +13871,12 @@ } } }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, "node_modules/xss": { "version": "1.0.15", "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.15.tgz", @@ -13243,6 +13983,41 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "license": "MIT", + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/zod": { "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", diff --git a/package.json b/package.json index 9a301a5..b39d25f 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "clean": "rm -rf dist", "setup": "ts-node -r tsconfig-paths/register src/scripts/auto-setup.ts", "migrate": "ts-node -r tsconfig-paths/register src/scripts/migrate.ts", + "migrate:cpc-csd": "ts-node -r tsconfig-paths/register src/scripts/migrate-cpc-csd-to-cpc-tables.ts", "seed:config": "ts-node -r tsconfig-paths/register src/scripts/seed-admin-config.ts", "seed:test-dealer": "ts-node -r tsconfig-paths/register src/scripts/seed-test-dealer.ts", "seed:dealer-user": "ts-node -r tsconfig-paths/register src/scripts/seed-dealer-user.ts", @@ -31,6 +32,7 @@ "test:ci": "jest --ci --coverage --passWithNoTests --forceExit" }, "dependencies": { + "@google-cloud/documentai": "^9.6.0", "@google-cloud/secret-manager": "^6.1.1", "@google-cloud/storage": "^7.18.0", "@google-cloud/vertexai": "^1.10.0", @@ -45,6 +47,7 @@ "cors": "^2.8.5", "dayjs": "^1.11.19", "dotenv": "^16.4.7", + "exceljs": "^4.4.0", "express": "^4.21.2", "express-rate-limit": "^7.5.0", "fast-xml-parser": "^5.3.3", @@ -66,6 +69,7 @@ "sanitize-html": "^2.17.1", "sequelize": "^6.37.5", "socket.io": "^4.8.1", + "string-similarity": "^4.0.4", "uuid": "^8.3.2", "web-push": "^3.6.7", "winston": "^3.17.0", @@ -87,10 +91,12 @@ "@types/passport-jwt": "^4.0.1", "@types/pg": "^8.15.6", "@types/sanitize-html": "^2.16.0", + "@types/string-similarity": "^4.0.2", "@types/supertest": "^6.0.2", "@types/web-push": "^3.6.4", "@typescript-eslint/eslint-plugin": "^8.19.1", "@typescript-eslint/parser": "^8.19.1", + "concurrently": "^9.1.2", "eslint": "^9.17.0", "jest": "^29.7.0", "nodemon": "^3.1.9", diff --git a/set-admin.ts b/set-admin.ts new file mode 100644 index 0000000..21bc17f --- /dev/null +++ b/set-admin.ts @@ -0,0 +1,43 @@ +import { sequelize } from './src/config/database'; +import { User } from './src/models/User'; + +async function makeAdmin() { + try { + const email = 'testuser11@eichergroup.com'; + console.log(`Setting role to ADMIN for: ${email}`); + + // Test connection first + await sequelize.authenticate(); + console.log('Database connected.'); + + const [updatedRows] = await User.update( + { role: 'ADMIN' }, + { where: { email: email } } + ); + + if (updatedRows > 0) { + console.log(`✅ Success! ${email} is now an ADMIN.`); + } else { + console.log(`⚠️ User not found in database: ${email}`); + console.log(`Creating user ${email} with ADMIN role...`); + + const newUser = await User.create({ + email: email, + oktaSub: `MANUAL_ADMIN_${Date.now()}`, + firstName: 'Test', + lastName: 'User 11', + displayName: 'Test User 11', + role: 'ADMIN', + isActive: true + }); + + console.log(`✅ Success! Created new ADMIN user: ${newUser.email}`); + } + } catch (error) { + console.error('❌ Error updating user:', error); + } finally { + await sequelize.close(); + } +} + +makeAdmin(); diff --git a/src/app.ts b/src/app.ts index 8dbe65c..ab36021 100644 --- a/src/app.ts +++ b/src/app.ts @@ -6,11 +6,13 @@ import cookieParser from 'cookie-parser'; import { UserService } from './services/user.service'; import { SSOUserData } from './types/auth.types'; import { sequelize } from './config/database'; +import { ensureCpcCdcSchema } from './services/cpc-cdc/ensureCpcCdcSchema'; import { corsMiddleware } from './middlewares/cors.middleware'; import { authenticateToken } from './middlewares/auth.middleware'; import { requireAdmin } from './middlewares/authorization.middleware'; import { metricsMiddleware, createMetricsRouter } from './middlewares/metrics.middleware'; import routes from './routes/index'; +import { registerCpcCsdCompatRoutes } from './routes/cpc-csd-compat.mount'; import form16Routes from './routes/form16.routes'; import { ensureUploadDir, UPLOAD_DIR } from './config/storage'; import { initializeGoogleSecretManager } from './services/googleSecretManager.service'; @@ -28,15 +30,25 @@ const app: express.Application = express(); // 1. Security middleware - Manual "Gold Standard" CSP to ensure it survives 301/404/etc. // This handles a specific Express/Helmet edge case where redirects lose headers. app.use((req: express.Request, res: express.Response, next: express.NextFunction) => { - const isDev = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'local'; + // Match server.ts: anything except production is "dev" for local tooling (.env often uses NODE_ENV=dev) + const nodeEnv = (process.env.NODE_ENV || '').toLowerCase(); + const isDev = nodeEnv !== 'production' && nodeEnv !== 'prod'; const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:3000'; // Build connect-src dynamically const connectSrc = ["'self'", "blob:", "data:"]; if (isDev) { - connectSrc.push("http://localhost:3000", "http://localhost:5000", "ws://localhost:3000", "ws://localhost:5000"); - if (frontendUrl.includes('localhost')) connectSrc.push(frontendUrl); + for (let port = 3000; port <= 3010; port++) { + connectSrc.push(`http://localhost:${port}`, `http://127.0.0.1:${port}`); + connectSrc.push(`ws://localhost:${port}`, `ws://127.0.0.1:${port}`); + } + for (let port = 5000; port <= 5005; port++) { + connectSrc.push(`http://localhost:${port}`, `http://127.0.0.1:${port}`); + connectSrc.push(`ws://localhost:${port}`, `ws://127.0.0.1:${port}`); + } + if (frontendUrl.includes('localhost') || frontendUrl.includes('127.0.0.1')) connectSrc.push(frontendUrl); } else if (frontendUrl && frontendUrl !== '*') { + const origins = frontendUrl.split(',').map(url => url.trim()).filter(Boolean); connectSrc.push(...origins); } @@ -97,6 +109,7 @@ export const initializeAppDatabase = async () => { try { await sequelize.authenticate(); console.log('✅ App database connection established'); + await ensureCpcCdcSchema(); } catch (error) { console.error('❌ App database connection failed:', error); throw error; @@ -147,6 +160,9 @@ app.get('/health', (_req: express.Request, res: express.Response) => { }); }); +// CPC-CSD-compatible paths (`/api/upload`, `/api/documents/*`, `/api/v1/ocr/*`) — same as `CPC-CSD/server` router +registerCpcCsdCompatRoutes(app); + // Mount API routes (form16 already mounted above before body parser) app.use('/api/v1', routes); diff --git a/src/controllers/CpcCdcController.ts b/src/controllers/CpcCdcController.ts new file mode 100644 index 0000000..1f2a307 --- /dev/null +++ b/src/controllers/CpcCdcController.ts @@ -0,0 +1,1147 @@ +import { Request, Response } from 'express'; +import fs from 'fs'; +import path from 'path'; +import { randomUUID } from 'crypto'; +import { CpcDocument } from '@models/CpcDocument'; +import { CpcAuditLog } from '@models/CpcAuditLog'; +import { cpcOcrService } from '@services/cpc-cdc/CpcOcrService'; +import { CpcValidationService } from '@services/cpc-cdc/CpcValidationService'; +import { CpcHistoryService } from '@services/cpc-cdc/CpcHistoryService'; +import { CpcRuleExtractService } from '@services/cpc-cdc/CpcRuleExtractService'; +import { cpcGcsService } from '@services/cpc-cdc/CpcGcsService'; +import { extractPdfTextFromBuffer } from '@services/cpc-cdc/extractPdfText'; +import { appendCpcDocumentFilters, cpcWhereFromAndParts } from '@services/cpc-cdc/utils'; +import { gcsStorageService } from '@services/gcsStorage.service'; + +import logger from '@utils/logger'; +import { Op } from 'sequelize'; +import { sequelize } from '@config/database'; +import { UPLOAD_DIR } from '@config/storage'; + +/** Vertex / ADC not configured locally — do not fail the whole upload in non-production. */ +function isLikelyVertexAuthOrCredentialFailure(err: unknown): boolean { + const e = err as { message?: string; name?: string; code?: string }; + const blob = `${e?.name || ''} ${e?.message || ''} ${e?.code || ''}`.toLowerCase(); + return ( + blob.includes('googleauth') || + blob.includes('unable to authenticate') || + blob.includes('could not load the default credentials') || + blob.includes('application default credentials') || + blob.includes('invalid_grant') || + (blob.includes('enotfound') && blob.includes('metadata.google.internal')) + ); +} + +const CPC_LOCAL_UPLOAD_DIR = path.join(process.cwd(), 'uploads', 'cpc-csd-files'); +const CPC_LOCAL_URL_PREFIX = 'cpc-local:'; +/** Max validation attempts per claim when `CPC_ENFORCE_MAX_ATTEMPTS` is not `false` (MSD may own attempts — then set env false). */ +const CPC_MAX_ATTEMPTS = Math.max(1, parseInt(process.env.CPC_MAX_ATTEMPTS || '2', 10) || 2); + +function isCpcAttemptLimitEnforced(): boolean { + return String(process.env.CPC_ENFORCE_MAX_ATTEMPTS ?? 'true').toLowerCase() !== 'false'; +} + +function minCpcCdcAttachmentsForClaim(bookingIdRaw: unknown, bookingTypeRaw: unknown): number { + const bt = String(bookingTypeRaw || '').toUpperCase(); + if (bt === 'CSD') return 1; + if (bt === 'CPC') return 2; + const bid = String(bookingIdRaw || '').toUpperCase(); + if (bid.startsWith('CSD-') || bid.startsWith('CSD_')) return 1; + return 2; +} + +function extForUploadedFile(mimetype: string, originalName: string): string { + const fromName = path.extname(originalName || '').toLowerCase(); + if (fromName && fromName.length <= 12) return fromName; + if (mimetype === 'application/pdf') return '.pdf'; + if (mimetype?.includes('jpeg') || mimetype === 'image/jpg') return '.jpg'; + if (mimetype?.includes('png')) return '.png'; + return '.bin'; +} + +/** Safe single path segment for disk/GCS (booking id, doc type token). */ +function sanitizePathSegment(segment: string, maxLen = 120): string { + const s = String(segment || '').trim(); + if (!s) return 'unknown-booking'; + const cleaned = s.replace(/[^a-zA-Z0-9._-]+/g, '_').replace(/_+/g, '_'); + return cleaned.slice(0, maxLen); +} + +/** Folder key under `cpc-csd-files/{csd|cpc}/` (same relative path in GCS bucket and local `uploads/`). */ +function deriveCpcCsdStorageChannel(bookingIdRaw: unknown, bookingTypeRaw: unknown): 'csd' | 'cpc' { + const bt = String(bookingTypeRaw || '').toUpperCase(); + if (bt === 'CSD') return 'csd'; + if (bt === 'CPC') return 'cpc'; + const bid = String(bookingIdRaw || '').toUpperCase(); + if (bid.startsWith('CSD-') || bid.startsWith('CSD_')) return 'csd'; + return 'cpc'; +} + +/** + * Same shape as workflow uploads: time + short id + role fields + safe original stem + extension. + * Example: `1713350400123-a1b2c3d4e5f6-a1-PO-purchase_order.pdf` + */ +function buildCpcCsdStoredFileName(params: { + documentId: string; + attemptNo: number; + docType: string; + originalName: string; + mimetype: string; + fileIndex: number; +}): string { + const ext = extForUploadedFile(params.mimetype, params.originalName); + const ts = Date.now(); + const shortId = params.documentId.replace(/-/g, '').slice(0, 12); + const stem = path.basename(params.originalName || 'file', path.extname(params.originalName || '')); + const origStem = sanitizePathSegment(stem, 72).toLowerCase() || 'file'; + const typePart = sanitizePathSegment(params.docType, 36).toLowerCase() || 'doc'; + return `${ts}-${shortId}-a${params.attemptNo}-i${params.fileIndex + 1}-${typePart}-${origStem}${ext}`; +} + +function contentTypeFromPath(p: string): string { + const ext = path.extname(p).toLowerCase(); + if (ext === '.pdf') return 'application/pdf'; + if (ext === '.png') return 'image/png'; + if (ext === '.jpg' || ext === '.jpeg') return 'image/jpeg'; + if (ext === '.gif') return 'image/gif'; + return 'application/octet-stream'; +} + +function resolveCpcLocalDiskPath(documentGcpUrl: string): string | null { + if (!documentGcpUrl.startsWith(CPC_LOCAL_URL_PREFIX)) return null; + const rel = documentGcpUrl.slice(CPC_LOCAL_URL_PREFIX.length).replace(/^\/+/, '').replace(/\\/g, '/'); + if (!rel || rel.includes('..')) return null; + const segments = rel.split('/').filter(Boolean); + if (segments.some((s) => s === '..')) return null; + const full = path.resolve(CPC_LOCAL_UPLOAD_DIR, ...segments); + const base = path.resolve(CPC_LOCAL_UPLOAD_DIR); + const baseSep = base.endsWith(path.sep) ? base : `${base}${path.sep}`; + if (!full.startsWith(baseSep) && full !== base) return null; + return full; +} + +/** Workflow-style `/uploads/...` URLs stored when GCS is unavailable (UAT). */ +function resolveUploadsDirFromPublicUrl(storageRef: string): string | null { + if (!storageRef.startsWith('/uploads/')) return null; + const rel = storageRef.slice('/uploads/'.length).replace(/^\/+/, '').replace(/\\/g, '/'); + if (!rel || rel.includes('..')) return null; + const segments = rel.split('/').filter(Boolean); + if (segments.some((s) => s === '..')) return null; + const full = path.resolve(UPLOAD_DIR, ...segments); + const base = path.resolve(UPLOAD_DIR); + const baseSep = base.endsWith(path.sep) ? base : `${base}${path.sep}`; + if (!full.startsWith(baseSep) && full !== base) return null; + return full; +} + +export class CpcCdcController { + + /** + * Validate a single document upload (Legacy support or single file) + */ + async validateDocumentUpload(req: Request, res: Response) { + + try { + const requestId = String(req.headers['x-request-id'] || randomUUID()); + const clientId = String(req.headers['x-client-id'] || (req as any).user?.email || 'unknown'); + const files = (req.files as Express.Multer.File[]) || (req.file ? [req.file] : []); + if (files.length === 0) { + return res.status(400).json({ + error_code: 'NO_FILE_UPLOADED', + error_message: 'No files were provided in the request.', + retryable: false + }); + } + + const skipMinAttachmentCheck = String((req.body as any)?.skip_min_attachment_check || '').toLowerCase() === 'true'; + const isUploadFlow = (req.path || req.originalUrl || '').includes('/ocr/upload'); + const { document_type, msd_payload, provider, booking_id, booking_type, claim_id, metadata_queue } = req.body; + const minAttachments = minCpcCdcAttachmentsForClaim(booking_id || claim_id, booking_type); + if (isUploadFlow && !skipMinAttachmentCheck && files.length < minAttachments) { + const msg = + minAttachments === 1 + ? 'CSD claims require at least 1 attachment (Purchase Order PDF/image).' + : 'CPC claims require at least 2 attachments: Authorization Letter and Aadhaar (one file each, or more if you split pages).'; + return res.status(400).json({ + error_code: 'MIN_ATTACHMENTS_REQUIRED', + error_message: msg, + retryable: false + }); + } + + const targetClaimId = claim_id || booking_id; + + if (!targetClaimId) { + return res.status(400).json({ + error_code: 'MISSING_CLAIM_ID', + error_message: 'claim_id or booking_id is required to track validation attempts.', + retryable: false + }); + } + + const storageChannel = deriveCpcCsdStorageChannel(targetClaimId, booking_type); + const safeBookingSeg = sanitizePathSegment(targetClaimId); + + // Support both single payload and metadata queue + let queue: any[] = []; + if (metadata_queue) { + try { + queue = typeof metadata_queue === 'string' ? JSON.parse(metadata_queue) : metadata_queue; + } catch (e) { + return res.status(400).json({ + error_code: 'INVALID_QUEUE', + error_message: 'Invalid metadata_queue format.', + retryable: false + }); + } + } else { + let parsedPayload = {}; + try { + parsedPayload = typeof msd_payload === 'string' ? JSON.parse(msd_payload) : msd_payload; + } catch (e) { + return res.status(400).json({ + error_code: 'INVALID_PAYLOAD', + error_message: 'Invalid msd_payload.', + retryable: false + }); + } + queue = [{ + document_type: document_type || "GENERIC_INVOICE", + msd_payload: parsedPayload + }]; + } + + const results: any[] = []; + const ipAddress = req.ip || req.headers['x-forwarded-for'] || req.socket.remoteAddress; + // Production: real Vertex/Gemini only unless CPC_ALLOW_DEGRADED_SAVE_WITHOUT_AI=true. + // Non-production: allow degraded saves by default so local CPC works without GCP; set env to "false" to force strict. + const nodeEnv = (process.env.NODE_ENV || '').toLowerCase(); + const isProdRuntime = nodeEnv === 'production' || nodeEnv === 'prod'; + const degradedEnv = String(process.env.CPC_ALLOW_DEGRADED_SAVE_WITHOUT_AI || '').toLowerCase(); + const allowDegradedSave = + degradedEnv === 'true' || (!isProdRuntime && degradedEnv !== 'false'); + + const requestedAttemptNo = Number((req.body as any)?.attempt_no); + const hasRequestedAttemptNo = Number.isFinite(requestedAttemptNo) && requestedAttemptNo > 0; + + const attemptRows = await CpcDocument.findAll({ + attributes: ['attemptNo'], + where: { claimId: targetClaimId }, + group: ['attemptNo'], + raw: true + }) as Array<{ attemptNo?: number }>; + const usedAttempts = new Set( + attemptRows + .map((r) => Number(r?.attemptNo || 0)) + .filter((n) => Number.isFinite(n) && n > 0) + ); + + if (isCpcAttemptLimitEnforced()) { + if (hasRequestedAttemptNo && requestedAttemptNo > CPC_MAX_ATTEMPTS) { + return res.status(422).json({ + error_code: 'MAX_ATTEMPTS_REACHED', + error_message: `Only ${CPC_MAX_ATTEMPTS} validation attempts are allowed per claim.`, + retryable: false + }); + } + + if (!hasRequestedAttemptNo && usedAttempts.size >= CPC_MAX_ATTEMPTS) { + return res.status(422).json({ + error_code: 'MAX_ATTEMPTS_REACHED', + error_message: `Only ${CPC_MAX_ATTEMPTS} validation attempts are allowed per claim.`, + retryable: false + }); + } + + if (hasRequestedAttemptNo && !usedAttempts.has(requestedAttemptNo) && usedAttempts.size >= CPC_MAX_ATTEMPTS) { + return res.status(422).json({ + error_code: 'MAX_ATTEMPTS_REACHED', + error_message: `Only ${CPC_MAX_ATTEMPTS} validation attempts are allowed per claim.`, + retryable: false + }); + } + } + + const currentAttempt = hasRequestedAttemptNo ? requestedAttemptNo : (usedAttempts.size + 1); + + for (let i = 0; i < files.length; i++) { + const file = files[i]; + const meta = queue[i] || queue[0]; // Fallback to first meta if queue is shorter than files + + const currentDocType = (meta.document_type || "GENERIC_INVOICE").toUpperCase(); + const expectedPayload = meta.msd_payload || {}; + const rawUiKeys = Array.isArray((meta as { expected_field_keys?: unknown }).expected_field_keys) + ? (meta as { expected_field_keys: unknown[] }).expected_field_keys + : Object.keys(expectedPayload); + const expectedFieldKeysForPipeline = [ + ...new Set( + rawUiKeys.map((k: unknown) => String(k ?? '').trim()).filter(Boolean) + ) + ]; + + try { + // 1. OCR (Optional) + let ocrText = ""; + const isDocAiConfigured = process.env.DOC_AI_PROCESSOR_ID && process.env.DOC_AI_PROCESSOR_ID !== "your-processor-id"; + if (isDocAiConfigured && provider !== "GEMINI_VERTEX_DIRECT") { + try { + const ocrResult = await cpcOcrService.runDocAIOcr({ + projectId: process.env.GCP_PROJECT_ID!, + location: process.env.GCP_LOCATION_DOC_AI || 'us', + processorId: process.env.DOC_AI_PROCESSOR_ID!, + fileBuffer: file.buffer, + mimeType: file.mimetype + }); + ocrText = ocrResult.text; + } catch (e) { + logger.warn(`[CpcController] OCR failed for ${file.originalname}`, e); + } + } + if (!ocrText?.trim() && file.buffer?.length && file.mimetype === 'application/pdf') { + try { + const pdfText = await extractPdfTextFromBuffer(file.buffer); + if (pdfText?.trim()) { + ocrText = pdfText; + logger.info( + `[CpcController] PDF text fallback for ${file.originalname} (${pdfText.length} chars)` + ); + } + } catch (e) { + logger.warn(`[CpcController] pdf-parse failed for ${file.originalname}`, e); + } + } + // 2. Extraction: RULES = local parser on OCR text; otherwise Vertex Gemini (real API — no fake values). + let extracted: Record = {}; + let confidence: Record = {}; + let extractionSource: 'rules_engine' | 'vertex_gemini' | 'degraded_empty' = 'degraded_empty'; + + if (provider === "RULES") { + const ruleOut = CpcRuleExtractService.extractWithRules(ocrText, { + msdPayload: expectedPayload, + documentType: currentDocType + }); + extracted = ruleOut.extracted_fields; + confidence = ruleOut.field_confidence; + extractionSource = 'rules_engine'; + } else { + const projectId = (process.env.GCP_PROJECT_ID || '').trim(); + const hasVertexProject = Boolean(projectId && !/^your-?project/i.test(projectId)); + if (!hasVertexProject) { + if (!allowDegradedSave) { + results.push({ + filename: file.originalname, + error_code: 'CPC_VERTEX_NOT_CONFIGURED', + error_message: + 'Vertex AI extraction requires a valid GCP_PROJECT_ID. Configure Vertex/Gemini, use provider RULES for OCR+rules-only, or set CPC_ALLOW_DEGRADED_SAVE_WITHOUT_AI=true for dev-only saves without AI.' + , + retryable: false + }); + continue; + } + logger.warn( + `[CpcController] GCP_PROJECT_ID missing — degraded save allowed (CPC_ALLOW_DEGRADED_SAVE_WITHOUT_AI) for ${file.originalname}` + ); + extracted = {}; + confidence = {}; + extractionSource = 'degraded_empty'; + } else { + try { + const geminiOut = await CpcValidationService.extractWithGemini({ + projectId, + location: process.env.VERTEX_AI_LOCATION || 'asia-south1', + documentType: currentDocType, + ocrText, + fileBuffer: file.buffer, + mimeType: file.mimetype, + expectedFields: + expectedFieldKeysForPipeline.length > 0 + ? expectedFieldKeysForPipeline + : Object.keys(expectedPayload), + msdReferencePayload: expectedPayload + }); + extracted = geminiOut.extracted_fields || {}; + confidence = geminiOut.field_confidence || {}; + extractionSource = 'vertex_gemini'; + } catch (geminiErr: any) { + // Non-production: never block upload on missing local GCP login / ADC (GoogleAuthError). + const authOrCred = isLikelyVertexAuthOrCredentialFailure(geminiErr); + const canDegrade = + allowDegradedSave || (!isProdRuntime && authOrCred); + if (!canDegrade) { + results.push({ + filename: file.originalname, + error_code: 'CPC_EXTRACTION_FAILED', + error_message: + authOrCred && isProdRuntime + ? 'Vertex AI could not authenticate (check GOOGLE_APPLICATION_CREDENTIALS or workload identity).' + : geminiErr?.message || 'Gemini/Vertex extraction failed' + , + retryable: true + }); + continue; + } + logger.warn( + `[CpcController] Vertex/Gemini unavailable or failed — saving with empty extraction (${file.originalname})`, + geminiErr?.message || geminiErr + ); + extracted = {}; + confidence = {}; + extractionSource = 'degraded_empty'; + } + } + } + + // 2b. When not using RULES-only: fill gaps from rule engine on OCR/PDF text (Vertex may omit; Docker OCR often empty). + if (extractionSource !== 'rules_engine' && ocrText?.trim()) { + const ruleFill = CpcRuleExtractService.extractWithRules(ocrText, { + msdPayload: expectedPayload, + documentType: currentDocType + }); + const ruleExtracted = ruleFill.extracted_fields as Record; + const ruleConfidence = ruleFill.field_confidence as Record; + for (const key of Object.keys(ruleExtracted)) { + if ( + expectedFieldKeysForPipeline.length > 0 && + !expectedFieldKeysForPipeline.includes(key) + ) { + continue; + } + const rv = ruleExtracted[key]; + const ev = extracted[key]; + const emptyEv = + ev === undefined || + ev === null || + (typeof ev === 'string' && String(ev).trim() === '') || + String(ev).toLowerCase() === 'null'; + if (emptyEv && rv != null && String(rv).trim() !== '') { + extracted[key] = rv; + if (confidence[key] === undefined || confidence[key] === null) { + confidence[key] = ruleConfidence[key]; + } + } + } + } + + // 2c. CSD PO: Vertex sometimes returns supplier letterhead as customer_name; OCR often has Sold To / Bill To. + if (String(currentDocType).toUpperCase().includes('CSD_PO') && ocrText?.trim()) { + const refined = CpcRuleExtractService.refineCsdPoCustomerName(ocrText, extracted.customer_name); + const prev = String(extracted.customer_name ?? '').trim(); + if (refined && refined !== prev) { + extracted.customer_name = refined; + extracted.authorized_person_name = refined; + const cc = confidence as Record; + if (cc.customer_name === undefined || cc.customer_name === null || Number(cc.customer_name) < 0.68) { + cc.customer_name = 0.72; + } + } + } + + // 3. Validation + const v = CpcValidationService.validateSrs( + expectedPayload, + extracted, + confidence, + currentDocType, + targetClaimId, + currentAttempt, + expectedFieldKeysForPipeline.length > 0 ? expectedFieldKeysForPipeline : null + ); + + const uiStatus = v.validation_status || v.status; + + const documentId = randomUUID(); + const storedFileName = buildCpcCsdStoredFileName({ + documentId, + attemptNo: currentAttempt, + docType: currentDocType, + originalName: file.originalname, + mimetype: file.mimetype, + fileIndex: i + }); + + let docUrl: string; + try { + const uploadResult = await gcsStorageService.uploadCpcCsdFileWithFallback({ + buffer: file.buffer, + originalName: file.originalname, + mimeType: file.mimetype, + channel: storageChannel, + bookingSegment: safeBookingSeg, + fileName: storedFileName + }); + docUrl = uploadResult.storageUrl; + const fp = uploadResult.filePath || ''; + const nestedOk = + fp.includes('/csd/') || + fp.includes('/cpc/') || + docUrl.startsWith('https://') || + docUrl.startsWith('http://'); + if (!nestedOk) { + logger.warn( + '[CpcController] Unexpected CPC/CSD storage path (expected cpc-csd-files/csd|cpc/.../documents/). Check you are not hitting an old API process.', + { filePath: fp, storageUrl: docUrl } + ); + } + } catch (e) { + logger.error( + `[CpcController] Could not persist CPC/CSD file for ${file.originalname}`, + e + ); + docUrl = `local://temp/${file.originalname}`; + } + + const saved = await CpcDocument.create({ + id: documentId, + bookingId: files.length > 1 ? `${targetClaimId}-${i + 1}` : (booking_id || targetClaimId), + claimId: targetClaimId, + attemptNo: currentAttempt, + documentType: currentDocType, + documentGcpUrl: docUrl, + provider: provider || "GEMINI_VERTEX", + msdPayload: expectedPayload, + extractedFields: extracted, + fieldConfidence: confidence, + validationStatus: uiStatus, + matchPercentage: v.match_percentage, + ipAddress: String(ipAddress || ''), + mismatchReasons: v.mismatch_reasons, + fieldResults: v.field_results + }); + + await CpcAuditLog.create({ + documentId: saved.id, + action: 'UPLOADED', + performedBy: clientId, + newState: { + status: uiStatus, + match: v.match_percentage, + attempt: currentAttempt, + request_id: requestId, + client_id: clientId, + timestamp: new Date().toISOString() + }, + remarks: `Document ${file.originalname} uploaded via unified pipeline (Attempt ${currentAttempt})` + }); + + results.push({ + document_id: saved.id, + booking_id: saved.bookingId, + claim_id: saved.claimId, + attempt_no: saved.attemptNo, + status: v.status, + validation_status: v.validation_status, + match_percentage: v.match_percentage, + overall_match_percentage: v.match_percentage, + threshold: v.threshold, + mismatch_summary: v.mismatch_summary, + mismatch_reasons: v.mismatch_reasons, + extracted_fields: extracted, + field_confidence: confidence, + field_results: v.field_results, + extraction_source: extractionSource + }); + } catch (fileErr: any) { + logger.error(`[CpcController] Failed processing file ${file.originalname}`, fileErr); + results.push({ + filename: file.originalname, + error: fileErr?.message || 'Processing failed', + error_code: 'INTERNAL_SERVER_ERROR', + error_message: fileErr?.message || 'Processing failed', + retryable: true + }); + } + } + + // Return legacy compatible bulk response if multiple files, or single object if one file + if (files.length > 1) { + return res.json({ + count: results.length, + results: results + }); + } else { + const single = results[0]; + if (!single || (single as { error?: string }).error) { + return res.status(422).json({ + error_code: (single as { error_code?: string })?.error_code || 'UPLOAD_FAILED', + error_message: (single as { error_message?: string })?.error_message || (single as { error?: string })?.error || 'Upload failed', + retryable: (single as { retryable?: boolean })?.retryable ?? true + }); + } + return res.json(single); + } + + } catch (error: any) { + logger.error("[CpcController] validateDocumentUpload Error:", error); + return res.status(500).json({ + error_code: 'SERVER_ERROR', + error_message: error.message || 'Internal Server Error', + retryable: true + }); + } + } + + + /** + * Get recent documents for the dashboard + */ + async getRecentDocuments(req: Request, res: Response) { + try { + const { search, status, type, limit = 50, page, sortBy, order } = req.query; + const take = parseInt(limit as string); + const pageNum = parseInt(page as string || '1'); + const skip = (pageNum - 1) * take; + + const andParts: Record[] = []; + appendCpcDocumentFilters(andParts, { + type: type as string, + status: status as string, + search: search as string, + searchIncludeId: true + }); + const where = cpcWhereFromAndParts(andParts); + + const validSortFields = ['id', 'bookingId', 'createdAt', 'documentType', 'validationStatus', 'claimId', 'matchPercentage']; + const sortKey = typeof sortBy === 'string' && validSortFields.includes(sortBy) ? sortBy : 'createdAt'; + const sortDir = order === 'asc' ? 'ASC' : 'DESC'; + + const { count, rows } = await CpcDocument.findAndCountAll({ + where, + limit: take, + offset: skip, + order: [[sortKey, sortDir]] + }); + + const enriched = rows.map((doc: any, idx: number) => { + const docJson = doc.toJSON(); + return { + ...docJson, + summary: CpcHistoryService.getSummaryRow(docJson, skip + idx) + }; + }); + + + return res.json({ + items: enriched, + meta: { + total: count, + page: pageNum, + limit: take, + pages: Math.ceil(count / take) + } + }); + } catch (error: any) { + logger.error("[CpcController] getRecentDocuments Error:", error); + return res.status(500).json({ + error_code: 'INTERNAL_SERVER_ERROR', + error_message: 'Failed to fetch documents', + retryable: true + }); + } + } + + /** + * Stream original upload bytes (GCS or local fallback). Used by UI preview; gs:// is not loadable in a browser iframe. + */ + async getDocumentFile(req: Request, res: Response) { + try { + const { id } = req.params; + const document = await CpcDocument.findByPk(id); + if (!document) { + return res.status(404).json({ + error_code: 'DOCUMENT_NOT_FOUND', + error_message: 'Document not found', + retryable: false + }); + } + + const ref = document.documentGcpUrl || ''; + + if (ref.startsWith('gs://')) { + try { + const buf = await cpcGcsService.downloadFromGcs(ref); + const ct = contentTypeFromPath(ref); + res.setHeader('Content-Type', ct); + const base = path.basename(ref.split('?')[0] || '') || 'document'; + res.setHeader('Content-Disposition', `inline; filename="${base}"`); + return res.status(200).send(buf); + } catch (err: unknown) { + logger.error('[CpcController] getDocumentFile GCS download failed', err); + return res.status(502).json({ + error_code: 'DOCUMENT_FETCH_FAILED', + error_message: 'Could not read file from storage', + retryable: true + }); + } + } + + const uploadsAbs = resolveUploadsDirFromPublicUrl(ref); + if (uploadsAbs && fs.existsSync(uploadsAbs)) { + res.setHeader('Content-Type', contentTypeFromPath(uploadsAbs)); + res.setHeader('Content-Disposition', 'inline'); + return res.status(200).sendFile(path.resolve(uploadsAbs)); + } + + const localAbs = resolveCpcLocalDiskPath(ref); + if (localAbs && fs.existsSync(localAbs)) { + res.setHeader('Content-Type', contentTypeFromPath(localAbs)); + res.setHeader('Content-Disposition', 'inline'); + return res.status(200).sendFile(path.resolve(localAbs)); + } + + if (ref.startsWith('http://') || ref.startsWith('https://')) { + return res.redirect(302, ref); + } + + return res.status(404).json({ + error_code: 'NO_PREVIEWABLE_FILE', + error_message: 'No previewable file for this document', + retryable: false + }); + } catch (error: unknown) { + logger.error('[CpcController] getDocumentFile Error:', error); + return res.status(500).json({ + error_code: 'INTERNAL_SERVER_ERROR', + error_message: 'Failed to stream document', + retryable: true + }); + } + } + + /** + * Get single document details including analytics breakdown + */ + async getDocumentById(req: Request, res: Response) { + try { + const { id } = req.params; + const document = await CpcDocument.findByPk(id, { + include: [{ model: CpcAuditLog, as: 'auditLogs' }] + }); + + if (!document) { + return res.status(404).json({ + error_code: 'DOCUMENT_NOT_FOUND', + error_message: 'Document not found', + retryable: false + }); + } + + const docJson = document.toJSON(); + const enriched = { + ...docJson, + field_results: CpcHistoryService.getDetailedFieldResults(docJson) + }; + + return res.json(enriched); + } catch (error: any) { + logger.error("[CpcController] getDocumentById Error:", error); + return res.status(500).json({ + error_code: 'INTERNAL_SERVER_ERROR', + error_message: 'Failed to fetch document', + retryable: true + }); + } + } + + /** + * Get dashboard analytics + */ + async getAnalytics(req: Request, res: Response) { + try { + const statusCounts: any = await CpcDocument.findAll({ + attributes: [ + 'validationStatus', + [sequelize.fn('COUNT', sequelize.col('id')), 'count'] + ], + group: ['validationStatus'] + }); + + const distribution: any = {}; + let totalDocs = 0; + statusCounts.forEach((item: any) => { + const status = item.get('validationStatus'); + const count = parseInt(item.get('count')); + distribution[status] = count; + totalDocs += count; + }); + + + const matchCount = (distribution['MATCH'] || 0) + (distribution['APPROVED'] || 0) + (distribution['SUCCESSFUL'] || 0); + const passRate = totalDocs > 0 ? Math.round((matchCount / totalDocs) * 100) : 0; + + const mismatchDocs = await CpcDocument.findAll({ + where: { + validationStatus: { [Op.in]: ['MISMATCH', 'REJECTED', 'UNSUCCESSFUL'] } + }, + attributes: ['mismatchReasons'], + limit: 200, + order: [['createdAt', 'DESC']] + }); + + const fieldErrors: Record = {}; + mismatchDocs.forEach((doc: any) => { + const reasons = doc.mismatchReasons; + if (Array.isArray(reasons)) { + reasons.forEach((reason: { field?: string }) => { + const field = reason.field; + if (field) { + fieldErrors[field] = (fieldErrors[field] || 0) + 1; + } + }); + } + }); + + const topMismatchFields = Object.entries(fieldErrors) + .sort(([, a], [, b]) => b - a) + .slice(0, 5) + .map(([field, count]) => ({ field, count })); + + const sevenDaysAgo = new Date(); + sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7); + + const dailyDocs: any = await CpcDocument.findAll({ + attributes: [ + [sequelize.fn('DATE', sequelize.col('created_at')), 'date'], + [sequelize.fn('COUNT', sequelize.col('id')), 'count'] + ], + where: { createdAt: { [Op.gte]: sevenDaysAgo } }, + group: [sequelize.fn('DATE', sequelize.col('created_at'))], + order: [[sequelize.fn('DATE', sequelize.col('created_at')), 'ASC']] + }); + + const dailyVolume = dailyDocs.map((d: any) => ({ + date: d.get('date'), + count: parseInt(d.get('count')) + })); + + return res.json({ + totalDocs, + passRate, + distribution, + topMismatchFields, + dailyVolume + }); + } catch (error: any) { + logger.error("[CpcController] getAnalytics Error:", error); + return res.status(500).json({ + error_code: 'INTERNAL_SERVER_ERROR', + error_message: 'Failed to fetch analytics', + retryable: true + }); + } + } + + /** + * Manually override validation status + */ + async updateDocumentStatus(req: Request, res: Response) { + try { + const { id } = req.params; + const { status, remarks, correctedFields } = req.body; + + const document = await CpcDocument.findByPk(id); + if (!document) { + return res.status(404).json({ + error_code: 'DOCUMENT_NOT_FOUND', + error_message: 'Document not found', + retryable: false + }); + } + + const previousStatus = document.validationStatus; + + await document.update({ + validationStatus: status, + extractedFields: correctedFields || document.extractedFields, + mismatchReasons: remarks ? [{ field: 'MANUAL_REVIEW', expected: '-', actual: remarks }] : document.mismatchReasons + }); + + const statusRequestId = String(req.headers['x-request-id'] || randomUUID()); + const statusClientId = String(req.headers['x-client-id'] || (req as any).user?.email || 'unknown'); + + await CpcAuditLog.create({ + documentId: id, + action: 'STATUS_UPDATED', + performedBy: statusClientId, + previousState: { status: previousStatus }, + newState: { + status, + request_id: statusRequestId, + client_id: statusClientId, + timestamp: new Date().toISOString() + }, + remarks: remarks || `Status manual update to ${status}` + }); + + return res.json(document); + } catch (error: any) { + logger.error("[CpcController] updateDocumentStatus Error:", error); + return res.status(500).json({ + error_code: 'INTERNAL_SERVER_ERROR', + error_message: 'Failed to update status', + retryable: true + }); + } + } + + /** + * Delete document record + */ + async deleteDocument(req: Request, res: Response) { + try { + const { id } = req.params; + const document = await CpcDocument.findByPk(id); + if (!document) { + return res.status(404).json({ + error_code: 'DOCUMENT_NOT_FOUND', + error_message: 'Document not found', + retryable: false + }); + } + + const ref = document.documentGcpUrl || ''; + const uploadsAbs = resolveUploadsDirFromPublicUrl(ref); + if (uploadsAbs && fs.existsSync(uploadsAbs)) { + try { + fs.unlinkSync(uploadsAbs); + } catch (e) { + logger.warn('[CpcController] Could not delete CPC/CSD file under uploads', e); + } + } + const localAbs = resolveCpcLocalDiskPath(ref); + if (localAbs && fs.existsSync(localAbs)) { + try { + fs.unlinkSync(localAbs); + } catch (e) { + logger.warn('[CpcController] Could not delete local CPC file', e); + } + } + + await document.destroy(); + return res.json({ success: true, message: 'Document deleted successfully' }); + } catch (error: any) { + logger.error("[CpcController] deleteDocument Error:", error); + return res.status(500).json({ + error_code: 'INTERNAL_SERVER_ERROR', + error_message: 'Failed to delete document', + retryable: false + }); + } + } + + /** + * Fetch all validation attempts for a claim + */ + async getClaimHistory(req: Request, res: Response) { + try { + const { claimId } = req.query; + if (!claimId) { + return res.status(400).json({ + error_code: 'MISSING_CLAIM_ID', + error_message: 'claimId is required', + retryable: false + }); + } + + const documents = await CpcDocument.findAll({ + where: { [Op.or]: [{ claimId: claimId as string }, { bookingId: claimId as string }] }, + order: [['attemptNo', 'ASC'], ['createdAt', 'DESC']] + }); + + const attemptsMap: any = {}; + documents.forEach((doc: any) => { + const attNum = doc.attemptNo || 1; + if (!attemptsMap[attNum]) attemptsMap[attNum] = []; + attemptsMap[attNum].push(doc.toJSON()); + }); + + const sortedAttempts = Object.keys(attemptsMap) + .sort((a, b) => parseInt(a) - parseInt(b)) + .map((num: string) => { + const docsInAttempt = attemptsMap[num]; + return { + attempt_no: parseInt(num), + created_at: docsInAttempt[0].createdAt, + documents: docsInAttempt.map((d: any) => ({ + ...d, + field_results: CpcHistoryService.getDetailedFieldResults(d) + })), + summary_report_rows: docsInAttempt.map((d: any, idx: number) => CpcHistoryService.getSummaryRow(d, idx)) + }; + }); + + + return res.json({ claimId, attempts: sortedAttempts }); + } catch (error: any) { + logger.error("[CpcController] getClaimHistory Error:", error); + return res.status(500).json({ + error_code: 'INTERNAL_SERVER_ERROR', + error_message: 'Failed to fetch history', + retryable: true + }); + } + } + + /** CPC-CSD `POST /api/v1/ocr/validate` — URL-based validation using document_gcp_url + msd_payload. */ + async validateDocumentByUrlStub(req: Request, res: Response) { + try { + const { + claim_id, + booking_id, + document_type, + document_gcp_url, + msd_payload, + provider + } = req.body || {}; + + const targetClaimId = claim_id || booking_id; + if (!targetClaimId) { + return res.status(400).json({ + error_code: 'MISSING_CLAIM_ID', + error_message: 'claim_id or booking_id is required', + retryable: false + }); + } + + if (!document_gcp_url || typeof document_gcp_url !== 'string') { + return res.status(400).json({ + error_code: 'INVALID_DOCUMENT_URL', + error_message: 'document_gcp_url is required and must be a gs:// path', + retryable: false + }); + } + + let payload = {}; + try { + payload = typeof msd_payload === 'string' ? JSON.parse(msd_payload) : (msd_payload || {}); + } catch { + return res.status(400).json({ + error_code: 'INVALID_PAYLOAD', + error_message: 'msd_payload must be valid JSON', + retryable: false + }); + } + + const requestedAttemptNo = Number((req.body as any)?.attempt_no); + const hasRequestedAttemptNo = Number.isFinite(requestedAttemptNo) && requestedAttemptNo > 0; + const attemptRows = await CpcDocument.findAll({ + attributes: ['attemptNo'], + where: { claimId: targetClaimId }, + group: ['attemptNo'], + raw: true + }) as Array<{ attemptNo?: number }>; + const usedAttempts = new Set( + attemptRows + .map((r) => Number(r?.attemptNo || 0)) + .filter((n) => Number.isFinite(n) && n > 0) + ); + + if (isCpcAttemptLimitEnforced()) { + if (hasRequestedAttemptNo && requestedAttemptNo > CPC_MAX_ATTEMPTS) { + return res.status(422).json({ + error_code: 'MAX_ATTEMPTS_REACHED', + error_message: `Only ${CPC_MAX_ATTEMPTS} validation attempts are allowed per claim`, + retryable: false + }); + } + if (!hasRequestedAttemptNo && usedAttempts.size >= CPC_MAX_ATTEMPTS) { + return res.status(422).json({ + error_code: 'MAX_ATTEMPTS_REACHED', + error_message: `Only ${CPC_MAX_ATTEMPTS} validation attempts are allowed per claim`, + retryable: false + }); + } + if (hasRequestedAttemptNo && !usedAttempts.has(requestedAttemptNo) && usedAttempts.size >= CPC_MAX_ATTEMPTS) { + return res.status(422).json({ + error_code: 'MAX_ATTEMPTS_REACHED', + error_message: `Only ${CPC_MAX_ATTEMPTS} validation attempts are allowed per claim`, + retryable: false + }); + } + } + + const currentAttempt = hasRequestedAttemptNo ? requestedAttemptNo : (usedAttempts.size + 1); + let fileBuffer: Buffer; + try { + fileBuffer = await cpcGcsService.downloadFromGcs(String(document_gcp_url)); + } catch (error: any) { + return res.status(422).json({ + error_code: error?.message === 'INVALID_DOCUMENT_URL' ? 'INVALID_DOCUMENT_URL' : 'DOCUMENT_FETCH_FAILED', + error_message: error?.message || 'Unable to fetch document from GCS', + retryable: true + }); + } + + const tempFile: Express.Multer.File = { + fieldname: 'file', + originalname: path.basename(String(document_gcp_url)), + encoding: '7bit', + mimetype: contentTypeFromPath(String(document_gcp_url)), + size: fileBuffer.length, + buffer: fileBuffer, + stream: undefined as any, + destination: '', + filename: '', + path: '' + }; + + (req as any).file = tempFile; + (req as any).files = [tempFile]; + req.body = { + ...req.body, + booking_id: booking_id || claim_id, + claim_id: targetClaimId, + attempt_no: currentAttempt, + skip_min_attachment_check: 'true', + provider: provider || 'GEMINI_VERTEX', + document_type: document_type || 'GENERIC_INVOICE', + msd_payload: payload + }; + + return this.validateDocumentUpload(req, res); + } catch (error: any) { + logger.error('[CpcController] validateDocumentByUrl Error:', error); + return res.status(500).json({ + error_code: 'INTERNAL_SERVER_ERROR', + error_message: error?.message || 'Internal server error', + retryable: true + }); + } + } + + /** CPC-CSD `POST /api/upload` — same GCS vs local behaviour as workflow requests; returns `gcsUrl` + `storageUrl`. */ + async uploadBareFile(req: Request, res: Response) { + try { + const file = req.file; + if (!file) { + return res.status(400).json({ + error_code: 'NO_FILE_UPLOADED', + error_message: 'No file uploaded', + retryable: false + }); + } + const uploadResult = await gcsStorageService.uploadCpcCsdFileWithFallback({ + buffer: file.buffer, + originalName: file.originalname, + mimeType: file.mimetype, + channel: 'cpc', + bookingSegment: 'bare-upload' + }); + const m = uploadResult.storageUrl.match(/^https:\/\/storage\.googleapis\.com\/([^/]+)\/(.+)$/); + const gcsUrl = m ? `gs://${m[1]}/${m[2]}` : uploadResult.storageUrl; + return res.json({ + gcsUrl, + storageUrl: uploadResult.storageUrl, + filePath: uploadResult.filePath + }); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : 'UPLOAD_FAILED'; + return res.status(500).json({ + error_code: 'UPLOAD_FAILED', + error_message: msg, + retryable: true + }); + } + } +} + +export const cpcCdcController = new CpcCdcController(); diff --git a/src/controllers/CpcReportController.ts b/src/controllers/CpcReportController.ts new file mode 100644 index 0000000..77d2cf1 --- /dev/null +++ b/src/controllers/CpcReportController.ts @@ -0,0 +1,204 @@ +import { Request, Response } from 'express'; +import { CpcHistoryService } from '../services/cpc-cdc/CpcHistoryService'; +import { CpcDocument } from '../models/CpcDocument'; +import { appendCpcDocumentFilters, cpcWhereFromAndParts } from '../services/cpc-cdc/utils'; +import ExcelJS from 'exceljs'; +import { ResponseHandler } from '../utils/responseHandler'; + +import { Op } from 'sequelize'; + +export class CpcReportController { + /** + * Download Excel report for a specific claim + */ + async downloadReport(req: Request, res: Response) { + try { + const { claimId } = req.params; + const { attempt } = req.query; + + const where: any = { + [Op.or]: [ + { claimId: claimId }, + { bookingId: claimId } + ] + }; + if (attempt) where.attemptNo = parseInt(attempt as string); + + const docs = await CpcDocument.findAll({ + where, + order: [['createdAt', 'DESC']] + }); + + if (!docs || docs.length === 0) { + return ResponseHandler.error(res, "No records found for this claim", 404); + } + + const workbook = new ExcelJS.Workbook(); + const sheet = workbook.addWorksheet('Validation Report'); + + // HEADERS + const row1 = sheet.getRow(1); + row1.values = [ + 'Booking Type', 'Booking Number', 'Document Count', 'Document Name', + 'Customer Name', '', '', '', '', + 'PO Number /Authorisation Letter Number', '', '', '', '', + 'Aadhar Number', '', '', '', '', + 'PO Amount / Authorisation Letter Amount', '', '', '', '', + 'Signature & Stamp Availability', '', '', '', '', + 'Final Validation' + ]; + + const row2 = sheet.getRow(2); + row2.values = [ + '', '', '', '', + 'Expected', 'OCR', 'Accuracy Matching %', 'Accuracy Criteria', 'Is Match the Accuracy', + 'Expected', 'OCR', 'Accuracy Matching %', 'Accuracy Criteria', 'Is Match the Accuracy', + 'Expected', 'OCR', 'Accuracy Matching %', 'Accuracy Criteria', 'Is Match the Accuracy', + 'Expected', 'OCR', 'Accuracy Matching %', 'Accuracy Criteria', 'Is Match the Accuracy', + 'Expected', 'OCR', 'Accuracy Matching Availability', 'Accuracy Criteria', 'Success Ratio', + '' + ]; + + sheet.mergeCells('E1:I1'); + sheet.mergeCells('J1:N1'); + sheet.mergeCells('O1:S1'); + sheet.mergeCells('T1:X1'); + sheet.mergeCells('Y1:AC1'); + sheet.mergeCells('A1:A2'); sheet.mergeCells('B1:B2'); sheet.mergeCells('C1:C2'); sheet.mergeCells('D1:D2'); + sheet.mergeCells('AD1:AD2'); + + [row1, row2].forEach((r: any) => { + r.font = { bold: true, size: 9 }; + r.alignment = { vertical: 'middle', horizontal: 'center', wrapText: true }; + r.eachCell((cell: any) => { + cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFD9D9D9' } }; + cell.border = { top: { style: 'thin' }, left: { style: 'thin' }, bottom: { style: 'thin' }, right: { style: 'thin' } }; + }); + }); + + docs.forEach((doc: any, idx: number) => { + + const rowData = CpcHistoryService.getSummaryRow(doc, idx); + const values = [ + rowData.booking_type, + rowData.booking_number, + rowData.document_count, + rowData.document_name, + rowData.customer_name_group.msd, rowData.customer_name_group.ocr, rowData.customer_name_group.accuracy_pct, rowData.customer_name_group.criteria, rowData.customer_name_group.is_match, + rowData.po_or_auth_number_group.msd, rowData.po_or_auth_number_group.ocr, rowData.po_or_auth_number_group.accuracy_pct, rowData.po_or_auth_number_group.criteria, rowData.po_or_auth_number_group.is_match, + rowData.aadhaar_number_group.msd, rowData.aadhaar_number_group.ocr, rowData.aadhaar_number_group.accuracy_pct, rowData.aadhaar_number_group.criteria, rowData.aadhaar_number_group.is_match, + rowData.amount_group.msd, rowData.amount_group.ocr, rowData.amount_group.accuracy_pct, rowData.amount_group.criteria, rowData.amount_group.is_match, + rowData.stamp_group.msd, rowData.stamp_group.ocr, rowData.stamp_group.accuracy_pct, rowData.stamp_group.criteria, rowData.stamp_group.is_match, + rowData.final_validation + ]; + const row = sheet.addRow(values); + row.eachCell((cell: any, colNum: number) => { + cell.border = { top: { style: 'thin' }, left: { style: 'thin' }, bottom: { style: 'thin' }, right: { style: 'thin' } }; + cell.font = { size: 8 }; + cell.alignment = { vertical: 'middle', horizontal: 'center' }; + + if (cell.value === 'N.A.' && colNum > 4) { + cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFF0000' } }; + cell.font = { color: { argb: 'FFFFFFFF' }, size: 8, bold: true }; + } + }); + }); + + sheet.addRow([]); + sheet.addRow([]); + const detailHeader = sheet.addRow(['Detailed Field-Wise Comparison']); + detailHeader.font = { bold: true, size: 12 }; + + docs.forEach((doc: any) => { + const docHeader = sheet.addRow([`Document: ${doc.documentType?.replace(/_/g, ' ')}`]); + docHeader.font = { bold: true, size: 10 }; + + + const subHeader = sheet.addRow(['Field', 'Expected', 'Extracted (OCR)', 'Accuracy %', 'Criteria', 'Status', 'Message']); + const finalResults = CpcHistoryService.getDetailedFieldResults(doc); + + finalResults.forEach((f: any) => { + sheet.addRow([ + f.field.replace(/_/g, ' '), + f.expected || '-', + f.extracted || 'Not extracted', + f.accuracy, + f.criteria, + f.pass ? 'PASS' : 'FAIL', + f.message + ]); + }); + + sheet.addRow([]); + }); + + res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + res.setHeader('Content-Disposition', `attachment; filename=Report_${claimId}.xlsx`); + await workbook.xlsx.write(res); + res.end(); + } catch (error: any) { + return ResponseHandler.error(res, error.message || "Report generation failed", 500); + } + } + + /** + * Download Master Audit Report for all filtered documents + */ + async downloadAllReport(req: Request, res: Response) { + try { + const { search, status, type } = req.query; + const andParts: Record[] = []; + appendCpcDocumentFilters(andParts, { + type: type as string, + status: status as string, + search: search as string, + searchIncludeId: false + }); + const where = cpcWhereFromAndParts(andParts); + + const docs = await CpcDocument.findAll({ + where, + order: [['createdAt', 'DESC']] + }); + + const workbook = new ExcelJS.Workbook(); + const sheet = workbook.addWorksheet('Master Audit Trail'); + + const row1 = sheet.getRow(1); + row1.values = ['Booking Type', 'Booking Number', 'Doc ID', 'Document Name', 'Customer Name', '', '', '', '', 'PO Number /Authorisation Letter Number', '', '', '', '', 'Aadhar Number', '', '', '', '', 'PO Amount / Authorisation Letter Amount', '', '', '', '', 'Signature & Stamp Availability', '', '', '', '', 'Final Validation']; + + const row2 = sheet.getRow(2); + row2.values = ['', '', '', '', 'Expected', 'OCR', 'Accuracy Matching %', 'Accuracy Criteria', 'Is Match the Accuracy', 'Expected', 'OCR', 'Accuracy Matching %', 'Accuracy Criteria', 'Is Match the Accuracy', 'Expected', 'OCR', 'Accuracy Matching %', 'Accuracy Criteria', 'Is Match the Accuracy', 'Expected', 'OCR', 'Accuracy Matching %', 'Accuracy Criteria', 'Is Match the Accuracy', 'Expected', 'OCR', 'Accuracy Matching Availability', 'Accuracy Criteria', 'Success Ratio', '']; + + sheet.mergeCells('E1:I1'); sheet.mergeCells('J1:N1'); sheet.mergeCells('O1:S1'); sheet.mergeCells('T1:X1'); sheet.mergeCells('Y1:AC1'); sheet.mergeCells('A1:A2'); sheet.mergeCells('B1:B2'); sheet.mergeCells('C1:C2'); sheet.mergeCells('D1:D2'); sheet.mergeCells('AD1:AD2'); + + [row1, row2].forEach((r: any) => { + r.font = { bold: true, size: 9 }; + r.alignment = { vertical: 'middle', horizontal: 'center', wrapText: true }; + r.eachCell((cell: any) => { cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFD9D9D9' } }; cell.border = { top: { style: 'thin' }, left: { style: 'thin' }, bottom: { style: 'thin' }, right: { style: 'thin' } }; }); + }); + + docs.forEach((doc: any, idx: number) => { + + const rowData = CpcHistoryService.getSummaryRow(doc, idx); + const values = [ + rowData.booking_type, rowData.booking_number, String(doc.id).slice(0, 8), rowData.document_name, + rowData.customer_name_group.msd, rowData.customer_name_group.ocr, rowData.customer_name_group.accuracy_pct, rowData.customer_name_group.criteria, rowData.customer_name_group.is_match, + rowData.po_or_auth_number_group.msd, rowData.po_or_auth_number_group.ocr, rowData.po_or_auth_number_group.accuracy_pct, rowData.po_or_auth_number_group.criteria, rowData.po_or_auth_number_group.is_match, + rowData.aadhaar_number_group.msd, rowData.aadhaar_number_group.ocr, rowData.aadhaar_number_group.accuracy_pct, rowData.aadhaar_number_group.criteria, rowData.aadhaar_number_group.is_match, + rowData.amount_group.msd, rowData.amount_group.ocr, rowData.amount_group.accuracy_pct, rowData.amount_group.criteria, rowData.amount_group.is_match, + rowData.stamp_group.msd, rowData.stamp_group.ocr, rowData.stamp_group.accuracy_pct, rowData.stamp_group.criteria, rowData.stamp_group.is_match, + rowData.final_validation + ]; + const row = sheet.addRow(values); + }); + + res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + res.setHeader('Content-Disposition', `attachment; filename=Master_Audit_Report.xlsx`); + await workbook.xlsx.write(res); + res.end(); + } catch (error: any) { + return ResponseHandler.error(res, error.message || "Master report failed", 500); + } + } +} diff --git a/src/controllers/admin.controller.ts b/src/controllers/admin.controller.ts index 264e504..a4f112d 100644 --- a/src/controllers/admin.controller.ts +++ b/src/controllers/admin.controller.ts @@ -9,6 +9,11 @@ import { initializeHolidaysCache, clearWorkingHoursCache } from '@utils/tatTimeU import { clearConfigCache } from '@services/configReader.service'; import { User, UserRole } from '@models/User'; import { sanitizeHtml, sanitizeObject, isHtmlEmpty } from '@utils/sanitizer'; +import { + CPC_CSD_ADMIN_CONFIG_KEY, + CPC_CDC_ADMIN_CONFIG_KEY_LEGACY, + selectCpcCsdAdminConfigValue, +} from '@utils/cpcCsdAdminConfigDb'; /** * Get all holidays (with optional year filter) @@ -564,6 +569,10 @@ const DEFAULT_FORM16_CONFIG = { reminderNotificationTemplate: 'Reminder: Dear [Name], your Form 16A submission is pending for request [Request ID]. Please complete it.', }; +const DEFAULT_CPC_CSD_CONFIG = { + viewerEmails: [] as string[], +}; + /** * Get Form 16 admin configuration (who can see submission data, 26AS, reminders) */ @@ -721,6 +730,93 @@ export const putForm16Config = async (req: Request, res: Response): Promise => { + try { + const raw = await selectCpcCsdAdminConfigValue(); + + if (raw) { + try { + const parsed = JSON.parse(raw); + res.json({ + success: true, + data: { + viewerEmails: Array.isArray(parsed.viewerEmails) ? parsed.viewerEmails : DEFAULT_CPC_CSD_CONFIG.viewerEmails, + }, + }); + return; + } catch { + // fall through to defaults + } + } + + res.json({ success: true, data: DEFAULT_CPC_CSD_CONFIG }); + } catch (error: any) { + logger.error('[Admin] Error fetching CPC-CSD config:', error); + res.status(500).json({ + success: false, + error: error.message || 'Failed to fetch CPC-CSD configuration', + }); + } +}; + +/** + * Update CPC-CSD admin configuration. + */ +export const putCpcCdcConfig = async (req: Request, res: Response): Promise => { + try { + const userId = req.user?.userId; + if (!userId) { + res.status(401).json({ success: false, error: 'User not authenticated' }); + return; + } + + const body = sanitizeObject(req.body as Record); + const normalizeEmail = (e: unknown) => String(e ?? '').trim().toLowerCase(); + const viewerEmails = Array.isArray(body.viewerEmails) + ? body.viewerEmails.map(normalizeEmail).filter(Boolean) + : DEFAULT_CPC_CSD_CONFIG.viewerEmails; + + const configValue = JSON.stringify({ + viewerEmails, + }); + + await sequelize.query( + `INSERT INTO admin_configurations ( + config_id, config_key, config_category, config_value, value_type, display_name, description, is_editable, is_sensitive, sort_order, created_at, updated_at, last_modified_by, last_modified_at + ) VALUES ( + gen_random_uuid(), :configKey, 'SYSTEM_SETTINGS', :configValue, 'JSON', 'CPC-CSD Admin Config', 'CPC-CSD module visibility settings', true, false, 0, NOW(), NOW(), :userId, NOW() + ) + ON CONFLICT (config_key) DO UPDATE SET + config_value = EXCLUDED.config_value, + last_modified_by = EXCLUDED.last_modified_by, + last_modified_at = NOW(), + updated_at = NOW()`, + { + replacements: { configKey: CPC_CSD_ADMIN_CONFIG_KEY, configValue, userId }, + type: QueryTypes.RAW, + } + ); + + await sequelize.query( + `DELETE FROM admin_configurations WHERE config_key = :legacy`, + { replacements: { legacy: CPC_CDC_ADMIN_CONFIG_KEY_LEGACY }, type: QueryTypes.RAW } + ); + + clearConfigCache(); + logger.info('[Admin] CPC-CSD configuration updated'); + res.json({ success: true, message: 'CPC-CSD configuration saved' }); + } catch (error: any) { + logger.error('[Admin] Error updating CPC-CSD config:', error); + res.status(500).json({ + success: false, + error: error.message || 'Failed to save CPC-CSD configuration', + }); + } +}; + /** * ============================================ * USER ROLE MANAGEMENT (RBAC) diff --git a/src/controllers/cpcPermission.controller.ts b/src/controllers/cpcPermission.controller.ts new file mode 100644 index 0000000..703d7a3 --- /dev/null +++ b/src/controllers/cpcPermission.controller.ts @@ -0,0 +1,36 @@ +import { Request, Response } from 'express'; +import { ResponseHandler } from '../utils/responseHandler'; +import logger from '@utils/logger'; +import { canAccessCpcCdc } from '../services/cpcPermission.service'; + +class CpcPermissionController { + /** + * GET /api/v1/cpc-csd/permissions (legacy: /api/v1/cpc-cdc/permissions) + * Returns CPC-CSD access permission for current user. + */ + async getPermissions(req: Request, res: Response): Promise { + try { + const user = req.user; + if (!user?.userId || !user?.email) { + ResponseHandler.unauthorized(res, 'Authentication required'); + return; + } + + const role = (user as any).role as string | undefined; + const canViewCpcCsd = await canAccessCpcCdc(user.email, role); + + ResponseHandler.success( + res, + { canViewCpcCsd, canViewCpcCdc: canViewCpcCsd }, + 'CPC-CSD permissions' + ); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + logger.error('[CpcPermissionController] getPermissions error:', error); + ResponseHandler.error(res, 'Failed to get CPC-CSD permissions', 500, errorMessage); + } + } +} + +export const cpcPermissionController = new CpcPermissionController(); + diff --git a/src/middlewares/cors.middleware.ts b/src/middlewares/cors.middleware.ts index 61a1b74..dfa2e14 100644 --- a/src/middlewares/cors.middleware.ts +++ b/src/middlewares/cors.middleware.ts @@ -1,5 +1,14 @@ import cors from 'cors'; +/** Vite dev: localhost vs 127.0.0.1, and ports 3000–3010 when 3000/3001 are already taken. */ +function getDevViteOrigins(): string[] { + const out: string[] = []; + for (let port = 3000; port <= 3010; port++) { + out.push(`http://localhost:${port}`, `http://127.0.0.1:${port}`); + } + return out; +} + // Configure allowed origins - uses only FRONTEND_URL from environment const getAllowedOrigins = (): string[] | boolean => { const frontendUrl = process.env.FRONTEND_URL; @@ -15,10 +24,9 @@ const getAllowedOrigins = (): string[] | boolean => { console.error(' Multiple origins: FRONTEND_URL=https://app1.com,https://app2.com'); return []; } else { - // Dev fallback: allow localhost:3000 - console.warn('⚠️ WARNING: FRONTEND_URL not set. Defaulting to http://localhost:3000 for development.'); - console.warn(' To avoid this warning, set FRONTEND_URL=http://localhost:3000 in your .env file'); - return ['http://localhost:3000']; + console.warn('⚠️ WARNING: FRONTEND_URL not set. Defaulting Vite dev origins (localhost + 127.0.0.1).'); + console.warn(' Set FRONTEND_URL in .env if you use another host/port.'); + return getDevViteOrigins(); } } @@ -35,12 +43,14 @@ const getAllowedOrigins = (): string[] | boolean => { if (origins.length === 0) { console.error('❌ ERROR: FRONTEND_URL is set but contains no valid URLs!'); - return isProduction ? [] : ['http://localhost:3000']; // Fallback for development + return isProduction ? [] : getDevViteOrigins(); // Fallback for development } - // In development always allow localhost:3000 (Vite default) so frontend works even if FRONTEND_URL is 3001 - if (!isProduction && !origins.includes('http://localhost:3000')) { - origins = ['http://localhost:3000', ...origins]; + // In development allow common Vite host/port combos (avoids CORS when Vite bumps to 3002+) + if (!isProduction) { + for (const o of getDevViteOrigins()) { + if (!origins.includes(o)) origins.push(o); + } } console.log(`✅ CORS: Allowing origins from FRONTEND_URL: ${origins.join(', ')}`); diff --git a/src/middlewares/cpcPermission.middleware.ts b/src/middlewares/cpcPermission.middleware.ts new file mode 100644 index 0000000..f290c7b --- /dev/null +++ b/src/middlewares/cpcPermission.middleware.ts @@ -0,0 +1,34 @@ +/** + * CPC-CSD permission middleware – enforces API-driven viewer list. + * Use after authenticateToken so req.user is available. + */ + +import { Request, Response, NextFunction } from 'express'; +import { ResponseHandler } from '../utils/responseHandler'; +import { canAccessCpcCdc } from '../services/cpcPermission.service'; + +export const requireCpcCdcAccess = async ( + req: Request, + res: Response, + next: NextFunction +): Promise => { + try { + const user = req.user; + if (!user?.userId || !user?.email) { + ResponseHandler.unauthorized(res, 'Authentication required'); + return; + } + + const role = (user as any).role as string | undefined; + const allowed = await canAccessCpcCdc(user.email, role); + if (!allowed) { + ResponseHandler.forbidden(res, 'You do not have permission to access CPC-CSD'); + return; + } + + next(); + } catch (error) { + ResponseHandler.error(res, 'Permission check failed', 500, error instanceof Error ? error.message : 'Unknown error'); + } +}; + diff --git a/src/migrations/2026041300-create-cpc-cdc-tables.ts b/src/migrations/2026041300-create-cpc-cdc-tables.ts new file mode 100644 index 0000000..ae30b37 --- /dev/null +++ b/src/migrations/2026041300-create-cpc-cdc-tables.ts @@ -0,0 +1,130 @@ +import { QueryInterface, DataTypes } from 'sequelize'; + +export async function up(queryInterface: QueryInterface): Promise { + // Create cpc_documents table + await queryInterface.createTable('cpc_documents', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + allowNull: false + }, + booking_id: { + type: DataTypes.STRING(255), + allowNull: true + }, + claim_id: { + type: DataTypes.STRING(255), + allowNull: true + }, + attempt_no: { + type: DataTypes.INTEGER, + defaultValue: 1, + allowNull: false + }, + document_type: { + type: DataTypes.STRING(255), + allowNull: true + }, + document_gcp_url: { + type: DataTypes.TEXT, + allowNull: true + }, + provider: { + type: DataTypes.STRING(255), + allowNull: true + }, + msd_payload: { + type: DataTypes.JSONB, + allowNull: true + }, + extracted_fields: { + type: DataTypes.JSONB, + allowNull: true + }, + field_confidence: { + type: DataTypes.JSONB, + allowNull: true + }, + validation_status: { + type: DataTypes.STRING(255), + allowNull: true + }, + match_percentage: { + type: DataTypes.FLOAT, + allowNull: true + }, + mismatch_reasons: { + type: DataTypes.JSONB, + allowNull: true + }, + field_results: { + type: DataTypes.JSONB, + allowNull: true + }, + ip_address: { + type: DataTypes.STRING(255), + allowNull: true + }, + created_at: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW, + allowNull: false + } + }); + + // Create cpc_audit_logs table + await queryInterface.createTable('cpc_audit_logs', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + allowNull: false + }, + document_id: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'cpc_documents', + key: 'id' + }, + onDelete: 'CASCADE' + }, + action: { + type: DataTypes.STRING(255), + allowNull: false + }, + previous_state: { + type: DataTypes.JSONB, + allowNull: true + }, + new_state: { + type: DataTypes.JSONB, + allowNull: true + }, + performed_by: { + type: DataTypes.STRING(255), + allowNull: true + }, + remarks: { + type: DataTypes.TEXT, + allowNull: true + }, + created_at: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW, + allowNull: false + } + }); + + // Unique index for the multi-attempt claim logic (idempotent for repeated startup migrations) + await queryInterface.sequelize.query(` + CREATE UNIQUE INDEX IF NOT EXISTS unique_cpc_document_attempt + ON cpc_documents (claim_id, attempt_no, document_type); + `); +} + +export async function down(queryInterface: QueryInterface): Promise { + await queryInterface.dropTable('cpc_audit_logs'); + await queryInterface.dropTable('cpc_documents'); +} diff --git a/src/migrations/20260414100000-ensure-cpc-cdc-tables-exist.ts b/src/migrations/20260414100000-ensure-cpc-cdc-tables-exist.ts new file mode 100644 index 0000000..b12cf58 --- /dev/null +++ b/src/migrations/20260414100000-ensure-cpc-cdc-tables-exist.ts @@ -0,0 +1,50 @@ +import { QueryInterface } from 'sequelize'; + +/** + * Idempotent CPC-CDC schema for environments where 2026041300 did not run or tables were dropped. + * Safe to run on top of an existing DB that already has these tables from the earlier migration. + */ +export async function up(queryInterface: QueryInterface): Promise { + await queryInterface.sequelize.query(` + CREATE TABLE IF NOT EXISTS cpc_documents ( + id UUID NOT NULL PRIMARY KEY, + booking_id VARCHAR(255), + claim_id VARCHAR(255), + attempt_no INTEGER NOT NULL DEFAULT 1, + document_type VARCHAR(255), + document_gcp_url TEXT, + provider VARCHAR(255), + msd_payload JSONB, + extracted_fields JSONB, + field_confidence JSONB, + validation_status VARCHAR(255), + match_percentage DOUBLE PRECISION, + mismatch_reasons JSONB, + field_results JSONB, + ip_address VARCHAR(255), + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() + ); + `); + + await queryInterface.sequelize.query(` + CREATE TABLE IF NOT EXISTS cpc_audit_logs ( + id UUID NOT NULL PRIMARY KEY, + document_id UUID NOT NULL REFERENCES cpc_documents(id) ON DELETE CASCADE, + action VARCHAR(255) NOT NULL, + previous_state JSONB, + new_state JSONB, + performed_by VARCHAR(255), + remarks TEXT, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() + ); + `); + + await queryInterface.sequelize.query(` + CREATE UNIQUE INDEX IF NOT EXISTS unique_cpc_document_attempt + ON cpc_documents (claim_id, attempt_no, booking_id); + `); +} + +export async function down(_queryInterface: QueryInterface): Promise { + // Non-destructive: tables may contain production CPC data. +} diff --git a/src/migrations/20260415120000-cpc-documents-unique-by-booking.ts b/src/migrations/20260415120000-cpc-documents-unique-by-booking.ts new file mode 100644 index 0000000..8ea8cc8 --- /dev/null +++ b/src/migrations/20260415120000-cpc-documents-unique-by-booking.ts @@ -0,0 +1,26 @@ +import { QueryInterface } from 'sequelize'; + +/** + * Batch upload can include multiple files of the same document_type in one attempt. + * Replace unique(claim_id, attempt_no, document_type) with unique(claim_id, attempt_no, booking_id) + * because booking_id is distinct per file (e.g. CLAIM-1, CLAIM-2, ...). + */ +export async function up(queryInterface: QueryInterface): Promise { + await queryInterface.sequelize.query(` + DROP INDEX IF EXISTS unique_cpc_document_attempt; + `); + await queryInterface.sequelize.query(` + CREATE UNIQUE INDEX IF NOT EXISTS unique_cpc_document_claim_attempt_booking + ON cpc_documents (claim_id, attempt_no, booking_id); + `); +} + +export async function down(queryInterface: QueryInterface): Promise { + await queryInterface.sequelize.query(` + DROP INDEX IF EXISTS unique_cpc_document_claim_attempt_booking; + `); + await queryInterface.sequelize.query(` + CREATE UNIQUE INDEX IF NOT EXISTS unique_cpc_document_attempt + ON cpc_documents (claim_id, attempt_no, document_type); + `); +} diff --git a/src/migrations/20260416120000-rename-cpc-cdc-admin-config-key.ts b/src/migrations/20260416120000-rename-cpc-cdc-admin-config-key.ts new file mode 100644 index 0000000..a9630a1 --- /dev/null +++ b/src/migrations/20260416120000-rename-cpc-cdc-admin-config-key.ts @@ -0,0 +1,26 @@ +import { QueryInterface } from 'sequelize'; + +/** + * Rename CPC admin viewer-list config key from CPC_CDC_* to CPC_CSD_* (display name aligned). + */ +export async function up(queryInterface: QueryInterface): Promise { + await queryInterface.sequelize.query(` + UPDATE admin_configurations + SET + config_key = 'CPC_CSD_ADMIN_CONFIG', + display_name = 'CPC-CSD Admin Config', + description = 'CPC-CSD module visibility settings' + WHERE config_key = 'CPC_CDC_ADMIN_CONFIG' + `); +} + +export async function down(queryInterface: QueryInterface): Promise { + await queryInterface.sequelize.query(` + UPDATE admin_configurations + SET + config_key = 'CPC_CDC_ADMIN_CONFIG', + display_name = 'CPC-CDC Admin Config', + description = 'CPC-CDC module visibility settings' + WHERE config_key = 'CPC_CSD_ADMIN_CONFIG' + `); +} diff --git a/src/models/CpcAuditLog.ts b/src/models/CpcAuditLog.ts new file mode 100644 index 0000000..1daf85e --- /dev/null +++ b/src/models/CpcAuditLog.ts @@ -0,0 +1,89 @@ +import { DataTypes, Model, Optional } from 'sequelize'; +import { sequelize } from '@config/database'; + +interface CpcAuditLogAttributes { + id: string; + documentId: string; + action: string; + previousState?: any; + newState?: any; + performedBy?: string; + remarks?: string; + createdAt?: Date; +} + +interface CpcAuditLogCreationAttributes extends Optional {} + +class CpcAuditLog extends Model implements CpcAuditLogAttributes { + public id!: string; + public documentId!: string; + public action!: string; + public previousState?: any; + public newState?: any; + public performedBy?: string; + public remarks?: string; + public createdAt!: Date; +} + +CpcAuditLog.init( + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + field: 'id' + }, + documentId: { + type: DataTypes.UUID, + allowNull: false, + field: 'document_id', + references: { + model: 'cpc_documents', + key: 'id' + } + }, + action: { + type: DataTypes.STRING(255), + allowNull: false + }, + previousState: { + type: DataTypes.JSONB, + allowNull: true, + field: 'previous_state' + }, + newState: { + type: DataTypes.JSONB, + allowNull: true, + field: 'new_state' + }, + performedBy: { + type: DataTypes.STRING(255), + allowNull: true, + field: 'performed_by' + }, + remarks: { + type: DataTypes.TEXT, + allowNull: true + }, + createdAt: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW, + field: 'created_at' + } + }, + { + sequelize, + modelName: 'CpcAuditLog', + tableName: 'cpc_audit_logs', + timestamps: false + } +); + +CpcAuditLog.belongsTo(sequelize.models.CpcDocument, { + foreignKey: 'documentId', + targetKey: 'id', + as: 'document' +}); + +export { CpcAuditLog }; + diff --git a/src/models/CpcDocument.ts b/src/models/CpcDocument.ts new file mode 100644 index 0000000..ab1085b --- /dev/null +++ b/src/models/CpcDocument.ts @@ -0,0 +1,143 @@ +import { DataTypes, Model, Optional } from 'sequelize'; +import { sequelize } from '@config/database'; + +interface CpcDocumentAttributes { + id: string; + bookingId?: string; + claimId?: string; + attemptNo?: number; + documentType?: string; + documentGcpUrl?: string; + provider?: string; + msdPayload?: any; + extractedFields?: any; + fieldConfidence?: any; + validationStatus?: string; + matchPercentage?: number; + mismatchReasons?: any; + fieldResults?: any; + ipAddress?: string; + createdAt?: Date; +} + +interface CpcDocumentCreationAttributes extends Optional {} + +class CpcDocument extends Model implements CpcDocumentAttributes { + public id!: string; + public bookingId?: string; + public claimId?: string; + public attemptNo?: number; + public documentType?: string; + public documentGcpUrl?: string; + public provider?: string; + public msdPayload?: any; + public extractedFields?: any; + public fieldConfidence?: any; + public validationStatus?: string; + public matchPercentage?: number; + public mismatchReasons?: any; + public fieldResults?: any; + public ipAddress?: string; + public createdAt!: Date; +} + +CpcDocument.init( + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + field: 'id' + }, + bookingId: { + type: DataTypes.STRING(255), + allowNull: true, + field: 'booking_id' + }, + claimId: { + type: DataTypes.STRING(255), + allowNull: true, + field: 'claim_id' + }, + attemptNo: { + type: DataTypes.INTEGER, + defaultValue: 1, + field: 'attempt_no' + }, + documentType: { + type: DataTypes.STRING(255), + allowNull: true, + field: 'document_type' + }, + documentGcpUrl: { + type: DataTypes.TEXT, + allowNull: true, + field: 'document_gcp_url' + }, + provider: { + type: DataTypes.STRING(255), + allowNull: true, + field: 'provider' + }, + msdPayload: { + type: DataTypes.JSONB, + allowNull: true, + field: 'msd_payload' + }, + extractedFields: { + type: DataTypes.JSONB, + allowNull: true, + field: 'extracted_fields' + }, + fieldConfidence: { + type: DataTypes.JSONB, + allowNull: true, + field: 'field_confidence' + }, + validationStatus: { + type: DataTypes.STRING(255), + allowNull: true, + field: 'validation_status' + }, + matchPercentage: { + type: DataTypes.FLOAT, + allowNull: true, + field: 'match_percentage' + }, + mismatchReasons: { + type: DataTypes.JSONB, + allowNull: true, + field: 'mismatch_reasons' + }, + fieldResults: { + type: DataTypes.JSONB, + allowNull: true, + field: 'field_results' + }, + ipAddress: { + type: DataTypes.STRING(255), + allowNull: true, + field: 'ip_address' + }, + createdAt: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW, + field: 'created_at' + } + }, + { + sequelize, + modelName: 'CpcDocument', + tableName: 'cpc_documents', + timestamps: false, + indexes: [ + { + name: 'unique_cpc_document_claim_attempt_booking', + unique: true, + fields: ['claimId', 'attemptNo', 'bookingId'] + } + ] + } +); + +export { CpcDocument }; diff --git a/src/models/index.ts b/src/models/index.ts index 853366c..e8bf4f4 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -42,6 +42,9 @@ import { Form16LedgerEntry } from './Form16LedgerEntry'; import { Form16SapResponse } from './Form16SapResponse'; import { Form16DebitNoteSapResponse } from './Form16DebitNoteSapResponse'; import { From16SapReadFile } from './From16SapReadFile'; +import { CpcDocument } from './CpcDocument'; +import { CpcAuditLog } from './CpcAuditLog'; + // Define associations const defineAssociations = () => { @@ -189,6 +192,13 @@ const defineAssociations = () => { // Note: belongsTo associations are defined in individual model files to avoid duplicate alias conflicts // Only hasMany associations from WorkflowRequest are defined here since they're one-way + + // CPC-CSD associations + CpcDocument.hasMany(CpcAuditLog, { + as: 'auditLogs', + foreignKey: 'documentId', + sourceKey: 'id' + }); }; // Initialize associations @@ -237,7 +247,9 @@ export { Form16LedgerEntry, Form16SapResponse, Form16DebitNoteSapResponse, - From16SapReadFile + From16SapReadFile, + CpcDocument, + CpcAuditLog }; // Export default sequelize instance diff --git a/src/routes/admin.routes.ts b/src/routes/admin.routes.ts index 7851457..213fa33 100644 --- a/src/routes/admin.routes.ts +++ b/src/routes/admin.routes.ts @@ -16,6 +16,7 @@ import { updateActivityTypeSchema, activityTypeParamsSchema, updateForm16ConfigSchema, + updateCpcCdcConfigSchema, } from '../validators/admin.validator'; import { getAllHolidays, @@ -29,6 +30,8 @@ import { resetConfiguration, getForm16Config, putForm16Config, + getCpcCdcConfig, + putCpcCdcConfig, updateUserRole, getUsersByRole, getRoleStatistics, @@ -139,6 +142,21 @@ router.get('/form16-config', getForm16Config); */ router.put('/form16-config', validateBody(updateForm16ConfigSchema), putForm16Config); +/** + * @route GET /api/admin/cpc-csd-config + * @desc Get CPC-CSD admin config (viewer emails) + * @access Admin + */ +router.get('/cpc-csd-config', getCpcCdcConfig); + +/** + * @route PUT /api/admin/cpc-csd-config + * @desc Update CPC-CSD admin config + * @body { viewerEmails? } + * @access Admin + */ +router.put('/cpc-csd-config', validateBody(updateCpcCdcConfigSchema), putCpcCdcConfig); + // ==================== User Role Management Routes (RBAC) ==================== /** diff --git a/src/routes/cpc-cdc.routes.ts b/src/routes/cpc-cdc.routes.ts new file mode 100644 index 0000000..383b7a2 --- /dev/null +++ b/src/routes/cpc-cdc.routes.ts @@ -0,0 +1,60 @@ +import { Router } from 'express'; +import multer from 'multer'; +import { cpcCdcController } from '../controllers/CpcCdcController'; +import { CpcReportController } from '../controllers/CpcReportController'; +import { asyncHandler } from '../middlewares/errorHandler.middleware'; +import { authenticateToken } from '../middlewares/auth.middleware'; +import { requireCpcCdcAccess } from '../middlewares/cpcPermission.middleware'; + +const router = Router(); +const cpcReportController = new CpcReportController(); + + +const disallowZipUpload: multer.Options['fileFilter'] = (_req, file, cb) => { + const lowerName = String(file.originalname || '').toLowerCase(); + const lowerMime = String(file.mimetype || '').toLowerCase(); + const isZip = lowerName.endsWith('.zip') || lowerMime.includes('zip'); + if (isZip) { + cb(new Error('ZIP files are not allowed for CPC-CSD validation')); + return; + } + cb(null, true); +}; + +// Configure Multer for memory storage (buffers needed for GCS/Gemini) +const upload = multer({ + storage: multer.memoryStorage(), + limits: { fileSize: 15 * 1024 * 1024 }, // 15MB limit + fileFilter: disallowZipUpload +}); + +// All CPC-CSD routes require authentication (mounted at /cpc-csd and legacy /cpc-cdc) +router.use(authenticateToken); +router.use(requireCpcCdcAccess); + +// OCR / Validation — mirror CPC-CSD: bulk uses `files[]`, single upload uses `file` +router.post('/v1/ocr/upload', upload.array('files', 20), asyncHandler(cpcCdcController.validateDocumentUpload.bind(cpcCdcController))); +router.post('/v1/ocr/validate-upload', upload.single('file'), asyncHandler(cpcCdcController.validateDocumentUpload.bind(cpcCdcController))); +router.post('/v1/ocr/validate', asyncHandler(cpcCdcController.validateDocumentByUrlStub.bind(cpcCdcController))); + + +// History and Documents (order aligned with CPC-CSD/server/src/routes/index.js) +router.get('/documents/analytics', asyncHandler(cpcCdcController.getAnalytics.bind(cpcCdcController))); +router.get('/documents/history', asyncHandler(cpcCdcController.getClaimHistory.bind(cpcCdcController))); +router.get('/documents/recent', asyncHandler(cpcCdcController.getRecentDocuments.bind(cpcCdcController))); +router.get('/documents/:id/file', asyncHandler(cpcCdcController.getDocumentFile.bind(cpcCdcController))); +router.get('/documents/:id', asyncHandler(cpcCdcController.getDocumentById.bind(cpcCdcController))); +router.put('/documents/:id/status', asyncHandler(cpcCdcController.updateDocumentStatus.bind(cpcCdcController))); +router.delete('/documents/:id', asyncHandler(cpcCdcController.deleteDocument.bind(cpcCdcController))); + +// Reports (Matching History.jsx exactly) +router.get('/v1/ocr/report/all/download', asyncHandler(cpcReportController.downloadAllReport.bind(cpcReportController))); +router.get('/v1/ocr/report/:claimId/download', asyncHandler(cpcReportController.downloadReport.bind(cpcReportController))); + +// Backwards compatibility or alternative paths +router.get('/report/all/download', asyncHandler(cpcReportController.downloadAllReport.bind(cpcReportController))); +router.get('/report/:claimId/download', asyncHandler(cpcReportController.downloadReport.bind(cpcReportController))); + +export default router; + + diff --git a/src/routes/cpc-csd-compat.mount.ts b/src/routes/cpc-csd-compat.mount.ts new file mode 100644 index 0000000..9576ec0 --- /dev/null +++ b/src/routes/cpc-csd-compat.mount.ts @@ -0,0 +1,89 @@ +import express from 'express'; +import multer from 'multer'; +import { authenticateToken } from '../middlewares/auth.middleware'; +import { asyncHandler } from '../middlewares/errorHandler.middleware'; +import { generalApiLimiter } from '../middlewares/rateLimiter.middleware'; +import { requireCpcCdcAccess } from '../middlewares/cpcPermission.middleware'; +import { cpcCdcController } from '../controllers/CpcCdcController'; +import { CpcReportController } from '../controllers/CpcReportController'; + +const memoryUpload = multer({ + storage: multer.memoryStorage(), + limits: { fileSize: 15 * 1024 * 1024 }, + fileFilter: (_req, file, cb) => { + const lowerName = String(file.originalname || '').toLowerCase(); + const lowerMime = String(file.mimetype || '').toLowerCase(); + const isZip = lowerName.endsWith('.zip') || lowerMime.includes('zip'); + if (isZip) { + cb(new Error('ZIP files are not allowed for CPC-CSD validation')); + return; + } + cb(null, true); + } +}); + +const cpcReportController = new CpcReportController(); + +const authLim = [authenticateToken, requireCpcCdcAccess, generalApiLimiter]; + +/** + * CPC-CSD (`CPC-CSD/server`) style URLs on re-workflow: + * - `POST /api/upload` + * - `GET /api/documents/...` (same order as legacy router) + * - `POST /api/v1/ocr/validate` | `validate-upload` | `upload` + * - `GET /api/v1/ocr/report/...` + * + * Disable with `CPC_LEGACY_COMPAT_ROUTES=false`. + */ +export function registerCpcCsdCompatRoutes(app: express.Application): void { + if (String(process.env.CPC_LEGACY_COMPAT_ROUTES || '').toLowerCase() === 'false') { + return; + } + + app.post( + '/api/upload', + ...authLim, + memoryUpload.single('file'), + asyncHandler(cpcCdcController.uploadBareFile.bind(cpcCdcController)) + ); + + const documentsRouter = express.Router(); + documentsRouter.use(...authLim); + documentsRouter.get('/analytics', asyncHandler(cpcCdcController.getAnalytics.bind(cpcCdcController))); + documentsRouter.get('/history', asyncHandler(cpcCdcController.getClaimHistory.bind(cpcCdcController))); + documentsRouter.get('/recent', asyncHandler(cpcCdcController.getRecentDocuments.bind(cpcCdcController))); + documentsRouter.get('/:id/file', asyncHandler(cpcCdcController.getDocumentFile.bind(cpcCdcController))); + documentsRouter.get('/:id', asyncHandler(cpcCdcController.getDocumentById.bind(cpcCdcController))); + documentsRouter.put('/:id/status', asyncHandler(cpcCdcController.updateDocumentStatus.bind(cpcCdcController))); + documentsRouter.delete('/:id', asyncHandler(cpcCdcController.deleteDocument.bind(cpcCdcController))); + app.use('/api/documents', documentsRouter); + + app.post( + '/api/v1/ocr/validate', + ...authLim, + asyncHandler(cpcCdcController.validateDocumentByUrlStub.bind(cpcCdcController)) + ); + app.post( + '/api/v1/ocr/validate-upload', + ...authLim, + memoryUpload.single('file'), + asyncHandler(cpcCdcController.validateDocumentUpload.bind(cpcCdcController)) + ); + app.post( + '/api/v1/ocr/upload', + ...authLim, + memoryUpload.array('files', 20), + asyncHandler(cpcCdcController.validateDocumentUpload.bind(cpcCdcController)) + ); + + app.get( + '/api/v1/ocr/report/all/download', + ...authLim, + asyncHandler(cpcReportController.downloadAllReport.bind(cpcReportController)) + ); + app.get( + '/api/v1/ocr/report/:claimId/download', + ...authLim, + asyncHandler(cpcReportController.downloadReport.bind(cpcReportController)) + ); +} diff --git a/src/routes/cpc-permission.routes.ts b/src/routes/cpc-permission.routes.ts new file mode 100644 index 0000000..bda973c --- /dev/null +++ b/src/routes/cpc-permission.routes.ts @@ -0,0 +1,16 @@ +import { Router } from 'express'; +import { authenticateToken } from '../middlewares/auth.middleware'; +import { asyncHandler } from '../middlewares/errorHandler.middleware'; +import { cpcPermissionController } from '../controllers/cpcPermission.controller'; + +const router = Router(); + +router.use(authenticateToken); + +router.get( + '/permissions', + asyncHandler(cpcPermissionController.getPermissions.bind(cpcPermissionController)) +); + +export default router; + diff --git a/src/routes/index.ts b/src/routes/index.ts index 511e15c..b3f439d 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -35,6 +35,9 @@ import antivirusRoutes from './antivirus.routes'; import dealerExternalRoutes from './dealerExternal.routes'; import form16Routes from './form16.routes'; import hsnSacCodeRoutes from './hsnSacCode.routes'; +import cpcCdcRoutes from './cpc-cdc.routes'; +import cpcPermissionRoutes from './cpc-permission.routes'; + const router = Router(); @@ -101,6 +104,10 @@ router.use('/dealers-external', generalApiLimiter, dealerExternalRoutes); // 200 router.use('/form16', uploadLimiter, form16Routes); // 50 req/15min (file uploads: extract, submissions, 26as) router.use('/api-tokens', authLimiter, apiTokenRoutes); // 20 req/15min (sensitive — same as auth) router.use('/hsn-sac', generalApiLimiter, hsnSacCodeRoutes); // 200 req/15min +router.use('/cpc-csd', generalApiLimiter, cpcPermissionRoutes); // 200 req/15min (canonical) +router.use('/cpc-csd', generalApiLimiter, cpcCdcRoutes); + + export default router; diff --git a/src/scripts/migrate-cpc-csd-to-cpc-tables.ts b/src/scripts/migrate-cpc-csd-to-cpc-tables.ts new file mode 100644 index 0000000..a4b12c6 --- /dev/null +++ b/src/scripts/migrate-cpc-csd-to-cpc-tables.ts @@ -0,0 +1,163 @@ +/** + * One-off migration: CPC-CSD Prisma tables "Document" and "AuditLog" → + * re-workflow tables cpc_documents and cpc_audit_logs. + * + * Usage: + * DATABASE_URL=postgres://... npm run migrate:cpc-csd + * + * Optional CPC_CSD_DATABASE_URL: when set, rows are read from that database + * and written to DATABASE_URL. When unset, both use DATABASE_URL (same DB; + * Prisma legacy tables must still exist alongside cpc_* tables). + */ +import 'dotenv/config'; +import { Sequelize, QueryTypes } from 'sequelize'; +import { sequelize, CpcDocument, CpcAuditLog } from '../models'; + +type LegacyDoc = Record; +type LegacyLog = Record; + +async function openSource(): Promise<{ sequelize: Sequelize; close: () => Promise }> { + const url = process.env.CPC_CSD_DATABASE_URL?.trim(); + if (url) { + const s = new Sequelize(url, { + dialect: 'postgres', + logging: false + }); + return { + sequelize: s, + close: async () => { + await s.close(); + } + }; + } + return { + sequelize, + close: async () => {} + }; +} + +async function tableExists(client: Sequelize, tableName: string): Promise { + const rows = (await client.query( + `SELECT EXISTS ( + SELECT 1 FROM information_schema.tables + WHERE table_schema = 'public' AND table_name = :tableName + ) AS "exists"`, + { replacements: { tableName }, type: QueryTypes.SELECT } + )) as { exists: boolean }[]; + return Boolean(rows[0]?.exists); +} + +async function migrateDocuments(source: Sequelize): Promise { + if (!(await tableExists(source, 'Document'))) { + console.warn('[migrate-cpc-csd] Table "Document" not found on source; skipping documents.'); + return 0; + } + + const rows = (await source.query('SELECT * FROM "Document"', { + type: QueryTypes.SELECT + })) as LegacyDoc[]; + + let inserted = 0; + for (const r of rows) { + if (!r.id) continue; + const existing = await CpcDocument.findByPk(r.id); + if (existing) continue; + + try { + await CpcDocument.create({ + id: r.id, + bookingId: r.bookingId ?? null, + claimId: r.claimId ?? null, + attemptNo: r.attemptNo ?? 1, + documentType: r.documentType ?? null, + documentGcpUrl: r.documentGcpUrl ?? null, + provider: r.provider ?? null, + msdPayload: r.msdPayload ?? null, + extractedFields: r.extractedFields ?? null, + fieldConfidence: r.fieldConfidence ?? null, + validationStatus: r.validationStatus ?? null, + matchPercentage: r.matchPercentage ?? null, + mismatchReasons: r.mismatchReasons ?? null, + fieldResults: r.fieldResults ?? null, + ipAddress: r.ipAddress ?? null, + createdAt: r.createdAt ? new Date(r.createdAt) : new Date() + }); + inserted += 1; + } catch (err: any) { + console.error(`[migrate-cpc-csd] Skip document ${r.id}:`, err?.message || err); + } + } + + return inserted; +} + +async function migrateAuditLogs(source: Sequelize): Promise { + if (!(await tableExists(source, 'AuditLog'))) { + console.warn('[migrate-cpc-csd] Table "AuditLog" not found on source; skipping audit logs.'); + return 0; + } + + const rows = (await source.query('SELECT * FROM "AuditLog"', { + type: QueryTypes.SELECT + })) as LegacyLog[]; + + let inserted = 0; + for (const r of rows) { + if (!r.id || !r.documentId) continue; + const parent = await CpcDocument.findByPk(r.documentId); + if (!parent) { + console.warn(`[migrate-cpc-csd] Skip audit ${r.id}: parent document ${r.documentId} missing`); + continue; + } + const existingLog = await CpcAuditLog.findByPk(r.id); + if (existingLog) continue; + + try { + await CpcAuditLog.create({ + id: r.id, + documentId: r.documentId, + action: r.action, + previousState: r.previousState ?? null, + newState: r.newState ?? null, + performedBy: r.performedBy ?? null, + remarks: r.remarks ?? null, + createdAt: r.createdAt ? new Date(r.createdAt) : new Date() + }); + inserted += 1; + } catch (err: any) { + console.error(`[migrate-cpc-csd] Skip audit log ${r.id}:`, err?.message || err); + } + } + + return inserted; +} + +async function printCounts(): Promise { + const docTotal = await CpcDocument.count(); + const logTotal = await CpcAuditLog.count(); + console.log(`[migrate-cpc-csd] Target counts: cpc_documents=${docTotal}, cpc_audit_logs=${logTotal}`); +} + +async function main(): Promise { + const { sequelize: source, close } = await openSource(); + try { + await sequelize.authenticate(); + await source.authenticate(); + console.log('[migrate-cpc-csd] Connected to target (DATABASE_URL) and source.'); + + const docInserted = await migrateDocuments(source); + const logInserted = await migrateAuditLogs(source); + + console.log(`[migrate-cpc-csd] New cpc_documents rows: ${docInserted}`); + console.log(`[migrate-cpc-csd] New cpc_audit_logs rows: ${logInserted}`); + await printCounts(); + } finally { + await close(); + await sequelize.close(); + } +} + +main().catch((e) => { + console.error('[migrate-cpc-csd] Failed:', e); + process.exit(1); +}); diff --git a/src/scripts/migrate.ts b/src/scripts/migrate.ts index 5edb965..fb24f83 100644 --- a/src/scripts/migrate.ts +++ b/src/scripts/migrate.ts @@ -75,6 +75,10 @@ import * as m67 from '../migrations/20260324110001-add-pan-number-to-26as'; import * as m68 from '../migrations/20260325090001-ensure-pan-number-in-26as'; import * as m69 from '../migrations/20260325094500-add-user-session-and-hsn-sac-codes'; import * as m70 from '../migrations/20260325175000-update-credit-notes-and-add-items'; +import * as m71 from '../migrations/2026041300-create-cpc-cdc-tables'; +import * as m72 from '../migrations/20260414100000-ensure-cpc-cdc-tables-exist'; +import * as m73 from '../migrations/20260416120000-rename-cpc-cdc-admin-config-key'; + interface Migration { name: string; @@ -157,6 +161,9 @@ const migrations: Migration[] = [ { name: '20260325090001-ensure-pan-number-in-26as', module: m68 }, { name: '20260325094500-add-user-session-and-hsn-sac-codes', module: m69 }, { name: '20260325175000-update-credit-notes-and-add-items', module: m70 }, + { name: '2026041300-create-cpc-cdc-tables', module: m71 }, + { name: '20260414100000-ensure-cpc-cdc-tables-exist', module: m72 }, + { name: '20260416120000-rename-cpc-cdc-admin-config-key', module: m73 }, ]; /** diff --git a/src/server.ts b/src/server.ts index 22cdeaf..e0c4962 100644 --- a/src/server.ts +++ b/src/server.ts @@ -113,14 +113,7 @@ const startServer = async (): Promise => { console.error('⚠️ Activity type seeding error:', error); } - // Ensure demo admin user exists (admin@example.com / Admin@123) - const { ensureDemoAdminUser } = require('./scripts/seed-admin-user'); - try { - await ensureDemoAdminUser(); - } catch (error) { - console.warn('⚠️ Demo admin user setup warning:', error); - } - + // Initialize holidays cache for TAT calculations try { await initializeHolidaysCache(); diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 016f2c7..4b933a6 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -5,7 +5,6 @@ import type { StringValue } from 'ms'; import { LoginResponse } from '../types/auth.types'; import logger, { logAuthEvent } from '../utils/logger'; import axios from 'axios'; -import bcrypt from 'bcryptjs'; import { v4 as uuidv4 } from 'uuid'; import { emitToUser } from '../realtime/socket'; import { ACCESS_TOKEN_TTL_MS } from '../config/sessionPolicy'; @@ -616,174 +615,16 @@ export class AuthService { } /** - * Authenticate user with username (email) and password via Okta API - * This is for direct API authentication (e.g., Postman, mobile apps) - * - * Flow: - * 1. Authenticate with Okta using username/password - * 2. Get access token from Okta - * 3. Fetch user info from Okta - * 4. Create/update user in our database if needed - * 5. Return our JWT tokens + * Authenticate user with username (email) and password via Okta (Resource Owner Password grant). + * For direct API clients (e.g. Postman) when Okta allows this grant; otherwise use token-exchange. + * No local or demo password bypass. */ async authenticateWithPassword(username: string, password: string, userAgent?: string): Promise { - // Demo admin: admin@example.com / Admin@123 (works with or without .env; for dev/demo only) - const DEMO_ADMIN_EMAIL = 'admin@example.com'; - const DEFAULT_DEMO_ADMIN_HASH = '$2a$10$H4ikTC.HDZPM0iFxjBy2C./WlkbGbidipIiZlXIJx6QpcBazdf12K'; // bcrypt of "Admin@123" - const tryLocalAdminLogin = async (): Promise => { - const normalizedInput = username?.trim?.()?.toLowerCase?.() ?? ''; - const adminEmail = process.env.LOCAL_ADMIN_EMAIL?.trim() || DEMO_ADMIN_EMAIL; - if (normalizedInput !== adminEmail.toLowerCase()) return null; - const hash = process.env.LOCAL_ADMIN_PASSWORD_HASH?.trim() || DEFAULT_DEMO_ADMIN_HASH; - const passwordMatch = await bcrypt.compare(password, hash); - if (!passwordMatch) return null; - let user = await User.findOne({ where: { email: adminEmail } }); - const sessionToken = uuidv4(); - const lastLoginDevice = parseDeviceFromUserAgent(userAgent); - - if (!user) { - user = await User.create({ - email: adminEmail, - oktaSub: 'local-ADMIN', - displayName: 'RE Admin', - firstName: 'RE', - lastName: 'Admin', - isActive: true, - role: 'ADMIN', - emailNotificationsEnabled: true, - pushNotificationsEnabled: true, - inAppNotificationsEnabled: true, - sessionToken, - lastLoginDevice, - lastLogin: new Date() - }); - logger.info('Demo admin user created on first login', { email: adminEmail }); - } else { - await user.update({ lastLogin: new Date(), sessionToken, lastLoginDevice }); - } - logger.info('Demo admin login successful', { email: adminEmail }); - const accessToken = this.generateAccessToken(user); - const refreshToken = this.generateRefreshToken(user); - return { - user: { - userId: user.userId, - employeeId: user.employeeId ?? null, - email: user.email, - firstName: user.firstName ?? null, - lastName: user.lastName ?? null, - displayName: user.displayName ?? null, - department: user.department ?? null, - designation: user.designation ?? null, - jobTitle: user.jobTitle ?? null, - role: user.role, - }, - accessToken, - refreshToken, - }; - }; - - // Helper: try local dealer login (TESTREFLOW) when ENABLE_LOCAL_DEALER_LOGIN is set (in scope for try and catch) - const tryLocalDealerLogin = async (): Promise => { - const enabled = process.env.ENABLE_LOCAL_DEALER_LOGIN?.toLowerCase()?.trim() === 'true'; - const hash = process.env.LOCAL_DEALER_PASSWORD_HASH?.trim(); - const localUsername = 'TESTREFLOW'; - const normalizedUsername = username?.trim?.()?.toUpperCase?.() ?? ''; - if (!enabled || !hash || normalizedUsername !== localUsername) return null; - const passwordMatch = await bcrypt.compare(password, hash); - if (!passwordMatch) return null; - logger.info('Local dealer login successful', { username: localUsername }); - return this.handleSSOCallback({ - oktaSub: 'local-TESTREFLOW', - email: 'testreflow@example.com', - displayName: 'Test Reflow Dealer', - firstName: 'Test', - lastName: 'Reflow', - }, userAgent); - }; - - // Fallback bcrypt hash for "Test@123" when .env hash is corrupted (dev only) - const ROHIT_DEALER_EMAIL = 'rohitm_ext@royalenfield.com'; - const FALLBACK_HASH_TEST123 = '$2a$10$gQ34/Jt9rOFDBWJqVur2W.ZWlN0vqAzt2I/6HKBKOtggowY/R8W/C'; - - // Helper: try local login by email (e.g. rohitm_ext@royalenfield.com) when LOCAL_DEALER_2_* is set or known dealer - const tryLocalDealerLoginByEmail = async (): Promise => { - const envEmail = process.env.LOCAL_DEALER_2_EMAIL?.trim()?.toLowerCase(); - const rawHash = process.env.LOCAL_DEALER_2_PASSWORD_HASH; - let hash = (typeof rawHash === 'string' ? rawHash.trim() : '') || ''; - if (hash.length >= 2 && ((hash.startsWith('"') && hash.endsWith('"')) || (hash.startsWith("'") && hash.endsWith("'")))) hash = hash.slice(1, -1); - const normalizedInput = username?.trim?.()?.toLowerCase?.() ?? ''; - const isRohitEmail = normalizedInput === ROHIT_DEALER_EMAIL; - const email = envEmail || (isRohitEmail ? ROHIT_DEALER_EMAIL : null); - const inputMatches = !!email && normalizedInput === email; - if (!inputMatches) { - logger.info('[Auth] Local dealer by email skip', { - hasEmail: !!envEmail, - hasHash: !!hash, - hashLen: hash.length, - inputMatch: inputMatches, - normalizedInput: normalizedInput ? `${normalizedInput.slice(0, 5)}...` : '', - }); - return null; - } - let passwordMatch = false; - if (hash.length >= 50) { - passwordMatch = await bcrypt.compare(password, hash); - } - if (!passwordMatch && isRohitEmail) { - passwordMatch = await bcrypt.compare(password, FALLBACK_HASH_TEST123); - if (passwordMatch) logger.info('[Auth] Local dealer login used fallback hash for', { email: ROHIT_DEALER_EMAIL }); - } - if (!passwordMatch) { - logger.warn('[Auth] Local dealer by email: password mismatch', { email }); - return null; - } - const { Op } = await import('sequelize'); - const user = await User.findOne({ where: { email: { [Op.iLike]: email } } }); - if (!user) { - logger.warn('Local dealer login by email: user not found', { email }); - return null; - } - const sessionToken = uuidv4(); - const lastLoginDevice = parseDeviceFromUserAgent(userAgent); - await user.update({ lastLogin: new Date(), sessionToken, lastLoginDevice }); - logger.info('Local dealer login by email successful', { email }); - const accessToken = this.generateAccessToken(user); - const refreshToken = this.generateRefreshToken(user); - return { - user: { - userId: user.userId, - employeeId: user.employeeId ?? null, - email: user.email, - firstName: user.firstName ?? null, - lastName: user.lastName ?? null, - displayName: user.displayName ?? null, - department: user.department ?? null, - designation: user.designation ?? null, - jobTitle: user.jobTitle ?? null, - role: user.role, - }, - accessToken, - refreshToken, - }; - }; - try { logger.info('Authenticating user with username/password', { username }); - // Demo admin (admin@example.com / Admin@123) and optional env-based local admin - const adminResult = await tryLocalAdminLogin(); - if (adminResult) return adminResult; - - // Development-only: try local dealer login when enabled - const localResult = await tryLocalDealerLogin(); - if (localResult) return localResult; - - // Optional: local login by email (e.g. rohit.m.ext@royalenfield.com) when LOCAL_DEALER_2_* set - const localEmailResult = await tryLocalDealerLoginByEmail(); - if (localEmailResult) return localEmailResult; - - // Step 1: Authenticate with Okta using Resource Owner Password flow - // Note: This requires Okta to have Resource Owner Password grant type enabled + // Authenticate with Okta using Resource Owner Password flow + // Requires Okta Resource Owner Password grant when used; otherwise use SSO / token-exchange. const tokenEndpoint = `${ssoConfig.oktaDomain}/oauth2/default/v1/token`; const tokenResponse = await axios.post( @@ -894,21 +735,6 @@ export class AuthService { oktaError: error.response?.data, }); - // When Okta does not allow password grant (e.g. only authorization_code), fall back to local logins - const msg = (error.message || '').toLowerCase(); - if (msg.includes('grant type') || msg.includes('not authorized to use the provided grant type')) { - const adminFallback = await tryLocalAdminLogin(); - if (adminFallback) { - logger.info('Local admin login used after Okta grant-type rejection'); - return adminFallback; - } - const localResult = await tryLocalDealerLogin(); - if (localResult) { - logger.info('Local dealer login used after Okta grant-type rejection'); - return localResult; - } - } - if (error.response?.data) { const errorData = error.response.data; if (typeof errorData === 'object' && !Array.isArray(errorData)) { diff --git a/src/services/cpc-cdc/CpcGcsService.ts b/src/services/cpc-cdc/CpcGcsService.ts new file mode 100644 index 0000000..4c34a6f --- /dev/null +++ b/src/services/cpc-cdc/CpcGcsService.ts @@ -0,0 +1,71 @@ +import { Storage } from "@google-cloud/storage"; +import path from 'path'; +import logger from "@utils/logger"; + +/** Optional layout for CPC/CSD objects (mirrors local `uploads/cpc-csd-files/...`). */ +export type CpcGcsUploadOptions = { + bucket?: string; + /** Directory prefix inside the bucket, no leading slash, e.g. `cpc-csd/csd/BOOK-1/documents` */ + objectDir?: string; + /** Final filename segment only (no path separators) */ + objectBaseName?: string; +}; + +class CpcGcsService { + private storage: Storage; + private bucketName: string; + + constructor() { + this.storage = new Storage({ + projectId: process.env.GCP_PROJECT_ID, + keyFilename: process.env.GCP_KEY_FILE + }); + this.bucketName = process.env.GCP_BUCKET_NAME || ''; + } + + parseGsUrl(gsUrl: string) { + if (!gsUrl || !gsUrl.startsWith("gs://")) throw new Error("INVALID_DOCUMENT_URL"); + const s = gsUrl.slice(5); + const [bucket, ...rest] = s.split("/"); + const objectPath = rest.join("/"); + if (!bucket || !objectPath) throw new Error("INVALID_DOCUMENT_URL"); + return { bucket, objectPath }; + } + + async downloadFromGcs(gsUrl: string): Promise { + const { bucket, objectPath } = this.parseGsUrl(gsUrl); + const [buf] = await this.storage.bucket(bucket).file(objectPath).download(); + return buf; + } + + /** + * Third argument can be a legacy custom bucket string, or structured options for path layout. + */ + async uploadToGcs( + fileBuffer: Buffer, + originalName: string, + legacyBucketOrOpts?: string | CpcGcsUploadOptions + ): Promise { + const opts: CpcGcsUploadOptions = + typeof legacyBucketOrOpts === 'string' ? { bucket: legacyBucketOrOpts } : legacyBucketOrOpts || {}; + const targetBucket = opts.bucket || this.bucketName; + const base = + opts.objectBaseName && !opts.objectBaseName.includes('/') && !opts.objectBaseName.includes('..') + ? opts.objectBaseName + : `${Date.now()}-${path.basename(originalName)}`; + const dir = + opts.objectDir && !opts.objectDir.includes('..') + ? opts.objectDir.replace(/^\/+|\/+$/g, '') + : 'cpc-csd/uploads'; + const fileName = `${dir}/${base}`.replace(/\\/g, '/'); + const bucket = this.storage.bucket(targetBucket); + const file = bucket.file(fileName); + + await file.save(fileBuffer); + + logger.info(`[CpcGcsService] File uploaded to gs://${targetBucket}/${fileName}`); + return `gs://${targetBucket}/${fileName}`; + } +} + +export const cpcGcsService = new CpcGcsService(); diff --git a/src/services/cpc-cdc/CpcHistoryService.ts b/src/services/cpc-cdc/CpcHistoryService.ts new file mode 100644 index 0000000..055c9be --- /dev/null +++ b/src/services/cpc-cdc/CpcHistoryService.ts @@ -0,0 +1,301 @@ +/** + * Utility to map OCR document data to the "Excel Screenshot Summary" format + * and ensure uniform detail field results for the CPC-CSD module. + */ + +const FIELD_DEFAULTS: any = { + AADHAAR: ['customer_name', 'aadhar_number', 'name', 'dob', 'gender', 'address'], + ADHAAR: ['customer_name', 'aadhar_number', 'name', 'dob', 'gender', 'address'], + CSD_PO: ['customer_name', 'po_number', 'po_amount', 'signature_and_stamp'], + GENERIC_INVOICE: ['customer_name', 'order_or_auth_number', 'invoice_value', 'invoice_date', 'tax_amount'], + RETAIL_INVOICE: ['customer_name', 'order_or_auth_number', 'invoice_value', 'invoice_date', 'tax_amount'], + INVOICE: ['customer_name', 'order_or_auth_number', 'invoice_value', 'invoice_date', 'tax_amount'], + AUTHORITY_LETTER: [ + 'customer_name', + 'letter_number', + 'letter_amount', + 'signature_and_stamp', + 'authorized_person_name', + 'order_or_authorisation_number', + 'invoice_value', + 'govt_signatory_and_stamp_present', + 'authority_grantor_name', + 'valid_until', + 'purpose', + 'date_of_issue' + ], + AUTH_LETTER: [ + 'customer_name', + 'letter_number', + 'letter_amount', + 'signature_and_stamp', + 'authorized_person_name', + 'order_or_authorisation_number', + 'invoice_value', + 'govt_signatory_and_stamp_present', + 'authority_grantor_name', + 'valid_until', + 'purpose', + 'date_of_issue' + ] +}; + +const CRITERIA_MAP: any = { + // Fallbacks when doc-type–specific text is not applied (UI / reports only) + aadhaar_number: 'Exact match', + aadhar_number: 'Exact match', + name: 'Text match', + dob: 'Exact after normalization', + gender: 'Exact (M/Male normalize)', + address: 'Text match', + + customer_name: 'Text match', + order_or_auth_number: 'Text match', + order_or_authorisation_number: 'Text match', + invoice_value: 'Amount comparison', + invoice_date: 'Date comparison', + tax_amount: 'Amount comparison', + + authorized_person_name: 'Text match', + authority_grantor_name: 'Text match', + letter_number: 'Text match', + valid_until: 'Exact date match', + purpose: 'Text match', + date_of_issue: 'Exact match', + + mail_extraction: 'Email on document vs expected', + + stamp: 'Signature / stamp vs expected', + signatory: 'Signature / stamp vs expected', + govt_signatory_and_stamp_present: 'Signature / stamp vs expected', + stamp_sign_present: 'Signature / stamp vs expected', + signature_and_stamp: 'Signature / stamp vs expected', + po_number: 'Exact match', + po_amount: 'Amount comparison', + letter_amount: 'Amount comparison' +}; + +/** Normalize document type for criteria copy (matches validation service naming). */ +function normalizeCriteriaDocType(docType?: string): string { + const u = String(docType || '').toUpperCase().trim(); + if (u.includes('AADHAAR') || u === 'ADHAAR') return 'AADHAAR'; + if (u.includes('CPC_AUTH') || u.includes('AUTHORITY')) return 'CPC_AUTH'; + if (u.includes('CSD_PO') || u.includes('PURCHASE') || (u.includes('PO') && u.includes('CSD'))) return 'CSD_PO'; + if (u.includes('RETAIL') || u.includes('INVOICE')) return 'RETAIL_INVOICE'; + return u; +} + +/** Human-readable accuracy criteria for reports / API field_results (shared with validation). */ +export function getCriteriaLabel(field: string, docType?: string): string { + if (!field) return 'Exact check'; + const f = field.toLowerCase(); + const dt = normalizeCriteriaDocType(docType); + + if ((f === 'order_or_authorisation_number' || f === 'po_number') && dt === 'CSD_PO') { + return 'Exact match'; + } + if (f === 'letter_number' && dt === 'CPC_AUTH') { + return 'Text match'; + } + if (f === 'aadhaar_number' || f === 'aadhar_number') { + return 'Exact match'; + } + if ((f === 'customer_name' || f === 'name' || f === 'authorized_person_name') && (dt === 'CSD_PO' || dt === 'CPC_AUTH' || dt === 'AADHAAR')) { + return 'Text match'; + } + if ((f === 'invoice_value' || f === 'po_amount') && dt === 'CSD_PO') { + return 'Amount comparison'; + } + if ((f === 'invoice_value' || f === 'letter_amount') && dt === 'CPC_AUTH') { + return 'Amount comparison'; + } + if (f === 'govt_signatory_and_stamp_present' || f === 'stamp_sign_present' || f === 'signature_and_stamp') { + return 'Signature / stamp vs expected'; + } + if (f === 'mail_extraction') { + return 'Email on document vs expected'; + } + + const key = Object.keys(CRITERIA_MAP).find((k: string) => f.includes(k.toLowerCase())); + return CRITERIA_MAP[key || ''] || 'Exact check'; +} + +/** Normalize match % from persisted validation row (snake or camel). */ +function matchPctFromResult(found: Record | null | undefined): number | null { + if (!found || typeof found !== 'object') return null; + const row = found as { match_percentage?: unknown; matchPercentage?: unknown }; + const v = row.match_percentage != null ? row.match_percentage : row.matchPercentage; + if (v == null || v === '') return null; + const n = Number(v); + if (!Number.isFinite(n)) return null; + return Math.round(n); +} + +export class CpcHistoryService { + /** + * Transforms a document into a detailed field result array. + * Ensures that if a field was expected but not extracted, it still shows up as a fail. + */ + static getDetailedFieldResults(doc: any) { + const rawDocTypeUpper = String(doc.documentType || doc.document_type || '').trim(); + const rawType = rawDocTypeUpper.toLowerCase().replace(/_/g, ' '); + + // Normalize type for internal lookup + let type = 'UNKNOWN'; + if (rawType.includes('aadhaar') || rawType.includes('adhaar')) type = 'AADHAAR'; + else if (rawType.includes('authority') || rawType.includes('auth') || rawType.includes('cpc letter')) type = 'AUTHORITY_LETTER'; + else if (rawType.includes('invoice')) type = 'GENERIC_INVOICE'; + else if (rawType.includes('purchase order') || rawType.includes('csd_po') || rawType.includes('po')) + type = 'CSD_PO'; + + const hardcodedKeys = FIELD_DEFAULTS[type] || []; + + // Read expected/extracted values from ALL possible variant keys + const expectedObj = doc.msdPayload || doc.msd_payload || {}; + const extractedObj = doc.extractedFields || doc.extracted_fields || {}; + + const payloadKeys = Object.keys(expectedObj); + const expectedKeys = payloadKeys.length > 0 ? payloadKeys : hardcodedKeys; + + const rawFr = doc.fieldResults ?? doc.field_results; + const existingResults = Array.isArray(rawFr) ? rawFr : []; + + const finalResults = expectedKeys.map((key: string) => { + const found = existingResults.find((r: any) => r.field?.toLowerCase() === key.toLowerCase()); + + const msdVal = expectedObj[key] || (found ? found.expected : '-'); + const ocrVal = extractedObj[key] || (found ? (found.extracted || found.actual) : '-'); + + if (found) { + const mp = matchPctFromResult(found); + const mpNum = mp != null ? mp : 0; + const st = String((found as { status?: string }).status || ''); + const pass = + (found as { pass?: boolean }).pass === true || + st === 'SUCCESSFUL' || + st === 'MATCH'; + return { + ...found, + field: key, + expected: String(msdVal), + extracted: String(ocrVal), + status: st || (found as { status?: string }).status, + match_percentage: mpNum, + matchPercentage: mpNum, + accuracy: + (found as { accuracy?: string }).accuracy || + (mp != null ? `${mp}%` : `${mpNum}%`), + criteria: (found as { criteria?: string }).criteria || getCriteriaLabel(key, rawDocTypeUpper), + pass, + message: + (found as { reason?: string }).reason || + (found as { message?: string }).message || + (pass ? 'Matched' : 'Mismatch detected') + }; + } + return { + field: key, + expected: String(msdVal), + extracted: String(ocrVal), + match_percentage: 0, + matchPercentage: 0, + accuracy: '0%', + criteria: getCriteriaLabel(key, rawDocTypeUpper), + pass: false, + status: 'MISSING', + message: 'Not found' + }; + }); + + return finalResults; + } + + /** + * Generates the "Excel Screenshot Summary" row for a document. + */ + static getSummaryRow(doc: any, idx: number) { + const rawType = String(doc.documentType || doc.document_type || '') + .toLowerCase() + .trim() + .replace(/_/g, ' '); + const results = this.getDetailedFieldResults(doc); + const findRes = (key: string) => results.find((r: any) => r.field.toLowerCase() === key.toLowerCase()) || null; + + + const booking_type = doc.claimId?.startsWith('CPC') ? 'CPC' : 'CSD'; + const booking_number = doc.bookingId || doc.claimId || 'N/A'; + + const mapGroup = (fieldKey: string, altKeys: string[] = []) => { + const res = findRes(fieldKey) || (altKeys.length > 0 ? results.find((r: any) => altKeys.some(ak => r.field.toLowerCase() === ak.toLowerCase())) : null); + + if (!res) return { msd: 'N.A.', ocr: 'N.A.', accuracy_pct: 'N.A.', criteria: 'N.A.', is_match: 'N.A.', isNA: true }; + return { + msd: res.expected, + ocr: res.extracted, + accuracy_pct: res.accuracy, + criteria: res.criteria, + is_match: res.pass ? 'Yes' : 'No', + isNA: false + }; + }; + + let f1, f2, f3, f4, f5; + const na = { msd: 'N.A.', ocr: 'N.A.', accuracy_pct: 'N.A.', criteria: 'N.A.', is_match: 'N.A.', isNA: true }; + + if (rawType.includes('aadhaar') || rawType.includes('adhaar')) { + f1 = mapGroup('customer_name', ['name', 'authorized_person_name']); + f2 = na; + f3 = mapGroup('aadhar_number', ['aadhaar_number']); + f4 = na; + f5 = na; + } else if (rawType.includes('authority') || rawType.includes('auth') || rawType.includes('cpc letter')) { + f1 = mapGroup('customer_name', ['authorized_person_name', 'name']); + f2 = mapGroup('letter_number', [ + 'order_or_auth_number', + 'letter_no', + 'order_or_authorisation_number' + ]); + f3 = na; + f4 = mapGroup('letter_amount', ['invoice_value', 'amount']); + f5 = mapGroup('signature_and_stamp', ['govt_signatory_and_stamp_present', 'stamp', 'signatory', 'stamp_sign_present']); + } else if (rawType.includes('purchase order') || rawType.includes('csd_po') || rawType.includes('po')) { + f1 = mapGroup('customer_name'); + f2 = mapGroup('po_number', ['order_or_authorisation_number', 'order_or_auth_number']); + f3 = na; + f4 = mapGroup('po_amount', ['invoice_value', 'amount']); + f5 = mapGroup('signature_and_stamp', ['govt_signatory_and_stamp_present', 'stamp', 'signatory', 'stamp_sign_present']); + } else if (rawType.includes('invoice')) { + f1 = mapGroup('customer_name'); + f2 = mapGroup('order_or_auth_number', ['order_or_authorisation_number']); + f3 = na; + f4 = mapGroup('invoice_value', ['tax_amount']); + f5 = mapGroup('govt_signatory_and_stamp_present', ['stamp', 'signatory', 'stamp_sign_present']); + } else { + f1 = mapGroup('customer_name', ['name', 'authorized_person_name']); + f2 = mapGroup('order_or_auth_number', ['aadhaar_number', 'order_or_authorisation_number']); + f3 = na; + f4 = mapGroup('invoice_value', ['amount']); + f5 = mapGroup('govt_signatory_and_stamp_present', ['stamp', 'signatory', 'stamp_sign_present']); + } + + const vs = String(doc.validationStatus || '').toUpperCase(); + const final_validation = + vs === 'SUCCESSFUL' || vs === 'MATCH' || vs === 'APPROVED' ? 'Successful' : 'Unsuccessful'; + + return { + booking_type, + booking_number, + document_count: idx + 1, + document_name: rawType.toUpperCase(), + f1, f2, f3, f4, f5, + customer_name_group: f1, + po_or_auth_number_group: f2, + aadhaar_number_group: f3, + amount_group: f4, + stamp_group: f5, + field_results: results, + final_validation, + createdAt: doc.createdAt + }; + } +} diff --git a/src/services/cpc-cdc/CpcOcrService.ts b/src/services/cpc-cdc/CpcOcrService.ts new file mode 100644 index 0000000..ca0f967 --- /dev/null +++ b/src/services/cpc-cdc/CpcOcrService.ts @@ -0,0 +1,44 @@ +import { DocumentProcessorServiceClient } from "@google-cloud/documentai"; +import logger from "@utils/logger"; + +export class CpcOcrService { + private client: DocumentProcessorServiceClient; + + constructor() { + this.client = new DocumentProcessorServiceClient({ + keyFilename: process.env.GCP_KEY_FILE + }); + } + + async runDocAIOcr(params: { + projectId: string, + location: string, + processorId: string, + fileBuffer: Buffer, + mimeType?: string + }) { + const { projectId, location, processorId, fileBuffer, mimeType } = params; + const name = `projects/${projectId}/locations/${location}/processors/${processorId}`; + + logger.info(`[CpcOcrService] Running Document AI OCR for processor: ${processorId}`); + + const request = { + name, + rawDocument: { + content: fileBuffer.toString("base64"), + mimeType: mimeType || "application/pdf", + }, + }; + + try { + const [result] = await this.client.processDocument(request); + const text = result?.document?.text || ""; + return { text }; + } catch (error) { + logger.error(`[CpcOcrService] Document AI Error: ${error instanceof Error ? error.message : String(error)}`); + throw error; + } + } +} + +export const cpcOcrService = new CpcOcrService(); diff --git a/src/services/cpc-cdc/CpcRuleExtractService.ts b/src/services/cpc-cdc/CpcRuleExtractService.ts new file mode 100644 index 0000000..f3f90fb --- /dev/null +++ b/src/services/cpc-cdc/CpcRuleExtractService.ts @@ -0,0 +1,371 @@ +import { calculateMatch } from './utils'; + +export type RuleExtractHints = { + /** MSD fields typed in UI — used to find the same text inside the PDF (no "Name:" label needed). */ + msdPayload?: Record; + /** When `CSD_PO`, prefer buyer/beneficiary lines (Sold To, Bill To, …) over the first generic `Name:` (often supplier). */ + documentType?: string; +}; + +/** + * Regex-based extraction logic for CPC-CSD documents. + * Provides a lightweight alternative to Gemini for common patterns. + * Field names align with MSD payloads from the CPC dashboard (e.g. authority_letter). + */ +function escapeRegExp(s: string): string { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +/** If MSD name appears verbatim (spacing flexible) in PDF text, return the matched span. */ +function matchMsdNameInBody(body: string, expected: string): string | null { + const e = String(expected || '').trim(); + if (e.length < 2) return null; + const flex = escapeRegExp(e).replace(/\s+/g, '\\s+'); + const m = body.match(new RegExp(flex, 'i')); + return m ? m[0].replace(/\s+/g, ' ').trim() : null; +} + +/** + * Same word as MSD on a line with other text (table cells, "Customer Arjun …") — strict substring match often fails. + */ +function findMsdNameTokenInOcr(body: string, expected: string): string | null { + const h = String(expected || '').trim(); + if (h.length < 2 || !body.trim()) return null; + const hl = h.toLowerCase(); + const noise = /^(qty|ref|date|page|gst|hsn|po|no|id|by|to|of|in|at|sl|sr|index|desc|amount|total)$/i; + const lines = body.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0); + for (const line of lines) { + if (line.length > 160) continue; + if (line.toLowerCase() === hl) return line; + const parts = line.split(/[\s,;:|/<>()[\]]+/).filter(Boolean); + for (const raw of parts) { + const p = raw.replace(/^[^A-Za-z\u0900-\u097F0-9]+|[^A-Za-z\u0900-\u097F0-9]+$/g, ''); + if (!p || p.length < 2 || noise.test(p)) continue; + if (p.toLowerCase() === hl) return p; + } + } + return null; +} + +/** Pick a short line whose fuzzy score vs MSD is high (authority letters often put name on its own line). */ +function pickNameLineByMsd(body: string, expected: string, minScore = 52): string | null { + const exp = String(expected || '').trim(); + if (exp.length < 2 || !body.trim()) return null; + let best: { line: string; score: number } | null = null; + const lines = body.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 2 && l.length < 120); + for (const line of lines) { + if (/^(page|ref|no\.?|date|subject|to|from|dear|sir|madam|annex|schedule|authority|letter|royal|enfield|\d+\s*\/\s*\d+)/i.test(line)) { + continue; + } + const s = calculateMatch(exp, line, 'authorized_person_name'); + if (s >= minScore && (!best || s > best.score)) { + best = { line, score: s }; + } + } + return best?.line ?? null; +} + +function normalizePan(s: string): string | null { + const p = String(s || '') + .toUpperCase() + .replace(/\s/g, ''); + return /^[A-Z]{5}[0-9]{4}[A-Z]$/.test(p) ? p : null; +} + +/** If MSD PAN appears in PDF text, return canonical PAN (OCR may split with spaces). */ +function panFromMsdHint(body: string, msdPan: unknown): string | null { + const p = normalizePan(String(msdPan ?? '')); + if (!p || !body) return null; + const compact = body.toUpperCase().replace(/[\s-]/g, ''); + return compact.includes(p) ? p : null; +} + +/** If MSD amount digits appear in body, return normalized digit string for range match. */ +function invoiceDigitsFromMsdHint(body: string, msdAmt: unknown): string | null { + const d = String(msdAmt ?? '').replace(/[^\d.]/g, ''); + if (!d || d.length < 1) return null; + const intPart = d.split('.')[0]; + if (intPart.length >= 2 && body.replace(/[^\d]/g, '').includes(intPart)) { + return d; + } + return null; +} + +/** Supplier / letterhead lines — not the CSD customer individual name. */ +const RE_COMPANY_NAME_HINT = + /\b(LIMITED|LTD\.?|L\.?\s*L\.?\s*P\.?|PVT\.?\s*LTD|PRIVATE\s+LIMITED|PVT|PTE|INC\.?|CORP|CORPORATION|INDIA\s+LTD|MOTORS?|AUTOMOBILES?|DEALERS?|ENTERPRISES?|SALES\s*(?:&|AND)?\s*SERVICE|WORKS|AGENCIES)\b/i; + +function looksLikeCompanyLine(s: string): boolean { + const x = String(s || '').trim(); + if (!x) return false; + if (RE_COMPANY_NAME_HINT.test(x)) return true; + if (/^[A-Z0-9.&\s\-]{14,}$/.test(x) && !/\s{2,}/.test(x)) return true; + return false; +} + +function trimBuyerCapture(raw: string): string { + let s = String(raw || '').replace(/\r/g, '').trim(); + s = s.replace(/^[:\-–—\s]+/, ''); + const cut = s.split(/\b(?:GSTIN|PAN|Phone|Tel|Email|E-?mail|Mob|Mobile|Address|Qty|Quantity|Part)\b/i)[0]; + s = (cut ?? s).trim(); + return s.replace(/\s+/g, ' ').trim(); +} + +function isCsdPoHints(hints?: RuleExtractHints): boolean { + const dt = String(hints?.documentType || '').toUpperCase(); + return dt.includes('CSD_PO') || dt.includes('PURCHASE_ORDER'); +} + +/** Many CSD PO line-items print: 16-digit card/UIN then customer name then plot no / address (Description column). */ +const RE_VEHICLE_TOKENS = + /^(ROYAL|ENFIELD|METEOR|CLASSIC|BULLET|HIMALAYAN|INTERCEPTOR|CONTINENTAL|STELLAR|THUNDER|BS-?VI|BSVI|SUPER|VARIANT|MODEL|CC|HP|ABS|QTY|HSN)$/i; + +function isPlausibleHumanNameFromPoDescription(s: string): boolean { + const x = String(s || '') + .replace(/\s+/g, ' ') + .trim(); + if (x.length < 3 || x.length > 72) return false; + const parts = x.split(/\s+/).filter(Boolean); + if (parts.length < 1 || parts.length > 6) return false; + if (looksLikeCompanyLine(x)) return false; + for (const p of parts) { + if (RE_VEHICLE_TOKENS.test(p)) return false; + } + return parts.some((p) => /^[A-Za-z\u0900-\u097F]{2,}$/.test(p)); +} + +/** + * Pattern: `5312423002619089 KALAIYARASAN K 71` — 16 digits (optional spaces in groups of 4), + * then name tokens, then often a short plot/house number or newline/address. + */ +function extractCsdPoNameInDescriptionColumn(body: string): string | null { + const norm = body.replace(/\r\n/g, '\n').replace(/\u00a0/g, ' '); + const digitRes: RegExp[] = [/\b\d{4}\s+\d{4}\s+\d{4}\s+\d{4}\b/g, /\b\d{16}\b/g]; + const seenAt = new Set(); + + for (const re of digitRes) { + re.lastIndex = 0; + let dm: RegExpExecArray | null; + while ((dm = re.exec(norm)) !== null) { + const compact = dm[0].replace(/\s/g, ''); + if (compact.length !== 16 || !/^\d{16}$/.test(compact)) continue; + if (seenAt.has(dm.index)) continue; + seenAt.add(dm.index); + + const tail = norm.slice(dm.index + dm[0].length).replace(/^\s+/, ''); + let nm = tail.match( + /^([A-Za-z\u0900-\u097F]+(?:\s+[A-Za-z\u0900-\u097F]+){0,5})(?=\s+\d{1,4}\b|\s*\n|\s*$)/i + ); + if (!nm?.[1]) { + const loose = tail.match(/^([A-Za-z\u0900-\u097F]{2,25})\b/i); + if (loose?.[1] && isPlausibleHumanNameFromPoDescription(loose[1])) nm = loose; + } + if (!nm?.[1]) continue; + const candidate = nm[1].replace(/\s+/g, ' ').trim(); + if (isPlausibleHumanNameFromPoDescription(candidate)) { + return candidate; + } + } + } + return null; +} + +/** + * CSD / defence-style POs usually put the customer under Sold To / Bill To / card holder, + * not under the first "Name:" (often dealer contact). + */ +function extractCsdPoBuyerFromLabels(body: string): string | null { + const norm = body.replace(/\r\n/g, '\n'); + const patterns: RegExp[] = [ + /(?:^|\n)\s*Sold\s*To\s*[:\-]?\s*\n*\s*([^\n\r]{2,120})/i, + /(?:^|\n)\s*Bill\s*To\s*[:\-]?\s*\n*\s*([^\n\r]{2,120})/i, + /(?:^|\n)\s*Ship\s*To\s*[:\-]?\s*\n*\s*([^\n\r]{2,120})/i, + /(?:^|\n)\s*Consignee\s*[:\-]?\s*\n*\s*([^\n\r]{2,120})/i, + /(?:^|\n)\s*(?:Buyer|Purchaser)\s*[:\-]?\s*\n*\s*([^\n\r]{2,120})/i, + /(?:^|\n)\s*Customer\s*(?:Name|Details)?\s*[:\-]?\s*\n*\s*([^\n\r]{2,120})/i, + /(?:^|\n)\s*CSD\s*Card(?:\s*Holder)?\s*[:\-]?\s*\n*\s*([^\n\r]{2,120})/i, + /(?:^|\n)\s*Card\s*Holder(?:\s*Name)?\s*[:\-]?\s*\n*\s*([^\n\r]{2,120})/i, + /(?:^|\n)\s*Beneficiary\s*[:\-]?\s*\n*\s*([^\n\r]{2,120})/i, + /(?:^|\n)\s*Name\s*of\s*(?:the\s*)?(?:Purchaser|Buyer|Customer)\s*[:\-]?\s*\n*\s*([^\n\r]{2,120})/i, + /(?:^|\n)\s*(?:Ordered|Order)\s*(?:By|Placed\s*By)\s*[:\-]?\s*\n*\s*([^\n\r]{2,120})/i + ]; + for (const re of patterns) { + const m = norm.match(re); + if (!m?.[1]) continue; + const line = trimBuyerCapture(m[1]); + if (line.length < 2 || line.length > 100) continue; + if (/^(page|date|amount|total|ref|subject)\b/i.test(line)) continue; + return line; + } + return null; +} + +export class CpcRuleExtractService { + /** + * If Vertex returned a supplier-style string but OCR shows a clear buyer line, prefer the buyer line. + */ + static refineCsdPoCustomerName(ocrText: string, customerName: unknown): string | null { + const cur = String(customerName ?? '').trim(); + const text = String(ocrText || ''); + const fromDesc = extractCsdPoNameInDescriptionColumn(text); + const fromLabels = extractCsdPoBuyerFromLabels(text); + const buyer = fromDesc || fromLabels; + if (!buyer) return cur.length >= 2 ? cur : null; + if (!cur) return buyer; + if (looksLikeCompanyLine(cur) && !looksLikeCompanyLine(buyer)) return buyer; + return cur; + } + + static extractWithRules(ocrText: string, hints?: RuleExtractHints) { + const t = String(ocrText || ""); + const msd = hints?.msdPayload || {}; + const isCsdPo = isCsdPoHints(hints); + + // Matches 12 digit Aadhaar (with optional spaces) + const aadhaarMatch = t.match(/\b\d{4}\s?\d{4}\s?\d{4}\b/); + + // Matches currency patterns + const invoiceMatch = t.match(/(?:₹|Rs\.?|INR)\s?[\d,]+(?:\.\d{1,2})?/i); + + // Matches common order/auth patterns + const orderMatch = t.match(/\b(?:PO|ORDER|AUTH|AUTHORIZATION)\s*[:\-]?\s*([A-Z0-9\-\/]{4,})/i); + + // Matches "Name: [Value]" / "Authorised Person" / applicant-style labels + const nameMatch = t.match(/\bName\s*[:\-]\s*([A-Za-z][A-Za-z0-9\s.'-]{2,79})/i); + const authPersonMatch = t.match( + /\b(?:authorized|authorised)\s+person\s*[:\-]\s*([A-Za-z][A-Za-z0-9\s.'-]{2,79})/i + ); + const applicantMatch = t.match( + /\b(?:applicant|holder|customer|borrower|dealer)\s*[:\-]\s*([A-Za-z][A-Za-z0-9\s.'-]{2,79})/i + ); + let displayNameRaw = isCsdPo + ? extractCsdPoNameInDescriptionColumn(t) || extractCsdPoBuyerFromLabels(t) || '' + : ''; + if (!displayNameRaw) { + displayNameRaw = (authPersonMatch?.[1] || nameMatch?.[1] || applicantMatch?.[1] || '').trim(); + } + + // MSD-guided: name often appears in body exactly as user typed (no label) — same idea as manual compare in CPC-CSD UI flow + if (!displayNameRaw) { + const fromAuth = msd.customer_name ?? msd.authorized_person_name ?? msd.name; + const hint = String(fromAuth ?? '').trim(); + if (hint) { + const minFuzzy = hint.length <= 10 ? 40 : 52; + displayNameRaw = + matchMsdNameInBody(t, hint) || + findMsdNameTokenInOcr(t, hint) || + pickNameLineByMsd(t, hint, minFuzzy) || + ''; + } + } + + // Title / ALL CAPS line fallback — include short single names (e.g. "Arjun") skipped by older rules + if (!displayNameRaw) { + const lines = t.split(/\r?\n/).map((l) => l.trim()).filter(Boolean); + const noiseLine = /^(qty|ref|date|page|gst|hsn|po|no|id|total|amount|index|desc|sl)$/i; + for (const line of lines) { + if (line.length < 3 || line.length > 80) continue; + if (noiseLine.test(line)) continue; + if (/^(ref|date|subject|to|from|dear|page|annex|authority|letter|royal|enfield|cpc|csd)\b/i.test(line)) { + continue; + } + if (isCsdPo && looksLikeCompanyLine(line)) { + continue; + } + const words = line.split(/\s+/).filter(Boolean); + const singleName = + words.length === 1 && + /^[A-Za-z\u0900-\u097F]{2,25}$/.test(words[0]) && + !RE_VEHICLE_TOKENS.test(words[0]) && + !looksLikeCompanyLine(words[0]); + const multiAllCaps = + /^[A-Z][A-Z0-9\s.'-]{4,70}$/.test(line) && words.length >= 2; + if (singleName || multiAllCaps) { + displayNameRaw = line; + break; + } + const titleCaseName = + words.length >= 1 && + words.length <= 4 && + words.every((w) => /^[A-Za-z\u0900-\u097F]{2,}$/.test(w)) && + !words.some((w) => RE_VEHICLE_TOKENS.test(w)) && + line[0] === line[0].toUpperCase() && + /[a-z\u0900-\u097F]/.test(line) && + !looksLikeCompanyLine(line); + if (titleCaseName && line.length <= 48) { + displayNameRaw = line; + break; + } + } + } + + let displayName = displayNameRaw.length >= 2 ? displayNameRaw.replace(/\s+/g, ' ').trim() : null; + if (isCsdPo && displayName) { + displayName = CpcRuleExtractService.refineCsdPoCustomerName(t, displayName) ?? displayName; + } + + // PAN (Indian format) + MSD hint (PDF may lack strict word boundaries) + let panFromRegex = t.match(/\b([A-Z]{5}[0-9]{4}[A-Z])\b/i); + let panVal = panFromRegex ? String(panFromRegex[1]).toUpperCase() : null; + if (!panVal && msd.pan_number != null) { + panVal = panFromMsdHint(t, msd.pan_number); + } + + // Numeric amount for range matching against MSD invoice_value + const amountDigits = invoiceMatch + ? String(invoiceMatch[0]).replace(/[^\d.]/g, '').replace(/^\.+|\.+$/g, '') + : null; + let invoiceValueNormalized = + amountDigits && amountDigits.length ? amountDigits : null; + if (!invoiceValueNormalized) { + invoiceValueNormalized = + invoiceDigitsFromMsdHint(t, msd.po_amount) || + invoiceDigitsFromMsdHint(t, msd.letter_amount) || + invoiceDigitsFromMsdHint(t, msd.invoice_value); + } + + const stampPresent = /(stamp|seal|authorized signatory|signature)/i.test(t); + const govtStampPresent = /(govt\.?\s*stamp|government\s*seal|govt\.?\s*signatory|official\s*stamp|authorized\s*signatory)/i.test(t) || stampPresent; + const stampYesNo = govtStampPresent ? 'yes' : 'no'; + const poOrOrder = orderMatch ? orderMatch[1].trim() : null; + const aadhaarDigits = aadhaarMatch ? aadhaarMatch[0].replace(/\s/g, '').trim() : null; + + return { + extracted_fields: { + authorized_person_name: displayName, + customer_name: displayName, + pan_number: panVal, + order_or_authorisation_number: poOrOrder, + po_number: poOrOrder, + order_or_auth_number: poOrOrder, + invoice_value: invoiceValueNormalized, + po_amount: invoiceValueNormalized, + letter_amount: invoiceValueNormalized, + aadhaar_number: aadhaarDigits, + aadhar_number: aadhaarDigits, + stamp_or_signatory_present: stampPresent, + stamp_sign_present: stampPresent, + govt_signatory_and_stamp_present: stampYesNo, + signature_and_stamp: stampYesNo + }, + field_confidence: { + authorized_person_name: displayName ? 0.65 : 0.2, + customer_name: displayName ? 0.65 : 0.2, + pan_number: panVal ? 0.85 : 0.2, + order_or_authorisation_number: orderMatch ? 0.7 : 0.2, + po_number: orderMatch ? 0.7 : 0.2, + order_or_auth_number: orderMatch ? 0.7 : 0.2, + invoice_value: invoiceValueNormalized ? 0.7 : 0.2, + po_amount: invoiceValueNormalized ? 0.7 : 0.2, + letter_amount: invoiceValueNormalized ? 0.7 : 0.2, + aadhaar_number: aadhaarMatch ? 0.85 : 0.2, + aadhar_number: aadhaarMatch ? 0.85 : 0.2, + stamp_or_signatory_present: stampPresent ? 0.55 : 0.3, + stamp_sign_present: stampPresent ? 0.55 : 0.3, + govt_signatory_and_stamp_present: govtStampPresent ? 0.55 : 0.3, + signature_and_stamp: govtStampPresent ? 0.55 : 0.3 + } + }; + } +} + diff --git a/src/services/cpc-cdc/CpcValidationService.ts b/src/services/cpc-cdc/CpcValidationService.ts new file mode 100644 index 0000000..b22ddb9 --- /dev/null +++ b/src/services/cpc-cdc/CpcValidationService.ts @@ -0,0 +1,802 @@ +import fs from 'fs'; +import path from 'path'; +import { VertexAI } from '@google-cloud/vertexai'; +import { calculateMatch, digitsOnly, normalizeMoney } from './utils'; +import { getCriteriaLabel } from './CpcHistoryService'; +import logger from '@utils/logger'; + +/** Vertex SDK does not read `GCP_KEY_FILE` by itself — must pass keyFilename (critical in Docker). */ +function resolveVertexServiceAccountPath(): string | undefined { + const fromAdc = (process.env.GOOGLE_APPLICATION_CREDENTIALS || '').trim(); + const fromKeyFile = (process.env.GCP_KEY_FILE || '').trim(); + const candidates = [ + fromAdc, + fromKeyFile ? path.resolve(process.cwd(), fromKeyFile) : '' + ].filter(Boolean); + for (const p of candidates) { + try { + if (fs.existsSync(p)) return path.resolve(p); + } catch { + /* ignore */ + } + } + return undefined; +} + +/** + * Decide which printed script Gemini should prefer when the document shows the same field in English and Hindi. + * Driven by the user's MSD/form string (Devanagari vs Latin letter counts). + */ +function preferScriptForMsdFieldValue(value: unknown): 'Devanagari' | 'Latin' { + const s = String(value ?? '').trim(); + if (!s) return 'Latin'; + try { + const dev = (s.match(/\p{Script=Devanagari}/gu) || []).length; + const lat = (s.match(/\p{Script=Latin}/gu) || []).length; + if (dev === 0 && lat === 0) return 'Latin'; + return dev >= lat ? 'Devanagari' : 'Latin'; + } catch { + return /[\u0900-\u097F]/.test(s) ? 'Devanagari' : 'Latin'; + } +} + +/** JSON block appended to Vertex prompt: per-field prefer_script from MSD input language. */ +function buildMsdScriptPreferenceBlock( + expectedFields: string[], + msdReferencePayload?: Record +): string { + if (!msdReferencePayload || typeof msdReferencePayload !== 'object') return ''; + const uniq = [...new Set((expectedFields || []).map((f) => String(f || '').trim()).filter(Boolean))]; + const keys = + uniq.length > 0 + ? uniq + : Object.keys(msdReferencePayload).filter((k) => { + const v = msdReferencePayload[k]; + return v !== undefined && v !== null && String(v).trim() !== ''; + }); + if (keys.length === 0) return ''; + const hints: Record = {}; + for (const key of keys) { + const raw = msdReferencePayload[key]; + if (raw === undefined || raw === null) continue; + const str = String(raw).trim(); + if (!str) continue; + hints[key] = { prefer_script: preferScriptForMsdFieldValue(raw) }; + } + if (Object.keys(hints).length === 0) return ''; + return `MSD_SCRIPT_PREFERENCE (per field: infer input language from MSD; when the document shows the same field in both English and Hindi, extract ONLY the on-page text whose script matches prefer_script for that key — do not translate; do not swap languages):\n${JSON.stringify(hints, null, 2)}\n`; +} + +const VALID_DOC_TYPES = ['CSD_PO', 'CPC_AUTH', 'AADHAAR', 'RETAIL_INVOICE'] as const; + +/** + * Field rules aligned with RE / Softude mail (Feb–Apr 2026): + * - Rahul: CSD PO # 100% exact, amounts ±₹5, per-field all-pass (no average-based gate). + * - Rohit table: customer / order (where fuzzy) ≥95%, invoice ≥98% OR ±₹5, stamp ≥85% fuzzy, + * Aadhaar 12-digit 100%, retail invoice # ≥95%, document date ≥90%. + */ +const DOCUMENT_RULES: any = { + /** CPC claim doc 2 */ + 'AADHAAR': { + 'name': { threshold: 90, method: 'fuzzy' }, + 'customer_name': { threshold: 90, method: 'fuzzy' }, + 'aadhaar_number': { threshold: 100, method: 'exact_length_12' }, + 'aadhar_number': { threshold: 100, method: 'exact_length_12' }, + 'gender': { threshold: 100, method: 'exact' }, + 'mail_extraction': { threshold: 90, method: 'fuzzy' } + }, + /** CPC claim doc 1 — authorization letter */ + 'CPC_AUTH': { + 'authorized_person_name': { threshold: 90, method: 'fuzzy' }, + 'customer_name': { threshold: 90, method: 'fuzzy' }, + 'authority_grantor_name': { threshold: 90, method: 'fuzzy' }, + 'letter_number': { threshold: 90, method: 'fuzzy' }, + 'invoice_value': { threshold: null, method: 'range_5_or_fuzzy_98' }, + 'letter_amount': { threshold: null, method: 'range_5_or_fuzzy_98' }, + 'amount': { threshold: null, method: 'range_5_or_fuzzy_98' }, + 'pan_number': { threshold: 95, method: 'fuzzy' }, + 'order_or_authorisation_number': { threshold: 95, method: 'fuzzy' }, + 'stamp_sign_present': { threshold: 85, method: 'boolean_fuzzy_85' }, + 'govt_signatory_and_stamp_present': { threshold: 85, method: 'boolean_fuzzy_85' }, + 'signature_and_stamp': { threshold: 85, method: 'boolean_fuzzy_85' }, + 'mail_extraction': { threshold: 90, method: 'fuzzy' } + }, + /** CSD — Purchase order: PO# remains exact 100% per Rahul; other fuzzy thresholds per Rohit table. */ + 'CSD_PO': { + 'customer_name': { threshold: 90, method: 'fuzzy' }, + 'name': { threshold: 90, method: 'fuzzy' }, + 'order_or_authorisation_number': { threshold: 100, method: 'exact' }, + 'po_number': { threshold: 100, method: 'exact' }, + 'invoice_value': { threshold: null, method: 'range_5_or_fuzzy_98' }, + 'po_amount': { threshold: null, method: 'range_5_or_fuzzy_98' }, + 'vendor_name': { threshold: 95, method: 'fuzzy' }, + 'govt_signatory_and_stamp_present': { threshold: 85, method: 'boolean_fuzzy_85' }, + 'signature_and_stamp': { threshold: 85, method: 'boolean_fuzzy_85' }, + 'mail_extraction': { threshold: 90, method: 'fuzzy' } + }, + 'RETAIL_INVOICE': { + 'customer_name': { threshold: 95, method: 'fuzzy' }, + 'order_or_authorisation_number': { threshold: 95, method: 'fuzzy' }, + 'invoice_value': { threshold: null, method: 'range_5_or_fuzzy_98' }, + 'invoice_date': { threshold: 90, method: 'fuzzy' }, + 'vendor_name': { threshold: 95, method: 'fuzzy' }, + 'mail_extraction': { threshold: 90, method: 'fuzzy' } + }, + 'GENERIC': { + 'default': { threshold: 95, method: 'fuzzy' } + } +}; + +/** Human-readable `field_results.threshold` for API/UI (no percentage figures). */ +function apiThresholdLabel(rule: { method?: string; threshold?: number | null }): string { + const m = rule?.method; + if (m === 'range_5_or_fuzzy_98' || m === 'range_5') return 'Amount comparison'; + if (m === 'boolean_fuzzy_85' || m === 'boolean') return 'Stamp / signature'; + if (m === 'exact_length_12') return 'Aadhaar number'; + if (m === 'exact' || m === 'exact_numeric') return 'Exact match'; + if (m === 'fuzzy') return 'Text match'; + return 'N/A'; +} + +function msdFieldDisplayName(fieldKey: string, docType?: string): string { + if (fieldKey === 'invoice_value' || fieldKey === 'po_amount') { + if (docType === 'CSD_PO') return 'PO Amount'; + if (docType === 'CPC_AUTH') return 'Letter Amount'; + } + if (fieldKey === 'letter_amount') return 'Letter Amount'; + const map: Record = { + authorized_person_name: 'Customer Name', + customer_name: 'Customer Name', + name: 'Customer Name', + letter_number: 'Letter Number', + po_number: 'PO Number', + order_or_authorisation_number: 'PO Number', + invoice_value: 'Document Amount', + po_amount: 'PO Amount', + amount: 'Letter Amount', + aadhaar_number: 'Aadhaar Number', + aadhar_number: 'Aadhaar Number', + govt_signatory_and_stamp_present: 'Signature & Stamp', + signature_and_stamp: 'Signature & Stamp', + stamp_sign_present: 'Signature & Stamp', + mail_extraction: 'Mail extraction', + pan_number: 'PAN', + vendor_name: 'Supplier Name', + authority_grantor_name: 'Authority Grantor', + gender: 'Gender' + }; + return map[fieldKey] || fieldKey.replace(/_/g, ' '); +} + +function buildMsdStyleMessage(fieldKey: string, status: string, docType?: string): string { + const label = msdFieldDisplayName(fieldKey, docType); + if (status === 'MISSING') { + return `According to the expected record and the document, the "${label}" could not be read from the document.\nKindly upload the document again or update the expected value.`; + } + return `According to the expected record and the document, the "${label}" does not match.\nKindly upload the document again or update the expected value.`; +} + +function pickRuleForKey(rules: Record, key: string): string { + const k = key.toLowerCase(); + const candidates = Object.keys(rules) + .filter((rk) => rk !== 'default') + .sort((a, b) => b.length - a.length); + const hit = candidates.find((rk) => k.includes(rk.toLowerCase())); + return hit || 'default'; +} + +function isWithinRange(valA: any, valB: any, diff: number = 5): boolean { + const a = parseFloat(String(valA).replace(/[^0-9.]/g, "")); + const b = parseFloat(String(valB).replace(/[^0-9.]/g, "")); + if (isNaN(a) || isNaN(b)) return false; + return Math.abs(a - b) <= diff; +} + +function isVertexModelAccessIssue(err: unknown): boolean { + const e = err as { message?: string; name?: string; code?: number | string }; + const blob = `${e?.name || ''} ${e?.message || ''} ${String(e?.code || '')}`.toLowerCase(); + return ( + blob.includes('publisher model') || + blob.includes('model') && blob.includes('not found') || + blob.includes('does not have access') || + blob.includes('status: 404') || + blob.includes('code":404') + ); +} + +export class CpcValidationService { + /** + * @param expectedFieldKeys When set (e.g. from UI row order), every listed key is validated — MSD values may be empty (fails with clear reason) and keys are not dropped. When omitted, keys come from `msdPayload` (non-blank key names only). + */ + static validateSrs( + msdPayload: any, + extractedFields: any, + fieldConfidence: any = {}, + docTypeAttr: string = 'generic_invoice', + claimId: string | null = null, + attemptNo: number = 1, + expectedFieldKeys?: string[] | null + ) { + let normalizedDocType = (docTypeAttr || "generic_invoice").toUpperCase(); + if (normalizedDocType === 'AADHAAR_CARD' || normalizedDocType === 'ADHAAR') normalizedDocType = 'AADHAAR'; + if (normalizedDocType === 'AUTHORITY_LETTER' || normalizedDocType === 'CPC_LETTER') normalizedDocType = 'CPC_AUTH'; + if (normalizedDocType === 'PURCHASE_ORDER' || normalizedDocType === 'PO') normalizedDocType = 'CSD_PO'; + if (normalizedDocType === 'INVOICE' || normalizedDocType === 'GENERIC_INVOICE') normalizedDocType = 'RETAIL_INVOICE'; + if (!VALID_DOC_TYPES.includes(normalizedDocType as any) && normalizedDocType !== 'GENERIC') { + logger.warn(`[CpcValidation] Unknown doc type "${docTypeAttr}" → falling back to GENERIC`); + } + + const rules = DOCUMENT_RULES[normalizedDocType] || DOCUMENT_RULES.GENERIC; + + const fieldResults: any[] = []; + const mismatchReasons: string[] = []; + let totalMatchPercent = 0; + let totalFields = 0; + let matchedCount = 0; + let mismatchedCount = 0; + let missingCount = 0; + + const globalThreshold = 95; + + const findNormalizedValue = (obj: any, targetKey: string) => { + const norm = (k: string) => k.toLowerCase().replace(/[\s_]/g, ''); + const normTarget = norm(targetKey); + if (obj[targetKey] !== undefined) return obj[targetKey]; + + /** MSD field → alternate keys produced by rules / Gemini */ + const synonymSources: Record = { + authorized_person_name: ['customer_name', 'name', 'authorized_person_name', 'account_holder_name'], + customer_name: ['customer_name', 'name', 'authorized_person_name', 'account_holder_name', 'customername'], + name: ['authorized_person_name', 'customer_name', 'customername'], + pan_number: ['pan_number', 'pan', 'panno'], + invoice_value: ['invoice_value', 'amount', 'total_amount', 'total_value', 'po_amount', 'letter_amount'], + po_amount: ['po_amount', 'invoice_value', 'amount', 'total_amount', 'total_value'], + letter_amount: ['letter_amount', 'invoice_value', 'amount', 'total_amount', 'total_value'], + aadhaar_number: ['aadhaar_number', 'aadhar_number', 'aadhaar', 'aadhaarnumber', 'id_number'], + aadhar_number: ['aadhar_number', 'aadhaar_number', 'aadhaar', 'aadhaarnumber', 'id_number'], + letter_number: ['letter_number', 'order_or_auth_number', 'auth_number', 'auth_no'], + order_or_authorisation_number: ['order_or_authorisation_number', 'order_or_auth_number', 'po_number', 'order_number'], + po_number: ['po_number', 'order_or_authorisation_number', 'order_or_auth_number', 'order_number'], + govt_signatory_and_stamp_present: [ + 'govt_signatory_and_stamp_present', + 'signature_and_stamp', + 'stamp_sign_present', + 'stamp_or_signatory_present' + ], + signature_and_stamp: [ + 'signature_and_stamp', + 'govt_signatory_and_stamp_present', + 'stamp_sign_present', + 'stamp_or_signatory_present' + ], + mail_extraction: ['mail_extraction', 'email', 'registered_email', 'contact_email', 'buyer_email', 'correspondence_email'] + }; + for (const alt of synonymSources[targetKey] || []) { + if (obj[alt] !== undefined && obj[alt] !== null && String(obj[alt]).trim() !== '') { + return obj[alt]; + } + } + + const aliases: any = { + name: ['customername', 'customer_name', 'full_name', 'authorized_person_name', 'account_holder_name'], + customer_name: ['customername', 'name', 'full_name', 'authorized_person_name', 'account_holder_name'], + aadhaar_number: ['aadhaarnumber', 'aadhar_number', 'aadhar', 'aadhaar', 'id_number'], + aadhar_number: ['aadhaarnumber', 'aadhaar_number', 'aadhaar', 'id_number'], + invoice_value: ['total_amount', 'amount', 'total_value', 'po_amount', 'letter_amount'], + po_amount: ['invoice_value', 'total_amount', 'amount', 'total_value'], + letter_amount: ['invoice_value', 'amount', 'total_value'], + letter_number: ['order_or_auth_number', 'auth_number', 'auth_no'], + order_or_authorisation_number: ['order_or_auth_number', 'po_number', 'order_number'], + po_number: ['order_or_authorisation_number', 'order_or_auth_number', 'order_number'], + govt_signatory_and_stamp_present: ['stamp_sign_present', 'stamp_or_signatory_present', 'signature_and_stamp'], + signature_and_stamp: ['govt_signatory_and_stamp_present', 'stamp_sign_present', 'stamp_or_signatory_present'], + mail_extraction: ['email', 'e_mail', 'contactemail', 'correspondenceemail'] + }; + + for (const k of Object.keys(obj)) { + const normKey = norm(k); + if (normKey === normTarget) return obj[k]; + for (const [canonical, list] of Object.entries(aliases)) { + if ( + norm(canonical) === normTarget && + (list as string[]).some((a) => norm(a) === normKey) + ) { + return obj[k]; + } + } + } + return undefined; + }; + + const fromUi = Array.isArray(expectedFieldKeys) + ? [...new Set(expectedFieldKeys.map((k) => String(k || '').trim()).filter(Boolean))] + : []; + + const expectedKeys = + fromUi.length > 0 + ? fromUi + : Object.keys(msdPayload || {}).filter((k) => k && String(k).trim() !== ''); + + for (const key of expectedKeys) { + totalFields++; + const rawExpected = msdPayload?.[key]; + const expectedStr = + rawExpected === null || rawExpected === undefined ? '' : String(rawExpected); + const msdValueEmpty = + expectedStr.trim() === '' || expectedStr.trim().toLowerCase() === 'null'; + + if (msdValueEmpty) { + const foundPeek = findNormalizedValue(extractedFields, key); + const confidence = fieldConfidence[key] || 0; + const label = msdFieldDisplayName(key, normalizedDocType); + mismatchReasons.push( + `According to the expected record, "${label}" was not provided. Enter the expected value to validate against the document.` + ); + fieldResults.push({ + field: key, + expected: '(not provided)', + extracted: foundPeek ?? null, + status: 'UNSUCCESSFUL', + match_percentage: 0, + threshold: 'N/A', + match_method: 'n/a', + extraction_confidence: confidence, + reason: 'Expected value was empty — enter a value to compare with the document.', + criteria: getCriteriaLabel(key, normalizedDocType) + }); + mismatchedCount++; + continue; + } + + const expected = rawExpected; + const found = findNormalizedValue(extractedFields, key); + const confidence = fieldConfidence[key] || 0; + + const ruleKey = pickRuleForKey(rules as Record, key); + const rule = rules[ruleKey] || rules.default || DOCUMENT_RULES.GENERIC.default; + + let matchPercent = 0; + let isPass = false; + let status = "UNSUCCESSFUL"; + let reason = null; + + if (found === undefined || found === null || String(found).trim() === "" || String(found).toLowerCase() === "null") { + status = "MISSING"; + reason = "Field not found in document"; + missingCount++; + } else { + if (rule.method === 'exact_numeric') { + const numExp = parseFloat(String(expected).replace(/[^0-9.]/g, '')); + const numFnd = parseFloat(String(found).replace(/[^0-9.]/g, '')); + isPass = !isNaN(numExp) && !isNaN(numFnd) && Math.round(numExp) === Math.round(numFnd); + matchPercent = isPass ? 100 : 0; + } else if (rule.method === 'exact') { + const normExp = String(expected).trim().toLowerCase().replace(/[\s\-\/]+/g, ''); + const normFnd = String(found).trim().toLowerCase().replace(/[\s\-\/]+/g, ''); + isPass = normExp === normFnd; + matchPercent = isPass ? 100 : 0; + } else if (rule.method === 'range_5') { + isPass = isWithinRange(expected, found, 5); + matchPercent = isPass ? 100 : 0; + } else if (rule.method === 'range_5_or_fuzzy_98') { + const inRange = isWithinRange(expected, found, 5); + const expM = normalizeMoney(String(expected)); + const fndM = normalizeMoney(String(found)); + const fuzzyMoney = + expM && fndM ? calculateMatch(expM, fndM, key) : calculateMatch(String(expected), String(found), key); + isPass = inRange || fuzzyMoney >= 98; + matchPercent = inRange ? 100 : fuzzyMoney; + } else if (rule.method === 'boolean') { + const normBool = (v: unknown) => { + const t = String(v ?? '') + .toLowerCase() + .trim(); + if (/\b(yes|true|1|present|available|signed)\b/.test(t)) return 'pos'; + if (/\b(no|false|0|absent|not\s*available|unavailable|n\/a)\b/.test(t)) return 'neg'; + return 'unk'; + }; + const ePol = normBool(expected); + const fPol = normBool(found); + if (ePol !== 'unk' && fPol !== 'unk') { + isPass = ePol === fPol; + } else { + isPass = + String(expected).trim().toLowerCase() === String(found).trim().toLowerCase(); + } + matchPercent = isPass ? 100 : 0; + } else if (rule.method === 'boolean_fuzzy_85') { + const expand = (v: unknown) => { + const t = String(v ?? '').toLowerCase(); + if (/\b(yes|true|1|present|available|signed)\b/.test(t)) return 'available'; + if (/\b(no|false|0|absent|not\s*available|unavailable|n\/a)\b/.test(t)) return 'not available'; + return String(v ?? '') + .trim() + .toLowerCase(); + }; + const ex = expand(expected); + const fd = expand(found); + matchPercent = calculateMatch(ex, fd, key); + isPass = matchPercent >= 85; + } else if (rule.method === 'exact_length_12') { + const dExp = String(expected).replace(/\D/g, ""); + const dFnd = String(found).replace(/\D/g, ""); + isPass = (dExp === dFnd && dFnd.length === 12); + matchPercent = isPass ? 100 : 0; + } else if (rule.threshold === 100) { + matchPercent = String(expected).trim().toLowerCase() === String(found).trim().toLowerCase() ? 100 : 0; + isPass = (matchPercent === 100); + } else { + matchPercent = calculateMatch(expected, found, key); + isPass = (matchPercent >= (rule.threshold || globalThreshold)); + } + + if (isPass) { + status = "SUCCESSFUL"; + matchedCount++; + } else { + status = "UNSUCCESSFUL"; + reason = 'Value does not match expected'; + mismatchedCount++; + } + } + + totalMatchPercent += matchPercent; + + if (status !== "SUCCESSFUL") { + mismatchReasons.push(buildMsdStyleMessage(key, status, normalizedDocType)); + } + + fieldResults.push({ + field: key, + expected: expected, + extracted: found || null, + status: status, + match_percentage: matchPercent, + threshold: apiThresholdLabel(rule), + match_method: rule.method, + extraction_confidence: confidence, + reason: reason, + criteria: getCriteriaLabel(key, normalizedDocType) + }); + } + + /** MSD: success only if every expected field passes its own rule (no averaging). */ + const allFieldsPass = + totalFields > 0 && mismatchedCount === 0 && missingCount === 0 && matchedCount === totalFields; + const overallAccuracy = totalFields > 0 ? Math.round(totalMatchPercent / totalFields) : 0; + const displayMatchPercent = allFieldsPass ? 100 : overallAccuracy; + const hasMissing = missingCount > 0; + const overallValidationStatus = hasMissing + ? "NEED_MANUAL" + : allFieldsPass + ? "MATCH" + : "MISMATCH"; + const overallStatus = overallValidationStatus === "MATCH" ? "SUCCESSFUL" : "UNSUCCESSFUL"; + + return { + claim_id: claimId, + attempt_no: attemptNo, + status: overallStatus, + validation_status: overallValidationStatus, + match_percentage: displayMatchPercent, + overall_match_percentage: displayMatchPercent, + threshold: 100, + all_fields_passed: allFieldsPass, + mismatch_summary: { + total_expected_fields: totalFields, + matched: matchedCount, + mismatched: mismatchedCount, + missing: missingCount, + all_fields_passed: allFieldsPass + }, + mismatch_reasons: mismatchReasons, + field_results: fieldResults + }; + } + + static async extractWithGemini(params: { + projectId: string; + location: string; + modelName?: string; + documentType: string; + ocrText?: string; + fileBuffer?: Buffer; + mimeType?: string; + expectedFields?: string[]; + /** MSD / form values — passed into prompt so Gemini aligns labels with user input (no secrets; same as document check). */ + msdReferencePayload?: Record; + }) { + const { + projectId, + location, + modelName, + documentType, + ocrText, + fileBuffer, + mimeType, + expectedFields = [], + msdReferencePayload + } = params; + + const saPath = resolveVertexServiceAccountPath(); + const vertexInit: ConstructorParameters[0] = { + project: projectId, + location + }; + if (saPath) { + (vertexInit as { googleAuthOptions?: { keyFilename: string } }).googleAuthOptions = { + keyFilename: saPath + }; + logger.info(`[CpcValidation] Vertex AI using service account file: ${saPath}`); + } else { + logger.warn( + '[CpcValidation] No GCP_KEY_FILE / GOOGLE_APPLICATION_CREDENTIALS on disk — Vertex uses ADC only (often empty inside Docker).' + ); + } + + const usedModel = + (modelName && String(modelName).trim()) || + process.env.GEMINI_MODEL?.trim() || + process.env.VERTEX_AI_MODEL?.trim() || + 'gemini-1.5-flash'; + + const promptText = this.buildPrompt(documentType, ocrText || "", expectedFields, params.msdReferencePayload); + const parts: any[] = [{ text: promptText }]; + + if (fileBuffer) { + parts.push({ + inlineData: { + mimeType: mimeType || "application/pdf", + data: fileBuffer.toString("base64") + } + }); + } + + const fallbackLocation = (process.env.CPC_VERTEX_FALLBACK_LOCATION || 'us-central1').trim(); + const fallbackModel = (process.env.CPC_VERTEX_FALLBACK_MODEL || 'gemini-2.0-flash-lite').trim(); + const attempts = [ + { location, model: usedModel, label: 'primary' }, + { location: fallbackLocation, model: fallbackModel, label: 'fallback' } + ].filter((a, i, arr) => arr.findIndex((x) => x.location === a.location && x.model === a.model) === i); + + let lastErr: unknown; + for (let idx = 0; idx < attempts.length; idx++) { + const attempt = attempts[idx]; + const attemptVertexInit: ConstructorParameters[0] = { + project: projectId, + location: attempt.location + }; + if (saPath) { + (attemptVertexInit as { googleAuthOptions?: { keyFilename: string } }).googleAuthOptions = { + keyFilename: saPath + }; + } + const vertexAI = new VertexAI(attemptVertexInit); + const model = vertexAI.getGenerativeModel({ model: attempt.model }); + try { + if (idx > 0) { + logger.warn( + `[CpcValidation] Retrying Vertex extraction using ${attempt.label} model/location (${attempt.model} @ ${attempt.location})` + ); + } + const resp = await model.generateContent({ + contents: [{ role: 'user', parts }], + generationConfig: { + temperature: 0.1, + maxOutputTokens: Math.min( + 8192, + parseInt(process.env.CPC_VERTEX_MAX_OUTPUT_TOKENS || '8192', 10) || 8192 + ) + } + }); + + const cand = resp?.response?.candidates?.[0] as { finishReason?: string; content?: { parts?: unknown[] } } | undefined; + if (cand?.finishReason && cand.finishReason !== 'STOP') { + logger.warn(`[CpcValidation] Gemini finishReason=${cand.finishReason}`); + } + + const out = + cand?.content?.parts?.map((p: any) => (typeof p?.text === 'string' ? p.text : '')).join('') || ''; + + if (!out) throw new Error('EMPTY_AI_RESPONSE'); + + const parsed = this.parseJsonLoose(out); + const merged: Record = { ...(parsed.extracted_fields || {}) }; + const lockKeys = [...new Set(expectedFields.map((k) => String(k || '').trim()).filter(Boolean))]; + for (const k of lockKeys) { + if (!(k in merged)) merged[k] = null; + } + parsed.extracted_fields = merged; + const keys = Object.keys(parsed.extracted_fields || {}); + if (keys.length === 0) { + logger.warn('[CpcValidation] Gemini returned empty extracted_fields; raw head: ' + out.slice(0, 400)); + } + return parsed; + } catch (error) { + lastErr = error; + const shouldRetry = idx < attempts.length - 1 && isVertexModelAccessIssue(error); + if (shouldRetry) { + logger.warn( + `[CpcValidation] Vertex attempt failed for ${attempt.model} @ ${attempt.location}. Trying fallback...`, + error + ); + continue; + } + logger.error("Gemini Extraction Error:", error); + throw error; + } + } + throw lastErr || new Error('AI_EXTRACTION_FAILED: Vertex extraction failed'); + } + + private static buildPrompt( + documentType: string, + ocrText: string, + expectedFields: string[] = [], + msdReferencePayload?: Record + ) { + const dt = documentType.toLowerCase(); + const rawDocType = String(documentType || ''); + const isAadhaar = dt.includes('aadhaar'); + const isInvoice = dt.includes('invoice') || dt.includes('retail'); + /** Avoid `includes('po')` — false positives on unrelated doc type strings. */ + const isCsdPo = + /\bcsd[_\s-]*po\b/i.test(rawDocType) || + /\bpurchase[_\s-]*order\b/i.test(rawDocType) || + /^\s*PO\s*$/i.test(rawDocType.trim()); + const isAuthorityDoc = + dt.includes('authority') || + dt.includes('cpc_auth') || + dt.includes('auth_letter') || + dt.includes('authority_letter') || + dt.includes('cpc_letter'); + + const schema: any = { + extracted_fields: {}, + field_confidence: {} + }; + + const userLockedKeys = [...new Set((expectedFields || []).map((f) => String(f || '').trim()).filter(Boolean))]; + + if (userLockedKeys.length > 0) { + userLockedKeys.forEach((f) => { + schema.extracted_fields[f] = 'string|null'; + }); + } else if (isAadhaar) { + schema.extracted_fields = { + customer_name: 'string', + aadhar_number: 'string', + name: 'string|null', + dob: 'string', + gender: 'string', + address: 'string', + aadhaar_number: 'string|null' + }; + } else if (isCsdPo) { + schema.extracted_fields = { + customer_name: 'string', + po_number: 'string', + po_amount: 'string', + signature_and_stamp: 'string|boolean', + vendor_name: 'string', + invoice_date: 'string', + order_or_authorisation_number: 'string|null', + invoice_value: 'string|null', + govt_signatory_and_stamp_present: 'string|boolean|null' + }; + } else if (isInvoice) { + schema.extracted_fields = { + customer_name: 'string', + order_or_authorisation_number: 'string', + invoice_value: 'string', + invoice_date: 'string', + vendor_name: 'string' + }; + } else if (isAuthorityDoc) { + schema.extracted_fields = { + customer_name: 'string', + letter_number: 'string|null', + letter_amount: 'string|null', + signature_and_stamp: 'string|boolean|null', + authorized_person_name: 'string|null', + authority_grantor_name: 'string', + valid_until: 'string', + purpose: 'string', + date_of_issue: 'string', + pan_number: 'string|null', + order_or_authorisation_number: 'string|null', + amount: 'string|null', + invoice_value: 'string|null', + stamp_sign_present: 'string|boolean|null', + govt_signatory_and_stamp_present: 'string|boolean|null' + }; + } + + Object.keys(schema.extracted_fields).forEach(key => { + schema.field_confidence[key] = "number (0-1)"; + }); + + const msdRef = + msdReferencePayload && + typeof msdReferencePayload === 'object' && + Object.keys(msdReferencePayload).length > 0 + ? JSON.stringify(msdReferencePayload, null, 2) + : ''; + + const scriptPrefBlock = buildMsdScriptPreferenceBlock(userLockedKeys, msdReferencePayload); + + return ` +Return ONLY valid JSON (no markdown). +Schema: +${JSON.stringify(schema, null, 2)} + +Instructions: +Extract fields based on the provided document_type. +${userLockedKeys.length > 0 + ? `MANDATORY_KEYS: Your JSON property "extracted_fields" MUST contain exactly these keys (same spelling, no extras): ${userLockedKeys.join(', ')}. Use null only when that value is not visible on the document image/PDF.` + : ''} +${userLockedKeys.length > 0 + ? `EXTRACTION REQUEST: Extract only what is needed for those keys; do not invent keys outside the list.` + : ''} +${msdRef ? `REFERENCE_VALUES (from the user's form — use to locate the correct rows/labels on the document; values in extracted_fields must match what is visibly printed on the PDF/image, not invented):\n${msdRef}\n` : ''} +${scriptPrefBlock} +BILINGUAL_FORMS: Indian CPC/CSD forms often print the same label in English and Hindi. For each key in MSD_SCRIPT_PREFERENCE (if present), the MSD value shows which language the user entered — prefer_script is Devanagari (Hindi script) vs Latin (English). When both languages appear for that field on the image/PDF, copy the value whose script matches prefer_script. When only one script is visible, extract that visible value. Never return the other language if both are printed and MSD is clearly single-script. Numeric-only fields (amounts, IDs): use digits as printed; script rule applies mainly to name and free-text fields. + +For Aadhaar: customer_name (holder name), aadhar_number (12 digits, no spaces preferred), optional dob (DDMMYYYY), gender, address. You may also populate legacy keys name and aadhaar_number if visible. +CRITICAL: For 'address', extract ONLY the physical location details. +${isCsdPo + ? `For CSD Purchase Order: extract po_number (PO reference — exact text), po_amount (digits only, rupees), vendor_name (supplier/dealer company from letterhead or From/Supplier block), customer_name (the human buyer / beneficiary — NOT the dealer company name), invoice_date, signature_and_stamp as yes/no (official stamp or authorized signatory visible). Legacy keys order_or_authorisation_number, invoice_value, govt_signatory_and_stamp_present may be filled with the same values if present. +For customer_name, read the value beside or under labels such as: Sold To, Bill To, Ship To, Consignee, Buyer, Purchaser, Customer, CSD Card / Card Holder, Beneficiary, Name of Purchaser/Buyer, Ordered By. Do NOT use the first generic "Name:" on the page if it sits under supplier/dealer details or is clearly a sales contact. +Many CSD PO line tables put the beneficiary in the Description column as: a 16-digit number (card/UIN style) immediately followed by the person's name (then often a house/plot number and address). Prefer that name for customer_name when present. +${expectedFields.some((f) => String(f).toLowerCase() === 'customer_name') ? "CRITICAL: The JSON key customer_name must hold the printed buyer/beneficiary person name from the PO (what the user typed in customer_name). Put the supplying company's legal name only under vendor_name when that key exists; never put the dealer letterhead name in customer_name." : ''}` + : ''} +${isInvoice ? 'For Retail Invoice: customer name, invoice amount (numeric only, exclude currency symbol), order/authorisation number, vendor name, and date.' : ''} +${isAuthorityDoc + ? 'For CPC / Authorization Letter: extract customer_name (person being authorized), letter_number, letter_amount (numeric), signature_and_stamp yes/no (stamp/signature visible). Also extract authority grantor, dates, purpose, PAN if visible when those keys exist in the schema. Legacy keys authorized_person_name, invoice_value, govt_signatory_and_stamp_present may mirror the same values.' + : ''} +${userLockedKeys.some((f) => String(f).toLowerCase() === 'mail_extraction') + ? "If 'mail_extraction' is requested: extract the email address or mail reference line visible on the document (official correspondence / contact email). Put the primary value in extracted_fields.mail_extraction." + : ''} +If a field name like 'pan_number' is requested, look for a 10-character alphanumeric string (5 letters, 4 digits, 1 letter). +For 'govt_signatory_and_stamp_present' or 'signature_and_stamp', check if the document has an official stamp or authorized signatory mark and return "yes" or "no". + +document_type: ${documentType} + +OCR_TEXT: +"""${ocrText ? ocrText.slice(0, 20000) : "No OCR text provided. Please extract directly from the provided document image/PDF."}""" +`; + } + + private static parseJsonLoose(text: string): { extracted_fields: Record; field_confidence: Record } { + let s = String(text || '').trim(); + s = s.replace(/^```(?:json)?\s*/i, '').replace(/\s*```\s*$/i, ''); + const a = s.indexOf('{'); + const b = s.lastIndexOf('}'); + if (a === -1) throw new Error('AI_EXTRACTION_FAILED: No JSON object found in LLM response'); + let parsed: Record; + try { + parsed = JSON.parse(s.slice(a, b + 1)) as Record; + } catch { + throw new Error('AI_EXTRACTION_FAILED: Invalid JSON from model'); + } + const nested = parsed.extracted_fields; + if (nested && typeof nested === 'object' && !Array.isArray(nested)) { + return { + extracted_fields: nested as Record, + field_confidence: + parsed.field_confidence && typeof parsed.field_confidence === 'object' + ? (parsed.field_confidence as Record) + : {} + }; + } + // Model sometimes returns flat keys instead of { extracted_fields: { ... } } + const fc = + parsed.field_confidence && typeof parsed.field_confidence === 'object' + ? (parsed.field_confidence as Record) + : {}; + const ef: Record = { ...parsed }; + delete ef.field_confidence; + delete ef.extracted_fields; + return { extracted_fields: ef, field_confidence: fc }; + } +} diff --git a/src/services/cpc-cdc/ensureCpcCdcSchema.ts b/src/services/cpc-cdc/ensureCpcCdcSchema.ts new file mode 100644 index 0000000..7428032 --- /dev/null +++ b/src/services/cpc-cdc/ensureCpcCdcSchema.ts @@ -0,0 +1,55 @@ +import { sequelize } from '@config/database'; +import logger from '@utils/logger'; + +/** + * Ensures CPC-CSD tables exist (idempotent). Runs at app startup so a fresh DB + * still serves CPC routes even if the migrations runner was skipped once. + */ +export async function ensureCpcCdcSchema(): Promise { + try { + await sequelize.query(` + CREATE TABLE IF NOT EXISTS cpc_documents ( + id UUID NOT NULL PRIMARY KEY, + booking_id VARCHAR(255), + claim_id VARCHAR(255), + attempt_no INTEGER NOT NULL DEFAULT 1, + document_type VARCHAR(255), + document_gcp_url TEXT, + provider VARCHAR(255), + msd_payload JSONB, + extracted_fields JSONB, + field_confidence JSONB, + validation_status VARCHAR(255), + match_percentage DOUBLE PRECISION, + mismatch_reasons JSONB, + field_results JSONB, + ip_address VARCHAR(255), + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() + ); + `); + + await sequelize.query(` + CREATE TABLE IF NOT EXISTS cpc_audit_logs ( + id UUID NOT NULL PRIMARY KEY, + document_id UUID NOT NULL REFERENCES cpc_documents(id) ON DELETE CASCADE, + action VARCHAR(255) NOT NULL, + previous_state JSONB, + new_state JSONB, + performed_by VARCHAR(255), + remarks TEXT, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() + ); + `); + + await sequelize.query(`DROP INDEX IF EXISTS unique_cpc_document_attempt;`); + await sequelize.query(` + CREATE UNIQUE INDEX IF NOT EXISTS unique_cpc_document_claim_attempt_booking + ON cpc_documents (claim_id, attempt_no, booking_id); + `); + + logger.info('[CPC-CSD] Schema check complete (cpc_documents / cpc_audit_logs).'); + } catch (err) { + logger.error('[CPC-CSD] ensureCpcCdcSchema failed — run `npm run migrate` in re-workflow-be.', err); + // Do not block app boot; CPC routes will error until DB is fixed. + } +} diff --git a/src/services/cpc-cdc/extractPdfText.ts b/src/services/cpc-cdc/extractPdfText.ts new file mode 100644 index 0000000..660d914 --- /dev/null +++ b/src/services/cpc-cdc/extractPdfText.ts @@ -0,0 +1,22 @@ +/** + * Pull plain text from a PDF buffer for CPC RULES / regex extraction when Document AI is off. + */ +export async function extractPdfTextFromBuffer(buffer: Buffer): Promise { + if (!buffer?.length) return ''; + try { + const { PDFParse } = await import('pdf-parse'); + const parser = new PDFParse({ data: new Uint8Array(buffer) }); + const textResult = await parser.getText(); + const text = textResult?.text ?? ''; + await parser.destroy(); + // #region agent log + fetch('http://127.0.0.1:7259/ingest/1bcd6134-2d07-4e57-96c5-9f7406df102e',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'5f3c70'},body:JSON.stringify({sessionId:'5f3c70',location:'extractPdfText.ts:success',message:'pdf-parse succeeded',data:{textLen:text.length},timestamp:Date.now(),hypothesisId:'B'})}).catch(()=>{}); + // #endregion + return typeof text === 'string' ? text : ''; + } catch (pdfErr: any) { + // #region agent log + fetch('http://127.0.0.1:7259/ingest/1bcd6134-2d07-4e57-96c5-9f7406df102e',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'5f3c70'},body:JSON.stringify({sessionId:'5f3c70',location:'extractPdfText.ts:catch',message:'pdf-parse FAILED',data:{errorName:pdfErr?.name,errorMessage:pdfErr?.message?.slice(0,200)},timestamp:Date.now(),hypothesisId:'B'})}).catch(()=>{}); + // #endregion + return ''; + } +} diff --git a/src/services/cpc-cdc/utils.ts b/src/services/cpc-cdc/utils.ts new file mode 100644 index 0000000..39391d2 --- /dev/null +++ b/src/services/cpc-cdc/utils.ts @@ -0,0 +1,195 @@ +import stringSimilarity from 'string-similarity'; +import { Op } from 'sequelize'; + +/** Shared list/report filters for CPC documents (parity with legacy CPC-CSD). */ +export function appendCpcDocumentFilters( + andParts: Record[], + opts: { + type?: string; + status?: string; + search?: string; + /** When true, `search` also matches document `id` (recent documents API). */ + searchIncludeId?: boolean; + } +): void { + const { type, status, search, searchIncludeId = false } = opts; + + if (type && type !== 'ALL') { + if (type === 'AADHAAR') { + andParts.push({ + [Op.or]: [ + { documentType: { [Op.iLike]: '%AADHAAR%' } }, + { documentType: { [Op.iLike]: '%ADHAAR%' } } + ] + }); + } else if (type === 'RETAIL_INVOICE') { + andParts.push({ + [Op.or]: [ + { documentType: { [Op.iLike]: '%RETAIL%' } }, + { documentType: { [Op.iLike]: '%INVOICE%' } } + ] + }); + } else if (type === 'CPC_AUTH') { + andParts.push({ + [Op.or]: [ + { documentType: { [Op.iLike]: '%AUTHORITY%' } }, + { documentType: { [Op.iLike]: '%CPC_AUTH%' } }, + { documentType: { [Op.iLike]: '%AUTH%' } } + ] + }); + } else if (type === 'CSD_PO') { + andParts.push({ + [Op.or]: [ + { documentType: { [Op.iLike]: '%CSD_PO%' } }, + { documentType: { [Op.iLike]: '%PURCHASE_ORDER%' } }, + { documentType: { [Op.iLike]: '%PO%' } } + ] + }); + } else { + andParts.push({ documentType: { [Op.iLike]: `%${type}%` } }); + } + } + + if (status && status !== 'ALL') { + if (status === 'SUCCESSFUL') { + andParts.push({ + validationStatus: { [Op.in]: ['SUCCESSFUL', 'MATCH', 'APPROVED'] } + }); + } else if (status === 'UNSUCCESSFUL') { + // Document-level "failed" outcomes. Per-field columns can still show green for fields that passed. + // NEED_MANUAL = missing required extraction; not MATCH/SUCCESSFUL/APPROVED. + andParts.push({ + validationStatus: { + [Op.in]: ['UNSUCCESSFUL', 'MISMATCH', 'REJECTED', 'NEED_MANUAL'] + } + }); + } else { + andParts.push({ validationStatus: status }); + } + } + + if (search) { + const orClause: Record[] = [ + { bookingId: { [Op.iLike]: `%${search}%` } }, + { claimId: { [Op.iLike]: `%${search}%` } }, + { documentType: { [Op.iLike]: `%${search}%` } } + ]; + if (searchIncludeId) { + orClause.unshift({ id: { [Op.iLike]: `%${search}%` } }); + } + andParts.push({ [Op.or]: orClause }); + } +} + +export function cpcWhereFromAndParts(andParts: Record[]): Record { + if (andParts.length === 0) return {}; + return { [Op.and]: andParts }; +} + +export function digitsOnly(str: string | null | undefined): string { + return String(str || "").replace(/\D/g, ""); +} + +export function normalizeMoney(str: string | null | undefined): string { + const cleaned = String(str || "").replace(/[^\d.]/g, ""); + const num = cleaned ? Number(cleaned) : NaN; + if (Number.isNaN(num)) return ""; + return String(Math.round(num)); +} + +export function cleanText(str: string | null | undefined): string { + return String(str || "").trim().replace(/\s+/g, " "); +} + +export function nameTokens(str: string | null | undefined): string[] { + return cleanText(str) + .toLowerCase() + .replace(/[^a-z\s]/g, " ") + .split(/\s+/) + .filter(Boolean); +} + +export function normalizeDate(str: string | null | undefined): string | null { + if (!str) return null; + // Remove non-alphanumeric chars + const clean = str.replace(/[^a-zA-Z0-9]/g, ""); + + // Attempt to match common formats (DDMMYYYY, DD-MM-YYYY, YYYY-MM-DD) + // 1. DDMMYYYY (8 digits) + if (/^\d{8}$/.test(clean)) { + const day = clean.substring(0, 2); + const month = clean.substring(2, 4); + const year = clean.substring(4, 8); + return `${year}-${month}-${day}`; + } + // 2. Already ISO-like YYYYMMDD + if (/^\d{4}\d{2}\d{2}$/.test(clean) && (clean.startsWith("19") || clean.startsWith("20"))) { + const year = clean.substring(0, 4); + const month = clean.substring(4, 6); + const day = clean.substring(6, 8); + return `${year}-${month}-${day}`; + } + + // Try Native Date parsing if it has separators + try { + const d = new Date(str); + if (!isNaN(d.getTime())) { + return d.toISOString().split('T')[0]; + } + } catch (e) { } + + return null; +} + +export function cleanAddress(str: string | null | undefined): string { + if (!str) return ""; + // Remove "S/O", "C/O", "D/O", "W/O" and following name until a comma or newline + return str.replace(/(?:[scdw]\/o[:\s]|care\sof[:\s]|son\sof[:\s]|daughter\sof[:\s]|wife\sof[:\s])[^,\n]*(?:,|\n)?/gi, "").trim(); +} + +export function calculateMatch(expected: string, found: string, key: string = ""): number { + if (!expected || !found) return 0; + + const lowerKey = key.toLowerCase(); + let expStr = String(expected).trim().toLowerCase(); + let fndStr = String(found).trim().toLowerCase(); + + // 1. Date Normalization Special Handling + if (lowerKey.includes('dob') || lowerKey.includes('date')) { + const normExp = normalizeDate(expStr); + const normFnd = normalizeDate(fndStr); + if (normExp && normFnd && normExp === normFnd) return 100; + + // Fallback to digits only for dates like "28-06-1990" vs "28061990" + const dExp = expStr.replace(/\D/g, ""); + const dFnd = fndStr.replace(/\D/g, ""); + if (dExp !== "" && dExp === dFnd) return 100; + } + + // 2. Address Cleanup + if (lowerKey.includes('address')) { + fndStr = cleanAddress(fndStr).toLowerCase(); + expStr = cleanAddress(expStr).toLowerCase(); + } + + // 3. Exact match + if (expStr === fndStr) return 100; + + // 4. String Similarity (Levenshtein/Dice) + const similarity = stringSimilarity.compareTwoStrings(expStr, fndStr); + const score = Math.round(similarity * 100); + + // 5. Token-based fallback (Good for names/addresses) + const tokensA = nameTokens(expStr); + const tokensB = nameTokens(fndStr); + if (tokensA.length > 0 && tokensB.length > 0) { + const setA = new Set(tokensA); + const setB = new Set(tokensB); + let intersection = 0; + for (const t of setA) if (setB.has(t)) intersection++; + const tokenScore = Math.round((intersection / Math.max(setA.size, setB.size)) * 100); + return Math.max(score, tokenScore); + } + + return score > 0 ? score : 0; +} diff --git a/src/services/cpcPermission.service.ts b/src/services/cpcPermission.service.ts new file mode 100644 index 0000000..7a529c6 --- /dev/null +++ b/src/services/cpcPermission.service.ts @@ -0,0 +1,54 @@ +/** + * CPC-CSD permission service – API-driven access based on admin configuration. + * Reads viewerEmails from CPC_CSD_ADMIN_CONFIG (legacy CPC_CDC_ADMIN_CONFIG until migrated). + */ + +import { selectCpcCsdAdminConfigValue } from '../utils/cpcCsdAdminConfigDb'; + +export interface CpcCdcViewerConfig { + viewerEmails: string[]; +} + +const emptyConfig: CpcCdcViewerConfig = { + viewerEmails: [], +}; + +function normalizeEmail(email: string): string { + return (email || '').trim().toLowerCase(); +} + +/** + * Load CPC-CSD viewer config from admin_configurations. + * Returns empty list if config is missing or invalid. + */ +export async function getCpcCdcViewerConfig(): Promise { + try { + const raw = await selectCpcCsdAdminConfigValue(); + if (!raw) { + return emptyConfig; + } + + const parsed = JSON.parse(raw); + const viewerEmails = Array.isArray(parsed.viewerEmails) + ? parsed.viewerEmails.map((e: unknown) => normalizeEmail(String(e ?? ''))).filter(Boolean) + : []; + + return { viewerEmails }; + } catch { + return emptyConfig; + } +} + +/** + * Check if user can access CPC-CSD section. + * - Admin: always allowed. + * - Otherwise: only listed emails are allowed. + */ +export async function canAccessCpcCdc(userEmail: string, role?: string): Promise { + if (role === 'ADMIN') return true; + + const config = await getCpcCdcViewerConfig(); + const email = normalizeEmail(userEmail); + if (!email) return false; + return config.viewerEmails.includes(email); +} diff --git a/src/services/gcsStorage.service.ts b/src/services/gcsStorage.service.ts index bd4e71b..afdba83 100644 --- a/src/services/gcsStorage.service.ts +++ b/src/services/gcsStorage.service.ts @@ -13,6 +13,18 @@ interface UploadFileOptions { fileType: 'documents' | 'attachments'; // Type of file: documents or attachments } +/** CPC/CSD uploads — same GCS vs local rules as {@link uploadFileWithFallback}. */ +export interface UploadCpcCsdFileOptions { + buffer: Buffer; + originalName: string; + mimeType: string; + channel: 'csd' | 'cpc'; + /** Booking / claim id (caller may pre-sanitize; service sanitizes again) */ + bookingSegment: string; + /** When set, used as the final filename (no path segments). Otherwise same pattern as workflow documents. */ + fileName?: string; +} + interface UploadResult { storageUrl: string; filePath: string; // GCS path @@ -322,6 +334,146 @@ class GCSStorageService { } } + private cpcCsdSanitizeBookingSegment(segment: string): string { + const s = String(segment || '').trim(); + if (!s) return 'unknown-booking'; + return s.replace(/[^a-zA-Z0-9._-]+/g, '_').replace(/_+/g, '_').slice(0, 120); + } + + /** Same filename pattern as workflow `saveToLocalStorage` / `uploadFile`. */ + private buildCpcCsdFileName(originalName: string, explicit?: string): string { + if (explicit && !explicit.includes('/') && !explicit.includes('..')) { + return explicit; + } + const timestamp = Date.now(); + const randomHash = Math.random().toString(36).substring(2, 8); + const safeName = originalName.replace(/[^a-zA-Z0-9._-]/g, '_'); + const extension = path.extname(originalName); + const nameWithoutExt = safeName.substring(0, Math.max(0, safeName.length - extension.length)); + return `${nameWithoutExt}-${timestamp}-${randomHash}${extension}`; + } + + /** + * Relative object path (same string in GCS and under {@link UPLOAD_DIR} for local fallback). + * Example: `cpc-csd-files/csd/BOOK-1/documents/scan-1713-abc.pdf` + */ + private cpcCsdRelativeObjectPath(channel: 'csd' | 'cpc', bookingSeg: string, fileName: string): string { + const ch = channel === 'cpc' ? 'cpc' : 'csd'; + const b = this.cpcCsdSanitizeBookingSegment(bookingSeg); + return `cpc-csd-files/${ch}/${b}/documents/${fileName}`.replace(/\\/g, '/'); + } + + /** + * Upload CPC/CSD document to GCS (same bucket lifecycle as workflow requests). + */ + async uploadCpcCsdFile(options: UploadCpcCsdFileOptions): Promise { + if (!this.storage) { + throw new Error('GCS storage not initialized. Check GCP configuration.'); + } + + const { buffer, originalName, mimeType, channel, bookingSegment } = options; + if (!buffer?.length) { + throw new Error('Buffer is required for CPC/CSD upload'); + } + + const fileName = this.buildCpcCsdFileName(originalName, options.fileName); + const gcsFilePath = this.cpcCsdRelativeObjectPath(channel, bookingSegment, fileName); + + try { + await this.ensureBucketExists(); + + const bucket = this.storage.bucket(this.bucketName); + const file = bucket.file(gcsFilePath); + + const uploadOptions: any = { + metadata: { + contentType: mimeType, + metadata: { + originalName, + uploadedAt: new Date().toISOString(), + cpcCsdChannel: channel, + }, + }, + }; + + await file.save(buffer, uploadOptions); + + let publicUrl: string; + try { + await file.makePublic(); + publicUrl = `https://storage.googleapis.com/${this.bucketName}/${gcsFilePath}`; + } catch (makePublicError: any) { + if (makePublicError?.code === 400 || makePublicError?.message?.includes('publicAccessPrevention')) { + logger.warn('[GCS] CPC/CSD file cannot be public; using signed URL.'); + publicUrl = await this.getSignedUrl(gcsFilePath, 60 * 24 * 365); + } else { + throw makePublicError; + } + } + + logger.info('[GCS] CPC/CSD file uploaded', { gcsPath: gcsFilePath, storageUrl: publicUrl }); + + return { + storageUrl: publicUrl, + filePath: gcsFilePath, + fileName, + }; + } catch (error) { + logger.error('[GCS] CPC/CSD upload failed:', error); + throw new Error(`Failed to upload CPC/CSD file to GCS: ${error instanceof Error ? error.message : 'Unknown error'}`); + } + } + + /** + * Local fallback for CPC/CSD — mirrors folder layout used in GCS (`cpc-csd-files/...`). + */ + saveCpcCsdToLocalStorage(options: UploadCpcCsdFileOptions): UploadResult { + const { buffer, originalName, channel, bookingSegment } = options; + if (!buffer?.length) { + throw new Error('Buffer is required for CPC/CSD local storage'); + } + + const fileName = this.buildCpcCsdFileName(originalName, options.fileName); + const relativePath = this.cpcCsdRelativeObjectPath(channel, bookingSegment, fileName); + const segments = relativePath.split('/').filter(Boolean); + const localDir = path.join(UPLOAD_DIR, ...segments.slice(0, -1)); + const localFilePath = path.join(UPLOAD_DIR, ...segments); + + if (!fs.existsSync(localDir)) { + fs.mkdirSync(localDir, { recursive: true }); + } + fs.writeFileSync(localFilePath, buffer); + const storageUrl = `/uploads/${relativePath}`; + + logger.info('[GCS] CPC/CSD file saved to local storage (fallback)', { + originalName, + localPath: relativePath, + storageUrl, + }); + + return { + storageUrl, + filePath: relativePath, + fileName, + }; + } + + /** + * CPC/CSD: try GCS first, then local under `uploads/` — same behaviour as {@link uploadFileWithFallback}. + */ + async uploadCpcCsdFileWithFallback(options: UploadCpcCsdFileOptions): Promise { + if (!this.isConfigured()) { + logger.info('[GCS] GCS not configured, using local storage for CPC/CSD'); + return this.saveCpcCsdToLocalStorage(options); + } + try { + return await this.uploadCpcCsdFile(options); + } catch (gcsError) { + logger.warn('[GCS] CPC/CSD GCS upload failed, falling back to local storage', { error: gcsError }); + return this.saveCpcCsdToLocalStorage(options); + } + } + /** * Upload file with automatic fallback to local storage * If GCS is configured and works, uploads to GCS. Otherwise, saves to local storage. diff --git a/src/services/notification.service.ts b/src/services/notification.service.ts index 5a90e53..e84dad4 100644 --- a/src/services/notification.service.ts +++ b/src/services/notification.service.ts @@ -36,8 +36,12 @@ class NotificationService { logger.warn('VAPID keys are not configured. Push notifications are disabled.'); return; } - webpush.setVapidDetails(contact, pub, priv); - logger.info('Web Push configured'); + try { + webpush.setVapidDetails(contact, pub, priv); + logger.info('Web Push configured'); + } catch (error) { + logger.warn('Invalid VAPID keys. Push notifications are disabled.', error); + } } async addSubscription(userId: string, subscription: PushSubscription, userAgent?: string) { diff --git a/src/utils/cpcCsdAdminConfigDb.ts b/src/utils/cpcCsdAdminConfigDb.ts new file mode 100644 index 0000000..82c2555 --- /dev/null +++ b/src/utils/cpcCsdAdminConfigDb.ts @@ -0,0 +1,25 @@ +/** + * CPC/CSD document module — admin_configurations keys for viewer allow-list. + * Legacy key kept for reads until migrated or overwritten by admin save. + */ +import { QueryTypes } from 'sequelize'; +import { sequelize } from '../config/database'; + +export const CPC_CSD_ADMIN_CONFIG_KEY = 'CPC_CSD_ADMIN_CONFIG'; +export const CPC_CDC_ADMIN_CONFIG_KEY_LEGACY = 'CPC_CDC_ADMIN_CONFIG'; + +/** Prefer CPC_CSD_ADMIN_CONFIG row; fall back to legacy CPC_CDC_ADMIN_CONFIG if present. */ +export async function selectCpcCsdAdminConfigValue(): Promise { + const result = await sequelize.query<{ config_value: string }>( + `SELECT config_value FROM admin_configurations + WHERE config_key IN (:kCsd, :kLegacy) + ORDER BY CASE WHEN config_key = :kCsd THEN 0 ELSE 1 END + LIMIT 1`, + { + replacements: { kCsd: CPC_CSD_ADMIN_CONFIG_KEY, kLegacy: CPC_CDC_ADMIN_CONFIG_KEY_LEGACY }, + type: QueryTypes.SELECT, + } + ); + if (!result?.length || !result[0].config_value) return null; + return result[0].config_value; +} diff --git a/src/validators/admin.validator.ts b/src/validators/admin.validator.ts index 046b30d..b0a9e20 100644 --- a/src/validators/admin.validator.ts +++ b/src/validators/admin.validator.ts @@ -101,3 +101,8 @@ export const updateForm16ConfigSchema = z.object({ reminderRunAtTime: z.string().regex(/^(\d{1,2}:\d{2})?$/, 'Time must be in HH:mm format').optional(), reminderNotificationTemplate: z.string().optional(), }); + +// ── CPC-CSD Configuration Schemas ── +export const updateCpcCdcConfigSchema = z.object({ + viewerEmails: z.array(z.string().email()).optional(), +}); From dfe2c1423aa8c86354e5c3d1adbaf47c9519674d Mon Sep 17 00:00:00 2001 From: Arjun Mehar Date: Fri, 17 Apr 2026 20:06:06 +0530 Subject: [PATCH 2/4] Frontend build deploy --- ...-B8GrXg3V.js => conclusionApi-CFo0PF7I.js} | 2 +- build/assets/index-Bap1UWaI.css | 1 + build/assets/index-CMYYGiJ3.js | 64 ------------------- build/assets/index-DoaotahL.js | 64 +++++++++++++++++++ build/assets/index-f-qcsO2P.css | 1 - build/assets/router-vendor-DbXFJHwt.js | 3 + build/assets/router-vendor-HW_ujxKo.js | 3 - build/index.html | 6 +- 8 files changed, 72 insertions(+), 72 deletions(-) rename build/assets/{conclusionApi-B8GrXg3V.js => conclusionApi-CFo0PF7I.js} (81%) create mode 100644 build/assets/index-Bap1UWaI.css delete mode 100644 build/assets/index-CMYYGiJ3.js create mode 100644 build/assets/index-DoaotahL.js delete mode 100644 build/assets/index-f-qcsO2P.css create mode 100644 build/assets/router-vendor-DbXFJHwt.js delete mode 100644 build/assets/router-vendor-HW_ujxKo.js diff --git a/build/assets/conclusionApi-B8GrXg3V.js b/build/assets/conclusionApi-CFo0PF7I.js similarity index 81% rename from build/assets/conclusionApi-B8GrXg3V.js rename to build/assets/conclusionApi-CFo0PF7I.js index 78c42f5..fd28bef 100644 --- a/build/assets/conclusionApi-B8GrXg3V.js +++ b/build/assets/conclusionApi-CFo0PF7I.js @@ -1 +1 @@ -import{a as s}from"./index-CMYYGiJ3.js";import"./radix-vendor-CLtqm-Ae.js";import"./charts-vendor-CmYZJIYl.js";import"./utils-vendor-BTBPSQfW.js";import"./ui-vendor-DgwXkk2Y.js";import"./socket-vendor-TjCxX7sJ.js";import"./redux-vendor-tbZCm13o.js";import"./router-vendor-HW_ujxKo.js";async function m(n){return(await s.post(`/conclusions/${n}/generate`)).data.data}async function f(n,t){return(await s.post(`/conclusions/${n}/finalize`,{finalRemark:t})).data.data}async function d(n){var t;try{return(await s.get(`/conclusions/${n}`)).data.data}catch(o){if(((t=o.response)==null?void 0:t.status)===404)return null;throw o}}export{f as finalizeConclusion,m as generateConclusion,d as getConclusion}; +import{a as s}from"./index-DoaotahL.js";import"./radix-vendor-CLtqm-Ae.js";import"./charts-vendor-CmYZJIYl.js";import"./utils-vendor-BTBPSQfW.js";import"./ui-vendor-DgwXkk2Y.js";import"./socket-vendor-TjCxX7sJ.js";import"./redux-vendor-tbZCm13o.js";import"./router-vendor-DbXFJHwt.js";async function m(n){return(await s.post(`/conclusions/${n}/generate`)).data.data}async function f(n,t){return(await s.post(`/conclusions/${n}/finalize`,{finalRemark:t})).data.data}async function d(n){var t;try{return(await s.get(`/conclusions/${n}`)).data.data}catch(o){if(((t=o.response)==null?void 0:t.status)===404)return null;throw o}}export{f as finalizeConclusion,m as generateConclusion,d as getConclusion}; diff --git a/build/assets/index-Bap1UWaI.css b/build/assets/index-Bap1UWaI.css new file mode 100644 index 0000000..0aa6e75 --- /dev/null +++ b/build/assets/index-Bap1UWaI.css @@ -0,0 +1 @@ +.file-preview-dialog{width:90vw!important;max-width:90vw!important;height:90vh!important;max-height:90vh!important}@media (max-width: 640px){.file-preview-dialog{width:100vw!important;max-width:100vw!important;height:100vh!important;max-height:100vh!important;border-radius:0!important;margin:0!important}}.file-preview-content{display:flex;flex-direction:column;height:100%;overflow:hidden}.file-preview-body{flex:1;overflow:auto;min-height:0}.dealer-completion-documents-modal{width:90vw!important;max-width:90vw!important;max-height:95vh!important}@media (max-width: 640px){.dealer-completion-documents-modal{width:95vw!important;max-width:95vw!important;max-height:95vh!important}}@media (min-width: 641px) and (max-width: 1023px){.dealer-completion-documents-modal{width:90vw!important;max-width:90vw!important}}@media (min-width: 1024px){.dealer-completion-documents-modal{width:90vw!important;max-width:1200px!important}}@media (min-width: 1536px){.dealer-completion-documents-modal{width:90vw!important;max-width:1200px!important}}.dealer-completion-documents-modal input[type=date]{position:relative;cursor:pointer}.dealer-completion-documents-modal input[type=date]::-webkit-calendar-picker-indicator{position:absolute;right:.5rem;cursor:pointer;opacity:1;z-index:1;pointer-events:auto}.dealer-completion-documents-modal input[type=date]::-webkit-inner-spin-button,.dealer-completion-documents-modal input[type=date]::-webkit-clear-button{display:none;-webkit-appearance:none}.dealer-completion-documents-modal input[type=date]::-moz-calendar-picker-indicator{position:absolute;right:.5rem;cursor:pointer;opacity:1}.dealer-proposal-modal{width:90vw!important;max-width:90vw!important;max-height:95vh!important}@media (max-width: 640px){.dealer-proposal-modal{width:95vw!important;max-width:95vw!important;max-height:95vh!important}}@media (min-width: 641px) and (max-width: 1023px){.dealer-proposal-modal{width:90vw!important;max-width:90vw!important}}@media (min-width: 1024px){.dealer-proposal-modal{width:90vw!important;max-width:1200px!important}}@media (min-width: 1536px){.dealer-proposal-modal{width:90vw!important;max-width:1200px!important}}.dealer-proposal-modal input[type=date]{position:relative;cursor:pointer}.dealer-proposal-modal input[type=date]::-webkit-calendar-picker-indicator{position:absolute;right:.5rem;cursor:pointer;opacity:1;z-index:1;pointer-events:auto}.dealer-proposal-modal input[type=date]::-webkit-inner-spin-button,.dealer-proposal-modal input[type=date]::-webkit-clear-button{display:none;-webkit-appearance:none}.dealer-proposal-modal input[type=date]::-moz-calendar-picker-indicator{position:absolute;right:.5rem;cursor:pointer;opacity:1}.dept-lead-io-modal{width:90vw!important;max-width:90vw!important;max-height:95vh!important}@media (max-width: 640px){.dept-lead-io-modal{width:95vw!important;max-width:95vw!important;max-height:95vh!important}}@media (min-width: 641px) and (max-width: 1023px){.dept-lead-io-modal{width:90vw!important;max-width:90vw!important}}@media (min-width: 1024px){.dept-lead-io-modal{width:90vw!important;max-width:1000px!important}}@media (min-width: 1536px){.dept-lead-io-modal{width:90vw!important;max-width:1000px!important}}.settlement-push-modal{width:90vw!important;max-width:1000px!important;min-width:320px!important;max-height:95vh!important;overflow:hidden;display:flex;flex-direction:column}@media (max-width: 640px){.settlement-push-modal{width:95vw!important;max-width:95vw!important;max-height:95vh!important}}@media (min-width: 641px) and (max-width: 1023px){.settlement-push-modal{width:90vw!important;max-width:900px!important}}.settlement-push-modal .flex-1{overflow-y:auto;padding-right:4px}.settlement-push-modal .flex-1::-webkit-scrollbar{width:6px}.settlement-push-modal .flex-1::-webkit-scrollbar-track{background:transparent}.settlement-push-modal .flex-1::-webkit-scrollbar-thumb{background:#e2e8f0;border-radius:10px}.settlement-push-modal .flex-1::-webkit-scrollbar-thumb:hover{background:#cbd5e1}.file-preview-dialog{width:95vw!important;max-width:1200px!important;max-height:95vh!important;padding:0!important;display:flex;flex-direction:column}.file-preview-content{display:flex;flex-direction:column;height:100%;width:100%}.file-upload-wrapper{position:relative;width:100%;height:140px;border:2px dashed #e2e8f0;border-radius:12px;display:flex;flex-direction:column;align-items:center;justify-content:center;background-color:#f8fafc;transition:all .3s cubic-bezier(.4,0,.2,1);cursor:pointer;overflow:hidden}.file-upload-wrapper:hover{border-color:#10b981;background-color:#f0fdf9;box-shadow:0 4px 12px #10b98114}.file-upload-input{position:absolute;width:100%;height:100%;opacity:0;cursor:pointer}.file-upload-text{color:#64748b;font-size:.9rem;pointer-events:none;text-align:center}.file-upload-icon{font-size:2rem;color:#94a3b8;margin-bottom:.5rem}.dashboard-container{font-family:Segoe UI,sans-serif;color:#4b5563}.page-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:2rem}.page-title{font-size:1.5rem;font-weight:600;color:#1f2937;margin:0}.page-subtitle{margin-top:.25rem;color:#6b7280;font-size:.9rem}.btn-new-request{background-color:#059669;color:#fff;border:none;padding:.5rem 1rem;border-radius:6px;font-weight:500;display:flex;align-items:center;gap:.5rem;cursor:pointer;transition:background-color .2s}.btn-new-request:hover{background-color:#047857}.stats-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:1.5rem;margin-bottom:2rem}.stat-card{background:#fff;padding:1.5rem;border-radius:12px;box-shadow:0 4px 6px -1px #0000000d,0 2px 4px -1px #00000008;text-align:left;border:1px solid rgba(229,231,235,.5);transition:transform .2s,box-shadow .2s;position:relative;overflow:hidden}.stat-card:hover{transform:translateY(-2px);box-shadow:0 10px 15px -3px #0000000d}.stat-card:before{content:"";position:absolute;top:0;left:0;width:4px;height:100%}.stat-card.blue:before{background-color:#3b82f6}.stat-card.yellow:before{background-color:#f59e0b}.stat-card.green:before{background-color:#10b981}.stat-card.red:before{background-color:#ef4444}.stat-label{display:block;font-size:.85rem;font-weight:500;color:#64748b;margin-bottom:.5rem;text-transform:uppercase;letter-spacing:.05em}.stat-value{font-size:2.25rem;font-weight:700;color:#0f172a;line-height:1}.table-container{background:#fff;border-radius:12px;border:1px solid rgba(229,231,235,.5);box-shadow:0 4px 6px -1px #0000000d;overflow:hidden;margin-top:2rem}.table-header-actions{padding:1.5rem;border-bottom:1px solid #f1f5f9;display:flex;justify-content:space-between;align-items:center;background-color:#fff}.table-title{font-size:1.1rem;font-weight:600;color:#0f172a}.data-table{width:100%;border-collapse:collapse}.data-table th{background-color:#f8fafc;text-align:left;padding:1rem 1.5rem;font-size:.75rem;font-weight:600;color:#64748b;text-transform:uppercase;letter-spacing:.05em;border-bottom:1px solid #e2e8f0}.data-table td{padding:1rem 1.5rem;border-bottom:1px solid #f1f5f9;font-size:.875rem;color:#334155;vertical-align:middle}.data-table tr:hover{background-color:#f8fafc}.dashboard-booking-id-link:hover{text-decoration:underline}.status-pill{display:inline-flex;align-items:center;gap:.5rem;padding:.35rem .85rem;border-radius:9999px;font-size:.75rem;font-weight:600;border:1px solid transparent;text-transform:capitalize}.status-icon{font-size:1rem}.status-pill.success{background-color:#ecfdf5;color:#047857;border-color:#a7f3d0}.status-pill.warning{background-color:#fffbeb;color:#b45309;border-color:#fcd34d}.status-pill.danger{background-color:#fef2f2;color:#b91c1c;border-color:#fca5a5}.status-pill.info{background-color:#eff6ff;color:#1d4ed8;border-color:#bfdbfe}.status-pill.neutral{background-color:#f3f4f6;color:#4b5563;border-color:#e5e7eb}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.ani-spin{animation:spin 1s linear infinite}.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:#00000080;display:flex;justify-content:center;align-items:center;z-index:1000}.modal-content{background:#fff;padding:0;border-radius:16px;width:95%;max-width:600px;max-height:90vh;overflow:hidden;position:relative;box-shadow:0 25px 50px -12px #00000040;display:flex;flex-direction:column}.modal-header{padding:1.5rem 2rem;border-bottom:1px solid #f1f5f9;display:flex;justify-content:space-between;align-items:center}.modal-body{padding:2rem;overflow-y:auto;flex:1}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:10px}::-webkit-scrollbar-thumb:hover{background:#94a3b8}.premium-input{width:100%;padding:.75rem 1rem;border:1px solid #e2e8f0;border-radius:8px;font-size:.95rem;transition:all .2s;background-color:#fff}.premium-input:focus{outline:none;border-color:#3b82f6;box-shadow:0 0 0 4px #3b82f61a}.premium-label{display:block;font-size:.875rem;font-weight:600;color:#475569;margin-bottom:.5rem}.queue-container{display:grid;grid-template-columns:240px 1fr;gap:0;border:1px solid #e2e8f0;border-radius:12px;overflow:hidden;height:500px;background:#fff}.queue-sidebar{background-color:#f8fafc;border-right:1px solid #e2e8f0;overflow-y:auto}.queue-item{padding:1rem;cursor:pointer;font-size:.875rem;border-bottom:1px solid #f1f5f9;display:flex;justify-content:space-between;align-items:center;transition:all .2s}.queue-item:hover{background-color:#f1f5f9}.queue-item.active{background-color:#eff6ff;color:#2563eb;font-weight:600;box-shadow:inset 4px 0 #2563eb}.modal-close{position:absolute;top:1rem;right:1rem;background:none;border:none;font-size:1.5rem;cursor:pointer;color:#9ca3af}.modal-close:hover{color:#1f2937}.modal-title{font-size:1.25rem;font-weight:600;margin-bottom:1.5rem;color:#111827;border-bottom:1px solid #e5e7eb;padding-bottom:1rem}@media (max-width: 1024px){.stats-grid{grid-template-columns:repeat(2,1fr)}}@media (max-width: 640px){.stats-grid{grid-template-columns:1fr}}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}[type=text],input:where(:not([type])),[type=email],[type=url],[type=password],[type=number],[type=date],[type=datetime-local],[type=month],[type=search],[type=tel],[type=time],[type=week],[multiple],textarea,select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow: 0 0 #0000}[type=text]:focus,input:where(:not([type])):focus,[type=email]:focus,[type=url]:focus,[type=password]:focus,[type=number]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=month]:focus,[type=search]:focus,[type=tel]:focus,[type=time]:focus,[type=week]:focus,[multiple]:focus,textarea:focus,select:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: #2563eb;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field{padding-top:0;padding-bottom:0}select{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}[multiple],[size]:where(select:not([size="1"])){background-image:initial;background-position:initial;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-color:#6b7280;border-width:1px;--tw-shadow: 0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 2px;--tw-ring-offset-color: #fff;--tw-ring-color: #2563eb;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[type=checkbox]:checked,[type=radio]:checked{border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e")}@media (forced-colors: active){[type=checkbox]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=radio]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e")}@media (forced-colors: active){[type=radio]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:checked:hover,[type=checkbox]:checked:focus,[type=radio]:checked:hover,[type=radio]:checked:focus{border-color:transparent;background-color:currentColor}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat}@media (forced-colors: active){[type=checkbox]:indeterminate{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:indeterminate:hover,[type=checkbox]:indeterminate:focus{border-color:transparent;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px solid ButtonText;outline:1px auto -webkit-focus-ring-color}*{border-color:hsl(var(--border));outline-color:hsl(var(--ring) / .5)}body{background-color:hsl(var(--background));color:hsl(var(--foreground))}*{box-sizing:border-box}p,div,span,h1,h2,h3,h4,h5,h6{text-align:inherit}*{word-wrap:break-word;overflow-wrap:break-word}body{text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}:where(:not(:has([class*=" text-"]),:not(:has([class^=text-])))) h1{font-size:1.875rem;font-weight:var(--font-weight-medium);line-height:1.4;letter-spacing:-.025em}:where(:not(:has([class*=" text-"]),:not(:has([class^=text-])))) h2{font-size:1.5rem;font-weight:var(--font-weight-medium);line-height:1.4;letter-spacing:-.025em}:where(:not(:has([class*=" text-"]),:not(:has([class^=text-])))) h3{font-size:1.25rem;font-weight:var(--font-weight-medium);line-height:1.4}:where(:not(:has([class*=" text-"]),:not(:has([class^=text-])))) h4{font-size:1rem;font-weight:var(--font-weight-medium);line-height:1.5}:where(:not(:has([class*=" text-"]),:not(:has([class^=text-])))) p{font-size:.875rem;font-weight:var(--font-weight-normal);line-height:1.6}:where(:not(:has([class*=" text-"]),:not(:has([class^=text-])))) label,:where(:not(:has([class*=" text-"]),:not(:has([class^=text-])))) button{font-size:.875rem;font-weight:var(--font-weight-medium);line-height:1.5}:where(:not(:has([class*=" text-"]),:not(:has([class^=text-])))) input{font-size:.875rem;font-weight:var(--font-weight-normal);line-height:1.5}.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.prose{color:var(--tw-prose-body);max-width:65ch}.prose :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em}.prose :where([class~=lead]):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-lead);font-size:1.25em;line-height:1.6;margin-top:1.2em;margin-bottom:1.2em}.prose :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-links);text-decoration:underline;font-weight:500}.prose :where(strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-bold);font-weight:600}.prose :where(a strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(blockquote strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(thead th strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal;margin-top:1.25em;margin-bottom:1.25em;padding-inline-start:1.625em}.prose :where(ol[type=A]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=A s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=I]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type=I s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type="1"]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal}.prose :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:disc;margin-top:1.25em;margin-bottom:1.25em;padding-inline-start:1.625em}.prose :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{font-weight:400;color:var(--tw-prose-counters)}.prose :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-bullets)}.prose :where(dt):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;margin-top:1.25em}.prose :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:var(--tw-prose-hr);border-top-width:1px;margin-top:3em;margin-bottom:3em}.prose :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:500;font-style:italic;color:var(--tw-prose-quotes);border-inline-start-width:.25rem;border-inline-start-color:var(--tw-prose-quote-borders);quotes:"“""”""‘""’";margin-top:1.6em;margin-bottom:1.6em;padding-inline-start:1em}.prose :where(blockquote p:first-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:open-quote}.prose :where(blockquote p:last-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:close-quote}.prose :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:800;font-size:2.25em;margin-top:0;margin-bottom:.8888889em;line-height:1.1111111}.prose :where(h1 strong):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:900;color:inherit}.prose :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:700;font-size:1.5em;margin-top:2em;margin-bottom:1em;line-height:1.3333333}.prose :where(h2 strong):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:800;color:inherit}.prose :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;font-size:1.25em;margin-top:1.6em;margin-bottom:.6em;line-height:1.6}.prose :where(h3 strong):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:700;color:inherit}.prose :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;margin-top:1.5em;margin-bottom:.5em;line-height:1.5}.prose :where(h4 strong):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:700;color:inherit}.prose :where(img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){display:block;margin-top:2em;margin-bottom:2em}.prose :where(video):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:500;font-family:inherit;color:var(--tw-prose-kbd);box-shadow:0 0 0 1px var(--tw-prose-kbd-shadows),0 3px 0 var(--tw-prose-kbd-shadows);font-size:.875em;border-radius:.3125rem;padding-top:.1875em;padding-inline-end:.375em;padding-bottom:.1875em;padding-inline-start:.375em}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-code);font-weight:600;font-size:.875em}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:"`"}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:"`"}.prose :where(a code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(h1 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(h2 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.875em}.prose :where(h3 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.9em}.prose :where(h4 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(blockquote code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(thead th code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(pre):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-pre-code);background-color:var(--tw-prose-pre-bg);overflow-x:auto;font-weight:400;font-size:.875em;line-height:1.7142857;margin-top:1.7142857em;margin-bottom:1.7142857em;border-radius:.375rem;padding-top:.8571429em;padding-inline-end:1.1428571em;padding-bottom:.8571429em;padding-inline-start:1.1428571em}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:transparent;border-width:0;border-radius:0;padding:0;font-weight:inherit;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:none}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:none}.prose :where(table):not(:where([class~=not-prose],[class~=not-prose] *)){width:100%;table-layout:auto;margin-top:2em;margin-bottom:2em;font-size:.875em;line-height:1.7142857}.prose :where(thead):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-th-borders)}.prose :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;vertical-align:bottom;padding-inline-end:.5714286em;padding-bottom:.5714286em;padding-inline-start:.5714286em}.prose :where(tbody tr):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-td-borders)}.prose :where(tbody tr:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:0}.prose :where(tbody td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:baseline}.prose :where(tfoot):not(:where([class~=not-prose],[class~=not-prose] *)){border-top-width:1px;border-top-color:var(--tw-prose-th-borders)}.prose :where(tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:top}.prose :where(th,td):not(:where([class~=not-prose],[class~=not-prose] *)){text-align:start}.prose :where(figure>*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose :where(figcaption):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-captions);font-size:.875em;line-height:1.4285714;margin-top:.8571429em}.prose{--tw-prose-body: #374151;--tw-prose-headings: #111827;--tw-prose-lead: #4b5563;--tw-prose-links: #111827;--tw-prose-bold: #111827;--tw-prose-counters: #6b7280;--tw-prose-bullets: #d1d5db;--tw-prose-hr: #e5e7eb;--tw-prose-quotes: #111827;--tw-prose-quote-borders: #e5e7eb;--tw-prose-captions: #6b7280;--tw-prose-kbd: #111827;--tw-prose-kbd-shadows: rgb(17 24 39 / 10%);--tw-prose-code: #111827;--tw-prose-pre-code: #e5e7eb;--tw-prose-pre-bg: #1f2937;--tw-prose-th-borders: #d1d5db;--tw-prose-td-borders: #e5e7eb;--tw-prose-invert-body: #d1d5db;--tw-prose-invert-headings: #fff;--tw-prose-invert-lead: #9ca3af;--tw-prose-invert-links: #fff;--tw-prose-invert-bold: #fff;--tw-prose-invert-counters: #9ca3af;--tw-prose-invert-bullets: #4b5563;--tw-prose-invert-hr: #374151;--tw-prose-invert-quotes: #f3f4f6;--tw-prose-invert-quote-borders: #374151;--tw-prose-invert-captions: #9ca3af;--tw-prose-invert-kbd: #fff;--tw-prose-invert-kbd-shadows: rgb(255 255 255 / 10%);--tw-prose-invert-code: #fff;--tw-prose-invert-pre-code: #d1d5db;--tw-prose-invert-pre-bg: rgb(0 0 0 / 50%);--tw-prose-invert-th-borders: #4b5563;--tw-prose-invert-td-borders: #374151;font-size:1rem;line-height:1.75}.prose :where(picture>img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose :where(li):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5em;margin-bottom:.5em}.prose :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.375em}.prose :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.375em}.prose :where(.prose>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.prose :where(.prose>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose :where(.prose>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.prose :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em}.prose :where(dd):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5em;padding-inline-start:1.625em}.prose :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(thead th:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose :where(thead th:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose :where(tbody td,tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){padding-top:.5714286em;padding-inline-end:.5714286em;padding-bottom:.5714286em;padding-inline-start:.5714286em}.prose :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(.prose>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(.prose>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.prose-sm{font-size:.875rem;line-height:1.7142857}.prose-sm :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.1428571em;margin-bottom:1.1428571em}.prose-sm :where([class~=lead]):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:1.2857143em;line-height:1.5555556;margin-top:.8888889em;margin-bottom:.8888889em}.prose-sm :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.3333333em;margin-bottom:1.3333333em;padding-inline-start:1.1111111em}.prose-sm :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:2.1428571em;margin-top:0;margin-bottom:.8em;line-height:1.2}.prose-sm :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:1.4285714em;margin-top:1.6em;margin-bottom:.8em;line-height:1.4}.prose-sm :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:1.2857143em;margin-top:1.5555556em;margin-bottom:.4444444em;line-height:1.5555556}.prose-sm :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.4285714em;margin-bottom:.5714286em;line-height:1.4285714}.prose-sm :where(img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.7142857em;margin-bottom:1.7142857em}.prose-sm :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.7142857em;margin-bottom:1.7142857em}.prose-sm :where(picture>img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose-sm :where(video):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.7142857em;margin-bottom:1.7142857em}.prose-sm :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.8571429em;border-radius:.3125rem;padding-top:.1428571em;padding-inline-end:.3571429em;padding-bottom:.1428571em;padding-inline-start:.3571429em}.prose-sm :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.8571429em}.prose-sm :where(h2 code):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.9em}.prose-sm :where(h3 code):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.8888889em}.prose-sm :where(pre):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.8571429em;line-height:1.6666667;margin-top:1.6666667em;margin-bottom:1.6666667em;border-radius:.25rem;padding-top:.6666667em;padding-inline-end:1em;padding-bottom:.6666667em;padding-inline-start:1em}.prose-sm :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.1428571em;margin-bottom:1.1428571em;padding-inline-start:1.5714286em}.prose-sm :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.1428571em;margin-bottom:1.1428571em;padding-inline-start:1.5714286em}.prose-sm :where(li):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.2857143em;margin-bottom:.2857143em}.prose-sm :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.4285714em}.prose-sm :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.4285714em}.prose-sm :where(.prose-sm>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5714286em;margin-bottom:.5714286em}.prose-sm :where(.prose-sm>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.1428571em}.prose-sm :where(.prose-sm>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.1428571em}.prose-sm :where(.prose-sm>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.1428571em}.prose-sm :where(.prose-sm>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.1428571em}.prose-sm :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5714286em;margin-bottom:.5714286em}.prose-sm :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.1428571em;margin-bottom:1.1428571em}.prose-sm :where(dt):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.1428571em}.prose-sm :where(dd):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.2857143em;padding-inline-start:1.5714286em}.prose-sm :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2.8571429em;margin-bottom:2.8571429em}.prose-sm :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-sm :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-sm :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-sm :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-sm :where(table):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.8571429em;line-height:1.5}.prose-sm :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:1em;padding-bottom:.6666667em;padding-inline-start:1em}.prose-sm :where(thead th:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose-sm :where(thead th:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose-sm :where(tbody td,tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){padding-top:.6666667em;padding-inline-end:1em;padding-bottom:.6666667em;padding-inline-start:1em}.prose-sm :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose-sm :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose-sm :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.7142857em;margin-bottom:1.7142857em}.prose-sm :where(figure>*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose-sm :where(figcaption):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.8571429em;line-height:1.3333333;margin-top:.6666667em}.prose-sm :where(.prose-sm>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-sm :where(.prose-sm>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}input[type=text]:focus,input[type=search]:focus,input:focus{outline:none}.border-gray-400{border-color:#9ca3af!important;border-width:1px!important;border-style:solid!important}.border-gray-500{border-color:#6b7280!important}input[data-slot=input],textarea[data-slot=textarea],[data-slot=select-trigger]{border-width:1px!important;border-style:solid!important}input:focus-visible,input[type=text]:focus-visible,input[type=number]:focus-visible,input[type=email]:focus-visible,input[type=search]:focus-visible,input[type=password]:focus-visible,textarea:focus-visible,select:focus-visible,[data-slot=input]:focus-visible,[data-slot=textarea]:focus-visible,[data-slot=select-trigger]:focus-visible,input:focus,input[type=text]:focus,input[type=number]:focus,input[type=email]:focus,input[type=search]:focus,textarea:focus,select:focus{border-color:var(--re-light-green)!important;outline:none!important;box-shadow:none!important;--tw-ring-width: 0 !important;--tw-ring-offset-width: 0 !important}.text-gray-400{color:#9ca3af!important}.\!text-gray-600,.text-gray-600{color:#4b5563!important}header.bg-white{background-color:#fff!important}[class*=border-gray]{border-style:solid}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.invisible{visibility:hidden}.collapse{visibility:collapse}.static{position:static}.\!fixed{position:fixed!important}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.\!inset-0{top:0!important;right:0!important;bottom:0!important;left:0!important}.inset-0{top:0;right:0;bottom:0;left:0}.inset-x-0{left:0;right:0}.inset-y-0{top:0;bottom:0}.\!bottom-0{bottom:0!important}.\!left-0{left:0!important}.\!right-0{right:0!important}.\!top-0{top:0!important}.-bottom-0\.5{bottom:-.125rem}.-bottom-1{bottom:-.25rem}.-bottom-12{bottom:-3rem}.-bottom-2{bottom:-.5rem}.-left-12{left:-3rem}.-left-\[31px\]{left:-31px}.-right-0\.5{right:-.125rem}.-right-1{right:-.25rem}.-right-12{right:-3rem}.-right-2{right:-.5rem}.-top-1{top:-.25rem}.-top-12{top:-3rem}.bottom-0{bottom:0}.bottom-1\/4{bottom:25%}.bottom-4{bottom:1rem}.bottom-full{bottom:100%}.left-0{left:0}.left-1{left:.25rem}.left-1\/2{left:50%}.left-1\/4{left:25%}.left-2{left:.5rem}.left-2\.5{left:.625rem}.left-3{left:.75rem}.left-6{left:1.5rem}.left-\[-9px\]{left:-9px}.left-\[50\%\]{left:50%}.right-0{right:0}.right-1{right:.25rem}.right-1\/4{right:25%}.right-2{right:.5rem}.right-3{right:.75rem}.right-4{right:1rem}.top-0{top:0}.top-1{top:.25rem}.top-1\.5{top:.375rem}.top-1\/2{top:50%}.top-1\/4{top:25%}.top-3\.5{top:.875rem}.top-4{top:1rem}.top-6{top:1.5rem}.top-\[1px\]{top:1px}.top-\[50\%\]{top:50%}.top-\[60\%\]{top:60%}.top-full{top:100%}.isolate{isolation:isolate}.z-10{z-index:10}.z-20{z-index:20}.z-40{z-index:40}.z-50{z-index:50}.z-\[100\]{z-index:100}.z-\[1\]{z-index:1}.order-1{order:1}.order-2{order:2}.col-span-1{grid-column:span 1 / span 1}.col-span-2{grid-column:span 2 / span 2}.col-span-4{grid-column:span 4 / span 4}.col-span-8{grid-column:span 8 / span 8}.col-span-full{grid-column:1 / -1}.col-start-2{grid-column-start:2}.row-span-2{grid-row:span 2 / span 2}.row-start-1{grid-row-start:1}.\!m-0{margin:0!important}.-mx-1{margin-left:-.25rem;margin-right:-.25rem}.-mx-4{margin-left:-1rem;margin-right:-1rem}.-mx-6{margin-left:-1.5rem;margin-right:-1.5rem}.mx-1{margin-left:.25rem;margin-right:.25rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.mx-3\.5{margin-left:.875rem;margin-right:.875rem}.mx-auto{margin-left:auto;margin-right:auto}.my-0\.5{margin-top:.125rem;margin-bottom:.125rem}.my-1{margin-top:.25rem;margin-bottom:.25rem}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-4{margin-top:1rem;margin-bottom:1rem}.-ml-4{margin-left:-1rem}.-mt-4{margin-top:-1rem}.mb-0\.5{margin-bottom:.125rem}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-12{margin-bottom:3rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-0{margin-left:0}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-4{margin-left:1rem}.ml-auto{margin-left:auto}.mr-1{margin-right:.25rem}.mr-1\.5{margin-right:.375rem}.mr-2{margin-right:.5rem}.mr-3{margin-right:.75rem}.mt-0{margin-top:0}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.mt-auto{margin-top:auto}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.\!flex{display:flex!important}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.table-caption{display:table-caption}.table-cell{display:table-cell}.table-row{display:table-row}.grid{display:grid}.contents{display:contents}.hidden{display:none}.size-10{width:2.5rem;height:2.5rem}.size-2{width:.5rem;height:.5rem}.size-2\.5{width:.625rem;height:.625rem}.size-3{width:.75rem;height:.75rem}.size-3\.5{width:.875rem;height:.875rem}.size-4{width:1rem;height:1rem}.size-7{width:1.75rem;height:1.75rem}.size-8{width:2rem;height:2rem}.size-9{width:2.25rem;height:2.25rem}.size-full{width:100%;height:100%}.\!h-12{height:3rem!important}.\!h-screen{height:100vh!important}.h-0\.5{height:.125rem}.h-1\.5{height:.375rem}.h-10{height:2.5rem}.h-11{height:2.75rem}.h-12{height:3rem}.h-14{height:3.5rem}.h-16{height:4rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-20{height:5rem}.h-24{height:6rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-32{height:8rem}.h-4{height:1rem}.h-48{height:12rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-96{height:24rem}.h-\[1\.15rem\]{height:1.15rem}.h-\[70vh\]{height:70vh}.h-\[80vh\]{height:80vh}.h-\[90vh\]{height:90vh}.h-\[calc\(100\%-1px\)\]{height:calc(100% - 1px)}.h-\[calc\(100vh-300px\)\]{height:calc(100vh - 300px)}.h-\[var\(--radix-navigation-menu-viewport-height\)\]{height:var(--radix-navigation-menu-viewport-height)}.h-\[var\(--radix-select-trigger-height\)\]{height:var(--radix-select-trigger-height)}.h-auto{height:auto}.h-full{height:100%}.h-px{height:1px}.h-screen{height:100vh}.h-svh{height:100svh}.max-h-32{max-height:8rem}.max-h-40{max-height:10rem}.max-h-56{max-height:14rem}.max-h-60{max-height:15rem}.max-h-80{max-height:20rem}.max-h-\[120px\]{max-height:120px}.max-h-\[150px\]{max-height:150px}.max-h-\[200px\]{max-height:200px}.max-h-\[300px\]{max-height:300px}.max-h-\[350px\]{max-height:350px}.max-h-\[400px\]{max-height:400px}.max-h-\[500px\]{max-height:500px}.max-h-\[85vh\]{max-height:85vh}.max-h-\[90vh\]{max-height:90vh}.max-h-\[calc\(100\%-140px\)\]{max-height:calc(100% - 140px)}.max-h-\[calc\(100\%-80px\)\]{max-height:calc(100% - 80px)}.min-h-0{min-height:0px}.min-h-16{min-height:4rem}.min-h-4{min-height:1rem}.min-h-\[100px\]{min-height:100px}.min-h-\[120px\]{min-height:120px}.min-h-\[150px\]{min-height:150px}.min-h-\[200px\]{min-height:200px}.min-h-\[240px\]{min-height:240px}.min-h-\[320px\]{min-height:320px}.min-h-\[4\.5rem\]{min-height:4.5rem}.min-h-\[50px\]{min-height:50px}.min-h-\[50vh\]{min-height:50vh}.min-h-\[600px\]{min-height:600px}.min-h-\[60px\]{min-height:60px}.min-h-\[60vh\]{min-height:60vh}.min-h-\[70vh\]{min-height:70vh}.min-h-\[720px\]{min-height:720px}.min-h-\[80px\]{min-height:80px}.min-h-\[80vh\]{min-height:80vh}.min-h-\[calc\(100vh-10rem\)\]{min-height:calc(100vh - 10rem)}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.min-h-svh{min-height:100svh}.\!w-screen{width:100vw!important}.w-0{width:0px}.w-1{width:.25rem}.w-1\.5{width:.375rem}.w-1\/2{width:50%}.w-10{width:2.5rem}.w-12{width:3rem}.w-14{width:3.5rem}.w-16{width:4rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-2\/3{width:66.666667%}.w-20{width:5rem}.w-24{width:6rem}.w-28{width:7rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-3\/4{width:75%}.w-32{width:8rem}.w-4{width:1rem}.w-40{width:10rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-64{width:16rem}.w-7{width:1.75rem}.w-72{width:18rem}.w-8{width:2rem}.w-9{width:2.25rem}.w-96{width:24rem}.w-\[100px\]{width:100px}.w-\[130px\]{width:130px}.w-\[140px\]{width:140px}.w-\[160px\]{width:160px}.w-\[180px\]{width:180px}.w-\[200px\]{width:200px}.w-\[250px\]{width:250px}.w-\[300px\]{width:300px}.w-\[320px\]{width:320px}.w-\[60px\]{width:60px}.w-\[80px\]{width:80px}.w-\[95vw\]{width:95vw}.w-auto{width:auto}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.w-max{width:-moz-max-content;width:max-content}.w-px{width:1px}.min-w-10{min-width:2.5rem}.min-w-5{min-width:1.25rem}.min-w-8{min-width:2rem}.min-w-9{min-width:2.25rem}.min-w-\[1000px\]{min-width:1000px}.min-w-\[100px\]{min-width:100px}.min-w-\[110px\]{min-width:110px}.min-w-\[120px\]{min-width:120px}.min-w-\[12rem\]{min-width:12rem}.min-w-\[140px\]{min-width:140px}.min-w-\[180px\]{min-width:180px}.min-w-\[200px\]{min-width:200px}.min-w-\[2rem\]{min-width:2rem}.min-w-\[3rem\]{min-width:3rem}.min-w-\[64px\]{min-width:64px}.min-w-\[72px\]{min-width:72px}.min-w-\[8rem\]{min-width:8rem}.min-w-\[90px\]{min-width:90px}.min-w-\[var\(--radix-select-trigger-width\)\]{min-width:var(--radix-select-trigger-width)}.\!max-w-none{max-width:none!important}.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-4xl{max-width:56rem}.max-w-5xl{max-width:64rem}.max-w-6xl{max-width:72rem}.max-w-7xl{max-width:80rem}.max-w-\[150px\]{max-width:150px}.max-w-\[1600px\]{max-width:1600px}.max-w-\[168px\]{max-width:168px}.max-w-\[180px\]{max-width:180px}.max-w-\[200px\]{max-width:200px}.max-w-\[280px\]{max-width:280px}.max-w-\[70\%\]{max-width:70%}.max-w-\[80px\]{max-width:80px}.max-w-\[98vw\]{max-width:98vw}.max-w-\[calc\(100\%-2rem\)\]{max-width:calc(100% - 2rem)}.max-w-full{max-width:100%}.max-w-lg{max-width:32rem}.max-w-max{max-width:-moz-max-content;max-width:max-content}.max-w-md{max-width:28rem}.max-w-none{max-width:none}.max-w-sm{max-width:24rem}.max-w-xl{max-width:36rem}.max-w-xs{max-width:20rem}.flex-\[3\]{flex:3}.flex-shrink-0,.shrink-0{flex-shrink:0}.grow{flex-grow:1}.grow-0{flex-grow:0}.basis-full{flex-basis:100%}.caption-bottom{caption-side:bottom}.border-collapse{border-collapse:collapse}.\!translate-x-0{--tw-translate-x: 0px !important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.\!translate-y-0{--tw-translate-y: 0px !important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-x-1\/2{--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-x-full{--tw-translate-x: -100%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-x-px{--tw-translate-x: -1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-\[-50\%\]{--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-px{--tw-translate-x: 1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0\.5{--tw-translate-y: .125rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-\[-50\%\]{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-\[calc\(-50\%_-_2px\)\]{--tw-translate-y: calc(-50% - 2px) ;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-45{--tw-rotate: 45deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-90{--tw-rotate: 90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-100{--tw-scale-x: 1;--tw-scale-y: 1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-75{--tw-scale-x: .75;--tw-scale-y: .75;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-90{--tw-scale-x: .9;--tw-scale-y: .9;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes ping{75%,to{transform:scale(2);opacity:0}}.animate-ping{animation:ping 1s cubic-bezier(0,0,.2,1) infinite}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-default{cursor:default}.cursor-help{cursor:help}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-none{resize:none}.resize{resize:both}.scroll-my-1{scroll-margin-top:.25rem;scroll-margin-bottom:.25rem}.scroll-py-1{scroll-padding-top:.25rem;scroll-padding-bottom:.25rem}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.list-none{list-style-type:none}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.grid-cols-8{grid-template-columns:repeat(8,minmax(0,1fr))}.grid-cols-9{grid-template-columns:repeat(9,minmax(0,1fr))}.grid-cols-\[0_1fr\]{grid-template-columns:0 1fr}.grid-rows-\[auto_auto\]{grid-template-rows:auto auto}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.items-stretch{align-items:stretch}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-items-start{justify-items:start}.gap-0{gap:0px}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-5{gap:1.25rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-0\.5{row-gap:.125rem}.gap-y-1{row-gap:.25rem}.gap-y-3{row-gap:.75rem}.-space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(-.5rem * var(--tw-space-x-reverse));margin-left:calc(-.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.25rem * var(--tw-space-x-reverse));margin-left:calc(.25rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.75rem * var(--tw-space-x-reverse));margin-left:calc(.75rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(0px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0px * var(--tw-space-y-reverse))}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.125rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem * var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-2\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.625rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.625rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(2rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2rem * var(--tw-space-y-reverse))}.divide-x>:not([hidden])~:not([hidden]){--tw-divide-x-reverse: 0;border-right-width:calc(1px * var(--tw-divide-x-reverse));border-left-width:calc(1px * calc(1 - var(--tw-divide-x-reverse)))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-blue-200\/50>:not([hidden])~:not([hidden]){border-color:#bfdbfe80}.divide-gray-100>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(243 244 246 / var(--tw-divide-opacity, 1))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(229 231 235 / var(--tw-divide-opacity, 1))}.self-start{align-self:flex-start}.self-end{align-self:flex-end}.justify-self-end{justify-self:end}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-x-hidden{overflow-x:hidden}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-line{white-space:pre-line}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.\!rounded-none{border-radius:0!important}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-\[2px\]{border-radius:2px}.rounded-\[4px\]{border-radius:4px}.rounded-\[inherit\]{border-radius:inherit}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-none{border-radius:0}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.rounded-xl{border-radius:.75rem}.rounded-b-lg{border-bottom-right-radius:var(--radius);border-bottom-left-radius:var(--radius)}.rounded-b-md{border-bottom-right-radius:calc(var(--radius) - 2px);border-bottom-left-radius:calc(var(--radius) - 2px)}.rounded-r-lg{border-top-right-radius:var(--radius);border-bottom-right-radius:var(--radius)}.rounded-t-md{border-top-left-radius:calc(var(--radius) - 2px);border-top-right-radius:calc(var(--radius) - 2px)}.rounded-tl-sm{border-top-left-radius:calc(var(--radius) - 4px)}.border{border-width:1px}.border-0{border-width:0px}.border-2{border-width:2px}.border-4{border-width:4px}.border-\[1\.5px\]{border-width:1.5px}.border-y{border-top-width:1px;border-bottom-width:1px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l{border-left-width:1px}.border-l-2{border-left-width:2px}.border-l-4{border-left-width:4px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-t-2{border-top-width:2px}.border-t-4{border-top-width:4px}.border-dashed{border-style:dashed}.border-none{border-style:none}.border-\[\#2d4a3e\]{--tw-border-opacity: 1;border-color:rgb(45 74 62 / var(--tw-border-opacity, 1))}.border-\[--re-green\]{border-color:var(--re-green)}.border-amber-100{--tw-border-opacity: 1;border-color:rgb(254 243 199 / var(--tw-border-opacity, 1))}.border-amber-200{--tw-border-opacity: 1;border-color:rgb(253 230 138 / var(--tw-border-opacity, 1))}.border-amber-200\/50{border-color:#fde68a80}.border-amber-300{--tw-border-opacity: 1;border-color:rgb(252 211 77 / var(--tw-border-opacity, 1))}.border-amber-500{--tw-border-opacity: 1;border-color:rgb(245 158 11 / var(--tw-border-opacity, 1))}.border-amber-700{--tw-border-opacity: 1;border-color:rgb(180 83 9 / var(--tw-border-opacity, 1))}.border-background{border-color:hsl(var(--background))}.border-blue-100{--tw-border-opacity: 1;border-color:rgb(219 234 254 / var(--tw-border-opacity, 1))}.border-blue-200{--tw-border-opacity: 1;border-color:rgb(191 219 254 / var(--tw-border-opacity, 1))}.border-blue-300{--tw-border-opacity: 1;border-color:rgb(147 197 253 / var(--tw-border-opacity, 1))}.border-blue-400{--tw-border-opacity: 1;border-color:rgb(96 165 250 / var(--tw-border-opacity, 1))}.border-blue-500{--tw-border-opacity: 1;border-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.border-blue-600{--tw-border-opacity: 1;border-color:rgb(37 99 235 / var(--tw-border-opacity, 1))}.border-border{border-color:hsl(var(--border))}.border-border\/50{border-color:hsl(var(--border) / .5)}.border-destructive{border-color:hsl(var(--destructive))}.border-emerald-100{--tw-border-opacity: 1;border-color:rgb(209 250 229 / var(--tw-border-opacity, 1))}.border-emerald-200{--tw-border-opacity: 1;border-color:rgb(167 243 208 / var(--tw-border-opacity, 1))}.border-gray-100{--tw-border-opacity: 1;border-color:rgb(243 244 246 / var(--tw-border-opacity, 1))}.border-gray-200{--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity, 1))}.border-gray-300{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity, 1))}.border-gray-400{--tw-border-opacity: 1;border-color:rgb(156 163 175 / var(--tw-border-opacity, 1))}.border-gray-500{--tw-border-opacity: 1;border-color:rgb(107 114 128 / var(--tw-border-opacity, 1))}.border-gray-700{--tw-border-opacity: 1;border-color:rgb(55 65 81 / var(--tw-border-opacity, 1))}.border-gray-800{--tw-border-opacity: 1;border-color:rgb(31 41 55 / var(--tw-border-opacity, 1))}.border-green-100{--tw-border-opacity: 1;border-color:rgb(220 252 231 / var(--tw-border-opacity, 1))}.border-green-200{--tw-border-opacity: 1;border-color:rgb(187 247 208 / var(--tw-border-opacity, 1))}.border-green-300{--tw-border-opacity: 1;border-color:rgb(134 239 172 / var(--tw-border-opacity, 1))}.border-green-500{--tw-border-opacity: 1;border-color:rgb(34 197 94 / var(--tw-border-opacity, 1))}.border-green-600{--tw-border-opacity: 1;border-color:rgb(22 163 74 / var(--tw-border-opacity, 1))}.border-indigo-200{--tw-border-opacity: 1;border-color:rgb(199 210 254 / var(--tw-border-opacity, 1))}.border-indigo-300{--tw-border-opacity: 1;border-color:rgb(165 180 252 / var(--tw-border-opacity, 1))}.border-input{border-color:hsl(var(--input))}.border-orange-200{--tw-border-opacity: 1;border-color:rgb(254 215 170 / var(--tw-border-opacity, 1))}.border-orange-300{--tw-border-opacity: 1;border-color:rgb(253 186 116 / var(--tw-border-opacity, 1))}.border-orange-400{--tw-border-opacity: 1;border-color:rgb(251 146 60 / var(--tw-border-opacity, 1))}.border-orange-500{--tw-border-opacity: 1;border-color:rgb(249 115 22 / var(--tw-border-opacity, 1))}.border-pink-100{--tw-border-opacity: 1;border-color:rgb(252 231 243 / var(--tw-border-opacity, 1))}.border-pink-200{--tw-border-opacity: 1;border-color:rgb(251 207 232 / var(--tw-border-opacity, 1))}.border-primary{border-color:hsl(var(--primary))}.border-purple-100{--tw-border-opacity: 1;border-color:rgb(243 232 255 / var(--tw-border-opacity, 1))}.border-purple-200{--tw-border-opacity: 1;border-color:rgb(233 213 255 / var(--tw-border-opacity, 1))}.border-purple-300{--tw-border-opacity: 1;border-color:rgb(216 180 254 / var(--tw-border-opacity, 1))}.border-purple-500{--tw-border-opacity: 1;border-color:rgb(168 85 247 / var(--tw-border-opacity, 1))}.border-re-green{border-color:var(--re-green)}.border-red-100{--tw-border-opacity: 1;border-color:rgb(254 226 226 / var(--tw-border-opacity, 1))}.border-red-200{--tw-border-opacity: 1;border-color:rgb(254 202 202 / var(--tw-border-opacity, 1))}.border-red-300{--tw-border-opacity: 1;border-color:rgb(252 165 165 / var(--tw-border-opacity, 1))}.border-red-500{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.border-red-500\/20{border-color:#ef444433}.border-red-600\/50{border-color:#dc262680}.border-red-700{--tw-border-opacity: 1;border-color:rgb(185 28 28 / var(--tw-border-opacity, 1))}.border-sidebar-border{border-color:hsl(var(--sidebar-border))}.border-slate-100{--tw-border-opacity: 1;border-color:rgb(241 245 249 / var(--tw-border-opacity, 1))}.border-slate-200{--tw-border-opacity: 1;border-color:rgb(226 232 240 / var(--tw-border-opacity, 1))}.border-slate-300{--tw-border-opacity: 1;border-color:rgb(203 213 225 / var(--tw-border-opacity, 1))}.border-slate-50{--tw-border-opacity: 1;border-color:rgb(248 250 252 / var(--tw-border-opacity, 1))}.border-slate-900{--tw-border-opacity: 1;border-color:rgb(15 23 42 / var(--tw-border-opacity, 1))}.border-teal-200{--tw-border-opacity: 1;border-color:rgb(153 246 228 / var(--tw-border-opacity, 1))}.border-teal-300{--tw-border-opacity: 1;border-color:rgb(94 234 212 / var(--tw-border-opacity, 1))}.border-transparent{border-color:transparent}.border-white{--tw-border-opacity: 1;border-color:rgb(255 255 255 / var(--tw-border-opacity, 1))}.border-white\/20{border-color:#fff3}.border-white\/30{border-color:#ffffff4d}.border-yellow-200{--tw-border-opacity: 1;border-color:rgb(254 240 138 / var(--tw-border-opacity, 1))}.border-yellow-300{--tw-border-opacity: 1;border-color:rgb(253 224 71 / var(--tw-border-opacity, 1))}.border-yellow-400{--tw-border-opacity: 1;border-color:rgb(250 204 21 / var(--tw-border-opacity, 1))}.border-l-amber-500{--tw-border-opacity: 1;border-left-color:rgb(245 158 11 / var(--tw-border-opacity, 1))}.border-l-blue-500{--tw-border-opacity: 1;border-left-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.border-l-emerald-500{--tw-border-opacity: 1;border-left-color:rgb(16 185 129 / var(--tw-border-opacity, 1))}.border-l-green-500{--tw-border-opacity: 1;border-left-color:rgb(34 197 94 / var(--tw-border-opacity, 1))}.border-l-orange-500{--tw-border-opacity: 1;border-left-color:rgb(249 115 22 / var(--tw-border-opacity, 1))}.border-l-red-500{--tw-border-opacity: 1;border-left-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.border-l-transparent{border-left-color:transparent}.border-t-amber-500{--tw-border-opacity: 1;border-top-color:rgb(245 158 11 / var(--tw-border-opacity, 1))}.border-t-blue-500{--tw-border-opacity: 1;border-top-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.border-t-emerald-500{--tw-border-opacity: 1;border-top-color:rgb(16 185 129 / var(--tw-border-opacity, 1))}.border-t-green-500{--tw-border-opacity: 1;border-top-color:rgb(34 197 94 / var(--tw-border-opacity, 1))}.border-t-re-red{border-top-color:var(--re-red)}.border-t-slate-900{--tw-border-opacity: 1;border-top-color:rgb(15 23 42 / var(--tw-border-opacity, 1))}.border-t-transparent{border-top-color:transparent}.bg-\[\#1a472a\]{--tw-bg-opacity: 1;background-color:rgb(26 71 42 / var(--tw-bg-opacity, 1))}.bg-\[\#2d4a3e\]{--tw-bg-opacity: 1;background-color:rgb(45 74 62 / var(--tw-bg-opacity, 1))}.bg-\[--re-green\]{background-color:var(--re-green)}.bg-accent{background-color:hsl(var(--accent))}.bg-amber-100{--tw-bg-opacity: 1;background-color:rgb(254 243 199 / var(--tw-bg-opacity, 1))}.bg-amber-200{--tw-bg-opacity: 1;background-color:rgb(253 230 138 / var(--tw-bg-opacity, 1))}.bg-amber-400{--tw-bg-opacity: 1;background-color:rgb(251 191 36 / var(--tw-bg-opacity, 1))}.bg-amber-50{--tw-bg-opacity: 1;background-color:rgb(255 251 235 / var(--tw-bg-opacity, 1))}.bg-amber-50\/30{background-color:#fffbeb4d}.bg-amber-50\/50{background-color:#fffbeb80}.bg-amber-50\/70{background-color:#fffbebb3}.bg-amber-500{--tw-bg-opacity: 1;background-color:rgb(245 158 11 / var(--tw-bg-opacity, 1))}.bg-amber-600{--tw-bg-opacity: 1;background-color:rgb(217 119 6 / var(--tw-bg-opacity, 1))}.bg-amber-900\/40{background-color:#78350f66}.bg-background{background-color:hsl(var(--background))}.bg-background\/80{background-color:hsl(var(--background) / .8)}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}.bg-black\/30{background-color:#0000004d}.bg-black\/50{background-color:#00000080}.bg-black\/60{background-color:#0009}.bg-blue-100{--tw-bg-opacity: 1;background-color:rgb(219 234 254 / var(--tw-bg-opacity, 1))}.bg-blue-100\/50{background-color:#dbeafe80}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-blue-50{--tw-bg-opacity: 1;background-color:rgb(239 246 255 / var(--tw-bg-opacity, 1))}.bg-blue-50\/30{background-color:#eff6ff4d}.bg-blue-50\/50{background-color:#eff6ff80}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-border{background-color:hsl(var(--border))}.bg-card{background-color:hsl(var(--card))}.bg-cyan-400{--tw-bg-opacity: 1;background-color:rgb(34 211 238 / var(--tw-bg-opacity, 1))}.bg-destructive{background-color:hsl(var(--destructive))}.bg-emerald-100{--tw-bg-opacity: 1;background-color:rgb(209 250 229 / var(--tw-bg-opacity, 1))}.bg-emerald-100\/50{background-color:#d1fae580}.bg-emerald-50{--tw-bg-opacity: 1;background-color:rgb(236 253 245 / var(--tw-bg-opacity, 1))}.bg-emerald-50\/30{background-color:#ecfdf54d}.bg-emerald-50\/70{background-color:#ecfdf5b3}.bg-emerald-600{--tw-bg-opacity: 1;background-color:rgb(5 150 105 / var(--tw-bg-opacity, 1))}.bg-foreground{background-color:hsl(var(--foreground))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity, 1))}.bg-gray-100\/80{background-color:#f3f4f6cc}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity, 1))}.bg-gray-300{--tw-bg-opacity: 1;background-color:rgb(209 213 219 / var(--tw-bg-opacity, 1))}.bg-gray-400{--tw-bg-opacity: 1;background-color:rgb(156 163 175 / var(--tw-bg-opacity, 1))}.bg-gray-50{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1))}.bg-gray-50\/30{background-color:#f9fafb4d}.bg-gray-50\/50{background-color:#f9fafb80}.bg-gray-50\/80{background-color:#f9fafbcc}.bg-gray-500{--tw-bg-opacity: 1;background-color:rgb(107 114 128 / var(--tw-bg-opacity, 1))}.bg-gray-600{--tw-bg-opacity: 1;background-color:rgb(75 85 99 / var(--tw-bg-opacity, 1))}.bg-gray-700{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.bg-gray-900\/95{background-color:#111827f2}.bg-green-100{--tw-bg-opacity: 1;background-color:rgb(220 252 231 / var(--tw-bg-opacity, 1))}.bg-green-400{--tw-bg-opacity: 1;background-color:rgb(74 222 128 / var(--tw-bg-opacity, 1))}.bg-green-50{--tw-bg-opacity: 1;background-color:rgb(240 253 244 / var(--tw-bg-opacity, 1))}.bg-green-50\/50{background-color:#f0fdf480}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-green-700{--tw-bg-opacity: 1;background-color:rgb(21 128 61 / var(--tw-bg-opacity, 1))}.bg-indigo-100{--tw-bg-opacity: 1;background-color:rgb(224 231 255 / var(--tw-bg-opacity, 1))}.bg-indigo-400{--tw-bg-opacity: 1;background-color:rgb(129 140 248 / var(--tw-bg-opacity, 1))}.bg-indigo-50{--tw-bg-opacity: 1;background-color:rgb(238 242 255 / var(--tw-bg-opacity, 1))}.bg-indigo-600{--tw-bg-opacity: 1;background-color:rgb(79 70 229 / var(--tw-bg-opacity, 1))}.bg-muted{background-color:hsl(var(--muted))}.bg-muted\/30{background-color:hsl(var(--muted) / .3)}.bg-muted\/50{background-color:hsl(var(--muted) / .5)}.bg-orange-100{--tw-bg-opacity: 1;background-color:rgb(255 237 213 / var(--tw-bg-opacity, 1))}.bg-orange-400{--tw-bg-opacity: 1;background-color:rgb(251 146 60 / var(--tw-bg-opacity, 1))}.bg-orange-50{--tw-bg-opacity: 1;background-color:rgb(255 247 237 / var(--tw-bg-opacity, 1))}.bg-orange-50\/50{background-color:#fff7ed80}.bg-orange-500{--tw-bg-opacity: 1;background-color:rgb(249 115 22 / var(--tw-bg-opacity, 1))}.bg-orange-600{--tw-bg-opacity: 1;background-color:rgb(234 88 12 / var(--tw-bg-opacity, 1))}.bg-pink-400{--tw-bg-opacity: 1;background-color:rgb(244 114 182 / var(--tw-bg-opacity, 1))}.bg-pink-50{--tw-bg-opacity: 1;background-color:rgb(253 242 248 / var(--tw-bg-opacity, 1))}.bg-popover{background-color:hsl(var(--popover))}.bg-primary{background-color:hsl(var(--primary))}.bg-primary\/20{background-color:hsl(var(--primary) / .2)}.bg-purple-100{--tw-bg-opacity: 1;background-color:rgb(243 232 255 / var(--tw-bg-opacity, 1))}.bg-purple-400{--tw-bg-opacity: 1;background-color:rgb(192 132 252 / var(--tw-bg-opacity, 1))}.bg-purple-50{--tw-bg-opacity: 1;background-color:rgb(250 245 255 / var(--tw-bg-opacity, 1))}.bg-purple-500{--tw-bg-opacity: 1;background-color:rgb(168 85 247 / var(--tw-bg-opacity, 1))}.bg-purple-600{--tw-bg-opacity: 1;background-color:rgb(147 51 234 / var(--tw-bg-opacity, 1))}.bg-re-green{background-color:var(--re-green)}.bg-re-light-green{background-color:var(--re-light-green)}.bg-re-red{background-color:var(--re-red)}.bg-red-100{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity, 1))}.bg-red-400{--tw-bg-opacity: 1;background-color:rgb(248 113 113 / var(--tw-bg-opacity, 1))}.bg-red-50{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity, 1))}.bg-red-50\/50{background-color:#fef2f280}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-red-500\/10{background-color:#ef44441a}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-red-600\/20{background-color:#dc262633}.bg-red-900\/40{background-color:#7f1d1d66}.bg-secondary{background-color:hsl(var(--secondary))}.bg-sidebar{background-color:hsl(var(--sidebar))}.bg-sidebar-border{background-color:hsl(var(--sidebar-border))}.bg-slate-100{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity, 1))}.bg-slate-100\/80{background-color:#f1f5f9cc}.bg-slate-200{--tw-bg-opacity: 1;background-color:rgb(226 232 240 / var(--tw-bg-opacity, 1))}.bg-slate-400{--tw-bg-opacity: 1;background-color:rgb(148 163 184 / var(--tw-bg-opacity, 1))}.bg-slate-50{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity, 1))}.bg-slate-50\/50{background-color:#f8fafc80}.bg-slate-50\/80{background-color:#f8fafccc}.bg-slate-500{--tw-bg-opacity: 1;background-color:rgb(100 116 139 / var(--tw-bg-opacity, 1))}.bg-slate-600{--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity, 1))}.bg-slate-700{--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity, 1))}.bg-slate-700\/50{background-color:#33415580}.bg-slate-800{--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity, 1))}.bg-slate-900{--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity, 1))}.bg-teal-100{--tw-bg-opacity: 1;background-color:rgb(204 251 241 / var(--tw-bg-opacity, 1))}.bg-teal-400{--tw-bg-opacity: 1;background-color:rgb(45 212 191 / var(--tw-bg-opacity, 1))}.bg-teal-50{--tw-bg-opacity: 1;background-color:rgb(240 253 250 / var(--tw-bg-opacity, 1))}.bg-teal-600{--tw-bg-opacity: 1;background-color:rgb(13 148 136 / var(--tw-bg-opacity, 1))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-white\/10{background-color:#ffffff1a}.bg-white\/20{background-color:#fff3}.bg-white\/50{background-color:#ffffff80}.bg-white\/60{background-color:#fff9}.bg-white\/70{background-color:#ffffffb3}.bg-white\/80{background-color:#fffc}.bg-yellow-100{--tw-bg-opacity: 1;background-color:rgb(254 249 195 / var(--tw-bg-opacity, 1))}.bg-yellow-400{--tw-bg-opacity: 1;background-color:rgb(250 204 21 / var(--tw-bg-opacity, 1))}.bg-yellow-400\/20{background-color:#facc1533}.bg-yellow-400\/30{background-color:#facc154d}.bg-yellow-50{--tw-bg-opacity: 1;background-color:rgb(254 252 232 / var(--tw-bg-opacity, 1))}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity, 1))}.bg-opacity-50{--tw-bg-opacity: .5}.bg-\[radial-gradient\(ellipse_at_top_right\,_var\(--tw-gradient-stops\)\)\]{background-image:radial-gradient(ellipse at top right,var(--tw-gradient-stops))}.bg-\[url\(\'data\:image\/svg\+xml\;base64\,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHZpZXdCb3g9IjAgMCA2MCA2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxnIGZpbGw9IiMxZTIxMmQiIGZpbGwtb3BhY2l0eT0iMC4wNSI\+PGNpcmNsZSBjeD0iMzAiIGN5PSIzMCIgcj0iMzAiLz48L2c\+PC9nPjwvc3ZnPg\=\=\'\)\]{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHZpZXdCb3g9IjAgMCA2MCA2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxnIGZpbGw9IiMxZTIxMmQiIGZpbGwtb3BhY2l0eT0iMC4wNSI+PGNpcmNsZSBjeD0iMzAiIGN5PSIzMCIgcj0iMzAiLz48L2c+PC9nPjwvc3ZnPg==)}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-amber-50{--tw-gradient-from: #fffbeb var(--tw-gradient-from-position);--tw-gradient-to: rgb(255 251 235 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-blue-400{--tw-gradient-from: #60a5fa var(--tw-gradient-from-position);--tw-gradient-to: rgb(96 165 250 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-blue-50{--tw-gradient-from: #eff6ff var(--tw-gradient-from-position);--tw-gradient-to: rgb(239 246 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-blue-500{--tw-gradient-from: #3b82f6 var(--tw-gradient-from-position);--tw-gradient-to: rgb(59 130 246 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-cyan-50{--tw-gradient-from: #ecfeff var(--tw-gradient-from-position);--tw-gradient-to: rgb(236 254 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-emerald-50{--tw-gradient-from: #ecfdf5 var(--tw-gradient-from-position);--tw-gradient-to: rgb(236 253 245 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-emerald-500{--tw-gradient-from: #10b981 var(--tw-gradient-from-position);--tw-gradient-to: rgb(16 185 129 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-emerald-600{--tw-gradient-from: #059669 var(--tw-gradient-from-position);--tw-gradient-to: rgb(5 150 105 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-gray-100{--tw-gradient-from: #f3f4f6 var(--tw-gradient-from-position);--tw-gradient-to: rgb(243 244 246 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-gray-400{--tw-gradient-from: #9ca3af var(--tw-gradient-from-position);--tw-gradient-to: rgb(156 163 175 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-gray-50{--tw-gradient-from: #f9fafb var(--tw-gradient-from-position);--tw-gradient-to: rgb(249 250 251 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-green-50{--tw-gradient-from: #f0fdf4 var(--tw-gradient-from-position);--tw-gradient-to: rgb(240 253 244 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-green-500{--tw-gradient-from: #22c55e var(--tw-gradient-from-position);--tw-gradient-to: rgb(34 197 94 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-indigo-50{--tw-gradient-from: #eef2ff var(--tw-gradient-from-position);--tw-gradient-to: rgb(238 242 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-indigo-500{--tw-gradient-from: #6366f1 var(--tw-gradient-from-position);--tw-gradient-to: rgb(99 102 241 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-orange-50{--tw-gradient-from: #fff7ed var(--tw-gradient-from-position);--tw-gradient-to: rgb(255 247 237 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-orange-500{--tw-gradient-from: #f97316 var(--tw-gradient-from-position);--tw-gradient-to: rgb(249 115 22 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-purple-50{--tw-gradient-from: #faf5ff var(--tw-gradient-from-position);--tw-gradient-to: rgb(250 245 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-purple-500{--tw-gradient-from: #a855f7 var(--tw-gradient-from-position);--tw-gradient-to: rgb(168 85 247 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-re-green{--tw-gradient-from: var(--re-green) var(--tw-gradient-from-position);--tw-gradient-to: rgb(255 255 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-red-50{--tw-gradient-from: #fef2f2 var(--tw-gradient-from-position);--tw-gradient-to: rgb(254 242 242 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-slate-100{--tw-gradient-from: #f1f5f9 var(--tw-gradient-from-position);--tw-gradient-to: rgb(241 245 249 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-slate-50{--tw-gradient-from: #f8fafc var(--tw-gradient-from-position);--tw-gradient-to: rgb(248 250 252 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-slate-600{--tw-gradient-from: #475569 var(--tw-gradient-from-position);--tw-gradient-to: rgb(71 85 105 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-slate-700{--tw-gradient-from: #334155 var(--tw-gradient-from-position);--tw-gradient-to: rgb(51 65 85 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-slate-800{--tw-gradient-from: #1e293b var(--tw-gradient-from-position);--tw-gradient-to: rgb(30 41 59 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-slate-900{--tw-gradient-from: #0f172a var(--tw-gradient-from-position);--tw-gradient-to: rgb(15 23 42 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-teal-500{--tw-gradient-from: #14b8a6 var(--tw-gradient-from-position);--tw-gradient-to: rgb(20 184 166 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-teal-600{--tw-gradient-from: #0d9488 var(--tw-gradient-from-position);--tw-gradient-to: rgb(13 148 136 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-yellow-400{--tw-gradient-from: #facc15 var(--tw-gradient-from-position);--tw-gradient-to: rgb(250 204 21 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-yellow-400\/20{--tw-gradient-from: rgb(250 204 21 / .2) var(--tw-gradient-from-position);--tw-gradient-to: rgb(250 204 21 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-yellow-50{--tw-gradient-from: #fefce8 var(--tw-gradient-from-position);--tw-gradient-to: rgb(254 252 232 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.via-slate-800{--tw-gradient-to: rgb(30 41 59 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), #1e293b var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-transparent{--tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), transparent var(--tw-gradient-via-position), var(--tw-gradient-to)}.to-amber-100{--tw-gradient-to: #fef3c7 var(--tw-gradient-to-position)}.to-amber-50{--tw-gradient-to: #fffbeb var(--tw-gradient-to-position)}.to-blue-100{--tw-gradient-to: #dbeafe var(--tw-gradient-to-position)}.to-blue-100\/50{--tw-gradient-to: rgb(219 234 254 / .5) var(--tw-gradient-to-position)}.to-blue-50{--tw-gradient-to: #eff6ff var(--tw-gradient-to-position)}.to-blue-500{--tw-gradient-to: #3b82f6 var(--tw-gradient-to-position)}.to-blue-600{--tw-gradient-to: #2563eb var(--tw-gradient-to-position)}.to-cyan-50{--tw-gradient-to: #ecfeff var(--tw-gradient-to-position)}.to-cyan-600{--tw-gradient-to: #0891b2 var(--tw-gradient-to-position)}.to-cyan-700{--tw-gradient-to: #0e7490 var(--tw-gradient-to-position)}.to-emerald-50{--tw-gradient-to: #ecfdf5 var(--tw-gradient-to-position)}.to-emerald-600{--tw-gradient-to: #059669 var(--tw-gradient-to-position)}.to-emerald-700{--tw-gradient-to: #047857 var(--tw-gradient-to-position)}.to-gray-100{--tw-gradient-to: #f3f4f6 var(--tw-gradient-to-position)}.to-gray-100\/50{--tw-gradient-to: rgb(243 244 246 / .5) var(--tw-gradient-to-position)}.to-gray-200{--tw-gradient-to: #e5e7eb var(--tw-gradient-to-position)}.to-gray-50{--tw-gradient-to: #f9fafb var(--tw-gradient-to-position)}.to-gray-500{--tw-gradient-to: #6b7280 var(--tw-gradient-to-position)}.to-green-100{--tw-gradient-to: #dcfce7 var(--tw-gradient-to-position)}.to-green-600{--tw-gradient-to: #16a34a var(--tw-gradient-to-position)}.to-indigo-50{--tw-gradient-to: #eef2ff var(--tw-gradient-to-position)}.to-indigo-600{--tw-gradient-to: #4f46e5 var(--tw-gradient-to-position)}.to-orange-100{--tw-gradient-to: #ffedd5 var(--tw-gradient-to-position)}.to-orange-50{--tw-gradient-to: #fff7ed var(--tw-gradient-to-position)}.to-pink-50{--tw-gradient-to: #fdf2f8 var(--tw-gradient-to-position)}.to-purple-100{--tw-gradient-to: #f3e8ff var(--tw-gradient-to-position)}.to-purple-50{--tw-gradient-to: #faf5ff var(--tw-gradient-to-position)}.to-purple-600{--tw-gradient-to: #9333ea var(--tw-gradient-to-position)}.to-red-100{--tw-gradient-to: #fee2e2 var(--tw-gradient-to-position)}.to-red-50{--tw-gradient-to: #fef2f2 var(--tw-gradient-to-position)}.to-red-600{--tw-gradient-to: #dc2626 var(--tw-gradient-to-position)}.to-rose-50{--tw-gradient-to: #fff1f2 var(--tw-gradient-to-position)}.to-slate-100{--tw-gradient-to: #f1f5f9 var(--tw-gradient-to-position)}.to-slate-100\/50{--tw-gradient-to: rgb(241 245 249 / .5) var(--tw-gradient-to-position)}.to-slate-50{--tw-gradient-to: #f8fafc var(--tw-gradient-to-position)}.to-slate-500{--tw-gradient-to: #64748b var(--tw-gradient-to-position)}.to-slate-700{--tw-gradient-to: #334155 var(--tw-gradient-to-position)}.to-slate-800{--tw-gradient-to: #1e293b var(--tw-gradient-to-position)}.to-slate-900{--tw-gradient-to: #0f172a var(--tw-gradient-to-position)}.to-teal-50{--tw-gradient-to: #f0fdfa var(--tw-gradient-to-position)}.to-teal-600{--tw-gradient-to: #0d9488 var(--tw-gradient-to-position)}.to-transparent{--tw-gradient-to: transparent var(--tw-gradient-to-position)}.to-violet-50{--tw-gradient-to: #f5f3ff var(--tw-gradient-to-position)}.to-white{--tw-gradient-to: #fff var(--tw-gradient-to-position)}.to-yellow-100\/50{--tw-gradient-to: rgb(254 249 195 / .5) var(--tw-gradient-to-position)}.to-yellow-500{--tw-gradient-to: #eab308 var(--tw-gradient-to-position)}.fill-current{fill:currentColor}.fill-primary{fill:hsl(var(--primary))}.object-contain{-o-object-fit:contain;object-fit:contain}.p-0{padding:0}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-12{padding:3rem}.p-2{padding:.5rem}.p-2\.5{padding:.625rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.p-\[3px\]{padding:3px}.p-px{padding:1px}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0{padding-top:0;padding-bottom:0}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-1\.5{padding-bottom:.375rem}.pb-10{padding-bottom:2.5rem}.pb-2{padding-bottom:.5rem}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-5{padding-bottom:1.25rem}.pb-6{padding-bottom:1.5rem}.pb-8{padding-bottom:2rem}.pl-1{padding-left:.25rem}.pl-1\.5{padding-left:.375rem}.pl-10{padding-left:2.5rem}.pl-2{padding-left:.5rem}.pl-3{padding-left:.75rem}.pl-4{padding-left:1rem}.pl-5{padding-left:1.25rem}.pl-6{padding-left:1.5rem}.pl-8{padding-left:2rem}.pl-9{padding-left:2.25rem}.pr-1{padding-right:.25rem}.pr-10{padding-right:2.5rem}.pr-2{padding-right:.5rem}.pr-2\.5{padding-right:.625rem}.pr-20{padding-right:5rem}.pr-4{padding-right:1rem}.pr-6{padding-right:1.5rem}.pr-8{padding-right:2rem}.pt-0{padding-top:0}.pt-1{padding-top:.25rem}.pt-1\.5{padding-top:.375rem}.pt-12{padding-top:3rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.pt-6{padding-top:1.5rem}.pt-8{padding-top:2rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-sans{font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-\[0\.8rem\]{font-size:.8rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.text-\[8px\]{font-size:8px}.text-\[9px\]{font-size:9px}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-black{font-weight:900}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.capitalize{text-transform:capitalize}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing: tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-\[1\.1\]{line-height:1.1}.leading-none{line-height:1}.leading-tight{line-height:1.25}.tracking-tight{letter-spacing:-.025em}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.tracking-widest{letter-spacing:.1em}.\!text-amber-600{--tw-text-opacity: 1 !important;color:rgb(217 119 6 / var(--tw-text-opacity, 1))!important}.\!text-amber-800{--tw-text-opacity: 1 !important;color:rgb(146 64 14 / var(--tw-text-opacity, 1))!important}.\!text-blue-700{--tw-text-opacity: 1 !important;color:rgb(29 78 216 / var(--tw-text-opacity, 1))!important}.\!text-emerald-700{--tw-text-opacity: 1 !important;color:rgb(4 120 87 / var(--tw-text-opacity, 1))!important}.\!text-gray-600{--tw-text-opacity: 1 !important;color:rgb(75 85 99 / var(--tw-text-opacity, 1))!important}.\!text-gray-700{--tw-text-opacity: 1 !important;color:rgb(55 65 81 / var(--tw-text-opacity, 1))!important}.\!text-green-800{--tw-text-opacity: 1 !important;color:rgb(22 101 52 / var(--tw-text-opacity, 1))!important}.\!text-purple-600{--tw-text-opacity: 1 !important;color:rgb(147 51 234 / var(--tw-text-opacity, 1))!important}.\!text-red-600{--tw-text-opacity: 1 !important;color:rgb(220 38 38 / var(--tw-text-opacity, 1))!important}.\!text-red-800{--tw-text-opacity: 1 !important;color:rgb(153 27 27 / var(--tw-text-opacity, 1))!important}.text-\[\#2d4a3e\]{--tw-text-opacity: 1;color:rgb(45 74 62 / var(--tw-text-opacity, 1))}.text-\[--re-green\]{color:var(--re-green)}.text-accent-foreground{color:hsl(var(--accent-foreground))}.text-amber-200{--tw-text-opacity: 1;color:rgb(253 230 138 / var(--tw-text-opacity, 1))}.text-amber-500{--tw-text-opacity: 1;color:rgb(245 158 11 / var(--tw-text-opacity, 1))}.text-amber-600{--tw-text-opacity: 1;color:rgb(217 119 6 / var(--tw-text-opacity, 1))}.text-amber-700{--tw-text-opacity: 1;color:rgb(180 83 9 / var(--tw-text-opacity, 1))}.text-amber-800{--tw-text-opacity: 1;color:rgb(146 64 14 / var(--tw-text-opacity, 1))}.text-amber-900{--tw-text-opacity: 1;color:rgb(120 53 15 / var(--tw-text-opacity, 1))}.text-blue-500{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity, 1))}.text-blue-600{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity, 1))}.text-blue-700{--tw-text-opacity: 1;color:rgb(29 78 216 / var(--tw-text-opacity, 1))}.text-blue-800{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity, 1))}.text-blue-900{--tw-text-opacity: 1;color:rgb(30 58 138 / var(--tw-text-opacity, 1))}.text-card-foreground{color:hsl(var(--card-foreground))}.text-current{color:currentColor}.text-cyan-600{--tw-text-opacity: 1;color:rgb(8 145 178 / var(--tw-text-opacity, 1))}.text-destructive{color:hsl(var(--destructive))}.text-destructive-foreground{color:hsl(var(--destructive-foreground))}.text-emerald-600{--tw-text-opacity: 1;color:rgb(5 150 105 / var(--tw-text-opacity, 1))}.text-emerald-700{--tw-text-opacity: 1;color:rgb(4 120 87 / var(--tw-text-opacity, 1))}.text-emerald-800{--tw-text-opacity: 1;color:rgb(6 95 70 / var(--tw-text-opacity, 1))}.text-emerald-900{--tw-text-opacity: 1;color:rgb(6 78 59 / var(--tw-text-opacity, 1))}.text-foreground{color:hsl(var(--foreground))}.text-gray-100{--tw-text-opacity: 1;color:rgb(243 244 246 / var(--tw-text-opacity, 1))}.text-gray-200{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity, 1))}.text-gray-800{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity, 1))}.text-gray-900{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-green-600{--tw-text-opacity: 1;color:rgb(22 163 74 / var(--tw-text-opacity, 1))}.text-green-700{--tw-text-opacity: 1;color:rgb(21 128 61 / var(--tw-text-opacity, 1))}.text-green-800{--tw-text-opacity: 1;color:rgb(22 101 52 / var(--tw-text-opacity, 1))}.text-green-900{--tw-text-opacity: 1;color:rgb(20 83 45 / var(--tw-text-opacity, 1))}.text-indigo-600{--tw-text-opacity: 1;color:rgb(79 70 229 / var(--tw-text-opacity, 1))}.text-indigo-700{--tw-text-opacity: 1;color:rgb(67 56 202 / var(--tw-text-opacity, 1))}.text-indigo-800{--tw-text-opacity: 1;color:rgb(55 48 163 / var(--tw-text-opacity, 1))}.text-indigo-900{--tw-text-opacity: 1;color:rgb(49 46 129 / var(--tw-text-opacity, 1))}.text-muted-foreground{color:hsl(var(--muted-foreground))}.text-orange-600{--tw-text-opacity: 1;color:rgb(234 88 12 / var(--tw-text-opacity, 1))}.text-orange-700{--tw-text-opacity: 1;color:rgb(194 65 12 / var(--tw-text-opacity, 1))}.text-orange-800{--tw-text-opacity: 1;color:rgb(154 52 18 / var(--tw-text-opacity, 1))}.text-orange-900{--tw-text-opacity: 1;color:rgb(124 45 18 / var(--tw-text-opacity, 1))}.text-pink-600{--tw-text-opacity: 1;color:rgb(219 39 119 / var(--tw-text-opacity, 1))}.text-pink-700{--tw-text-opacity: 1;color:rgb(190 24 93 / var(--tw-text-opacity, 1))}.text-pink-900{--tw-text-opacity: 1;color:rgb(131 24 67 / var(--tw-text-opacity, 1))}.text-popover-foreground{color:hsl(var(--popover-foreground))}.text-primary{color:hsl(var(--primary))}.text-primary-foreground{color:hsl(var(--primary-foreground))}.text-purple-500{--tw-text-opacity: 1;color:rgb(168 85 247 / var(--tw-text-opacity, 1))}.text-purple-600{--tw-text-opacity: 1;color:rgb(147 51 234 / var(--tw-text-opacity, 1))}.text-purple-700{--tw-text-opacity: 1;color:rgb(126 34 206 / var(--tw-text-opacity, 1))}.text-purple-800{--tw-text-opacity: 1;color:rgb(107 33 168 / var(--tw-text-opacity, 1))}.text-purple-900{--tw-text-opacity: 1;color:rgb(88 28 135 / var(--tw-text-opacity, 1))}.text-re-green{color:var(--re-green)}.text-re-red{color:var(--re-red)}.text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}.text-red-300{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-red-600{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity, 1))}.text-red-700{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity, 1))}.text-red-800{--tw-text-opacity: 1;color:rgb(153 27 27 / var(--tw-text-opacity, 1))}.text-red-900{--tw-text-opacity: 1;color:rgb(127 29 29 / var(--tw-text-opacity, 1))}.text-secondary-foreground{color:hsl(var(--secondary-foreground))}.text-sidebar-foreground{color:hsl(var(--sidebar-foreground))}.text-sidebar-foreground\/70{color:hsl(var(--sidebar-foreground) / .7)}.text-slate-200{--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1))}.text-slate-300{--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity, 1))}.text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.text-slate-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.text-slate-600{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity, 1))}.text-slate-700{--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity, 1))}.text-slate-800{--tw-text-opacity: 1;color:rgb(30 41 59 / var(--tw-text-opacity, 1))}.text-slate-900{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity, 1))}.text-teal-600{--tw-text-opacity: 1;color:rgb(13 148 136 / var(--tw-text-opacity, 1))}.text-teal-700{--tw-text-opacity: 1;color:rgb(15 118 110 / var(--tw-text-opacity, 1))}.text-teal-800{--tw-text-opacity: 1;color:rgb(17 94 89 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-white\/70{color:#ffffffb3}.text-white\/80{color:#fffc}.text-white\/90{color:#ffffffe6}.text-yellow-400{--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity, 1))}.text-yellow-500{--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity, 1))}.text-yellow-600{--tw-text-opacity: 1;color:rgb(202 138 4 / var(--tw-text-opacity, 1))}.text-yellow-700{--tw-text-opacity: 1;color:rgb(161 98 7 / var(--tw-text-opacity, 1))}.text-yellow-800{--tw-text-opacity: 1;color:rgb(133 77 14 / var(--tw-text-opacity, 1))}.text-yellow-900{--tw-text-opacity: 1;color:rgb(113 63 18 / var(--tw-text-opacity, 1))}.underline{text-decoration-line:underline}.line-through{text-decoration-line:line-through}.underline-offset-4{text-underline-offset:4px}.opacity-0{opacity:0}.opacity-100{opacity:1}.opacity-20{opacity:.2}.opacity-40{opacity:.4}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.opacity-75{opacity:.75}.opacity-85{opacity:.85}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_0_1px_hsl\(var\(--sidebar-border\)\)\]{--tw-shadow: 0 0 0 1px hsl(var(--sidebar-border));--tw-shadow-colored: 0 0 0 1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-none{--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-blue-100\/50{--tw-shadow-color: rgb(219 234 254 / .5);--tw-shadow: var(--tw-shadow-colored)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.ring{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-0{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-2{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-4{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-blue-200{--tw-ring-opacity: 1;--tw-ring-color: rgb(191 219 254 / var(--tw-ring-opacity, 1))}.ring-blue-600{--tw-ring-opacity: 1;--tw-ring-color: rgb(37 99 235 / var(--tw-ring-opacity, 1))}.ring-destructive\/20{--tw-ring-color: hsl(var(--destructive) / .2)}.ring-green-500\/30{--tw-ring-color: rgb(34 197 94 / .3)}.ring-orange-500{--tw-ring-opacity: 1;--tw-ring-color: rgb(249 115 22 / var(--tw-ring-opacity, 1))}.ring-ring\/50{--tw-ring-color: hsl(var(--ring) / .5)}.ring-sidebar-ring{--tw-ring-color: hsl(var(--sidebar-ring))}.ring-white{--tw-ring-opacity: 1;--tw-ring-color: rgb(255 255 255 / var(--tw-ring-opacity, 1))}.ring-white\/20{--tw-ring-color: rgb(255 255 255 / .2)}.ring-yellow-200{--tw-ring-opacity: 1;--tw-ring-color: rgb(254 240 138 / var(--tw-ring-opacity, 1))}.ring-offset-1{--tw-ring-offset-width: 1px}.ring-offset-background{--tw-ring-offset-color: hsl(var(--background))}.ring-offset-green-50{--tw-ring-offset-color: #f0fdf4}.blur{--tw-blur: blur(8px);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.blur-3xl{--tw-blur: blur(64px);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.grayscale-\[0\.5\]{--tw-grayscale: grayscale(.5);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur{--tw-backdrop-blur: blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-\[2px\]{--tw-backdrop-blur: blur(2px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-sm{--tw-backdrop-blur: blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-xl{--tw-backdrop-blur: blur(24px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[color\,box-shadow\]{transition-property:color,box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[left\,right\,width\]{transition-property:left,right,width;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[margin\,opacity\]{transition-property:margin,opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[width\,height\,padding\]{transition-property:width,height,padding;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[width\]{transition-property:width;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-none{transition-property:none}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.delay-1000{transition-delay:1s}.duration-1000{transition-duration:1s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.duration-500{transition-duration:.5s}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-linear{transition-timing-function:linear}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.text-balance{text-wrap:balance}.line-clamp-1{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:1}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.line-clamp-3{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3}.leading-relaxed{line-height:1.625}.flex-start{align-items:flex-start}.text-re-green,.group:hover .group-hover\:text-re-green{color:var(--re-green)}.bg-gradient-to-br{background-image:linear-gradient(to bottom right,var(--tw-gradient-stops))}.space-y-6>*+*{margin-top:1.5rem}.space-y-4>*+*{margin-top:1rem}.min-w-0{min-width:0px}.break-words{overflow-wrap:break-word;word-wrap:break-word}.flex-shrink-0{flex-shrink:0}.sidebar-trigger{z-index:10}@media (max-width: 768px){.sidebar-mobile.\!open{left:0!important}.sidebar-mobile{position:fixed;top:0;left:-100%;width:16rem;height:100vh;z-index:50;transition:left .3s ease}.sidebar-mobile.open{left:0}.main-content-mobile{width:100%;margin-left:0}}.sidebar-container{transition:width .3s ease-in-out;overflow:hidden}.sidebar-content{width:16rem;height:100vh;position:relative}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.3s}.flex-1{flex:1 1 0%}.min-w-0{min-width:0}.bg-destructive{background-color:#dc2626!important}.border-gray-200{border-color:#e5e7eb!important}.border-gray-300{border-color:#d1d5db!important}.bg-white,[data-slot=dialog-content]{background-color:#fff!important}.overflow-y-auto::-webkit-scrollbar{width:6px}.overflow-y-auto::-webkit-scrollbar-track{background:transparent}.overflow-y-auto::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:3px}.overflow-y-auto::-webkit-scrollbar-thumb:hover{background:#94a3b8}.overflow-y-auto{scrollbar-width:thin;scrollbar-color:rgb(203 213 225) transparent}@custom-variant dark (&:is(.dark *));:root{--font-size: 16px;--background: 35 8% 96%;--foreground: 0 0% 10%;--card: 0 0% 100%;--card-foreground: 0 0% 10%;--popover: 0 0% 100%;--popover-foreground: 0 0% 10%;--primary: 158 24% 24%;--primary-foreground: 0 0% 100%;--secondary: 150 7% 57%;--secondary-foreground: 0 0% 100%;--muted: 35 8% 90%;--muted-foreground: 0 0% 42%;--accent: 43 62% 49%;--accent-foreground: 0 0% 10%;--destructive: 0 84% 50%;--destructive-foreground: 0 0% 100%;--border: 35 8% 82%;--input: transparent;--input-background: 0 0% 100%;--switch-background: 35 8% 82%;--font-weight-medium: 500;--font-weight-normal: 400;--ring: 158 24% 24%;--chart-1: 158 24% 24%;--chart-2: 150 7% 57%;--chart-3: 43 62% 49%;--chart-4: 0 84% 50%;--chart-5: 0 0% 42%;--radius: .625rem;--sidebar: 0 0% 10%;--sidebar-foreground: 0 0% 100%;--sidebar-primary: 158 24% 24%;--sidebar-primary-foreground: 0 0% 100%;--sidebar-accent: 158 24% 24%;--sidebar-accent-foreground: 0 0% 100%;--sidebar-border: 0 0% 20%;--sidebar-ring: 158 24% 24%;--re-green: #2d4a3e;--re-gold: #c9b037;--re-dark: #1a1a1a;--re-light-green: #8a9b8e;--re-red: #DA281C}.dark{--background: oklch(.145 0 0);--foreground: oklch(.985 0 0);--card: oklch(.145 0 0);--card-foreground: oklch(.985 0 0);--popover: oklch(.145 0 0);--popover-foreground: oklch(.985 0 0);--primary: oklch(.985 0 0);--primary-foreground: oklch(.205 0 0);--secondary: oklch(.269 0 0);--secondary-foreground: oklch(.985 0 0);--muted: oklch(.269 0 0);--muted-foreground: oklch(.708 0 0);--accent: oklch(.269 0 0);--accent-foreground: oklch(.985 0 0);--destructive: oklch(.396 .141 25.723);--destructive-foreground: oklch(.637 .237 25.331);--border: oklch(.269 0 0);--input: oklch(.269 0 0);--ring: oklch(.439 0 0);--font-weight-medium: 500;--font-weight-normal: 400;--chart-1: oklch(.488 .243 264.376);--chart-2: oklch(.696 .17 162.48);--chart-3: oklch(.769 .188 70.08);--chart-4: oklch(.627 .265 303.9);--chart-5: oklch(.645 .246 16.439);--sidebar: oklch(.205 0 0);--sidebar-foreground: oklch(.985 0 0);--sidebar-primary: oklch(.488 .243 264.376);--sidebar-primary-foreground: oklch(.985 0 0);--sidebar-accent: oklch(.269 0 0);--sidebar-accent-foreground: oklch(.985 0 0);--sidebar-border: oklch(.269 0 0);--sidebar-ring: oklch(.439 0 0)}@theme inline{ --color-background: var(--background); --color-foreground: var(--foreground); --color-card: var(--card); --color-card-foreground: var(--card-foreground); --color-popover: var(--popover); --color-popover-foreground: var(--popover-foreground); --color-primary: var(--primary); --color-primary-foreground: var(--primary-foreground); --color-secondary: var(--secondary); --color-secondary-foreground: var(--secondary-foreground); --color-muted: var(--muted); --color-muted-foreground: var(--muted-foreground); --color-accent: var(--accent); --color-accent-foreground: var(--accent-foreground); --color-destructive: var(--destructive); --color-destructive-foreground: var(--destructive-foreground); --color-border: var(--border); --color-input: var(--input); --color-input-background: var(--input-background); --color-switch-background: var(--switch-background); --color-ring: var(--ring); --color-chart-1: var(--chart-1); --color-chart-2: var(--chart-2); --color-chart-3: var(--chart-3); --color-chart-4: var(--chart-4); --color-chart-5: var(--chart-5); --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) + 4px); --color-sidebar: var(--sidebar); --color-sidebar-foreground: var(--sidebar-foreground); --color-sidebar-primary: var(--sidebar-primary); --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); --color-sidebar-accent: var(--sidebar-accent); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-border: var(--sidebar-border); --color-sidebar-ring: var(--sidebar-ring); --color-re-green: var(--re-green); --color-re-gold: var(--re-gold); --color-re-dark: var(--re-dark); --color-re-light-green: var(--re-light-green); --color-re-red: var(--re-red); }html{font-size:var(--font-size)}.hover\:border-gray-400:hover{border-color:#9ca3af!important;border-width:1px!important;border-style:solid!important}.hover\:border-gray-500:hover{border-color:#6b7280!important}.focus\:bg-white:focusheader{background-color:#fff!important}.disabled\:text-gray-400:disabled{color:#9ca3af!important}.data-\[state\=active\]\:bg-white[data-state="active"]header{background-color:#fff!important}.\[\&_\.table-wrapper_table\]\:border-gray-300 .table-wrapper table,.\[\&_\.table-wrapper_table_td\]\:border-gray-300 .table-wrapper table td,.\[\&_\.table-wrapper_table_th\]\:border-gray-300 .table-wrapper table th,.\[\&_table\]\:border-gray-300 table,.\[\&_table_td\]\:border-gray-300 table td,.\[\&_table_th\]\:border-gray-300 table th{border-color:#d1d5db!important}.selection\:bg-primary *::-moz-selection{background-color:hsl(var(--primary))}.selection\:bg-primary *::selection{background-color:hsl(var(--primary))}.selection\:text-primary-foreground *::-moz-selection{color:hsl(var(--primary-foreground))}.selection\:text-primary-foreground *::selection{color:hsl(var(--primary-foreground))}.selection\:bg-primary::-moz-selection{background-color:hsl(var(--primary))}.selection\:bg-primary::selection{background-color:hsl(var(--primary))}.selection\:text-primary-foreground::-moz-selection{color:hsl(var(--primary-foreground))}.selection\:text-primary-foreground::selection{color:hsl(var(--primary-foreground))}.file\:inline-flex::file-selector-button{display:inline-flex}.file\:h-7::file-selector-button{height:1.75rem}.file\:border-0::file-selector-button{border-width:0px}.file\:bg-transparent::file-selector-button{background-color:transparent}.file\:text-sm::file-selector-button{font-size:.875rem;line-height:1.25rem}.file\:font-medium::file-selector-button{font-weight:500}.file\:text-foreground::file-selector-button{color:hsl(var(--foreground))}.placeholder\:text-muted-foreground::-moz-placeholder{color:hsl(var(--muted-foreground))}.placeholder\:text-muted-foreground::placeholder{color:hsl(var(--muted-foreground))}.placeholder\:text-slate-400::-moz-placeholder{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.placeholder\:text-slate-400::placeholder{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:-inset-2:after{content:var(--tw-content);top:-.5rem;right:-.5rem;bottom:-.5rem;left:-.5rem}.after\:inset-y-0:after{content:var(--tw-content);top:0;bottom:0}.after\:left-1\/2:after{content:var(--tw-content);left:50%}.after\:w-1:after{content:var(--tw-content);width:.25rem}.after\:w-\[2px\]:after{content:var(--tw-content);width:2px}.after\:-translate-x-1\/2:after{content:var(--tw-content);--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.first\:rounded-l-md:first-child{border-top-left-radius:calc(var(--radius) - 2px);border-bottom-left-radius:calc(var(--radius) - 2px)}.first\:border-l:first-child{border-left-width:1px}.last\:mb-0:last-child{margin-bottom:0}.last\:flex-none:last-child{flex:none}.last\:rounded-r-md:last-child{border-top-right-radius:calc(var(--radius) - 2px);border-bottom-right-radius:calc(var(--radius) - 2px)}.last\:border-0:last-child{border-width:0px}.last\:border-b-0:last-child{border-bottom-width:0px}.last\:pb-0:last-child{padding-bottom:0}.empty\:before\:pointer-events-none:empty:before{content:var(--tw-content);pointer-events:none}.empty\:before\:text-muted-foreground:empty:before{content:var(--tw-content);color:hsl(var(--muted-foreground))}.empty\:before\:content-\[attr\(data-placeholder\)\]:empty:before{--tw-content: attr(data-placeholder);content:var(--tw-content)}.focus-within\:relative:focus-within{position:relative}.focus-within\:z-20:focus-within{z-index:20}.focus-within\:border-blue-500:focus-within{--tw-border-opacity: 1;border-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.hover\:scale-105:hover{--tw-scale-x: 1.05;--tw-scale-y: 1.05;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:scale-110:hover{--tw-scale-x: 1.1;--tw-scale-y: 1.1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:scale-\[1\.002\]:hover{--tw-scale-x: 1.002;--tw-scale-y: 1.002;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:border-amber-300:hover{--tw-border-opacity: 1;border-color:rgb(252 211 77 / var(--tw-border-opacity, 1))}.hover\:border-blue-200:hover{--tw-border-opacity: 1;border-color:rgb(191 219 254 / var(--tw-border-opacity, 1))}.hover\:border-blue-300:hover{--tw-border-opacity: 1;border-color:rgb(147 197 253 / var(--tw-border-opacity, 1))}.hover\:border-blue-400:hover{--tw-border-opacity: 1;border-color:rgb(96 165 250 / var(--tw-border-opacity, 1))}.hover\:border-blue-500:hover{--tw-border-opacity: 1;border-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.hover\:border-blue-600:hover{--tw-border-opacity: 1;border-color:rgb(37 99 235 / var(--tw-border-opacity, 1))}.hover\:border-gray-200:hover{--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity, 1))}.hover\:border-gray-300:hover{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity, 1))}.hover\:border-gray-400:hover{--tw-border-opacity: 1;border-color:rgb(156 163 175 / var(--tw-border-opacity, 1))}.hover\:border-gray-500:hover{--tw-border-opacity: 1;border-color:rgb(107 114 128 / var(--tw-border-opacity, 1))}.hover\:border-green-600:hover{--tw-border-opacity: 1;border-color:rgb(22 163 74 / var(--tw-border-opacity, 1))}.hover\:border-re-green:hover{border-color:var(--re-green)}.hover\:border-red-200:hover{--tw-border-opacity: 1;border-color:rgb(254 202 202 / var(--tw-border-opacity, 1))}.hover\:border-slate-300:hover{--tw-border-opacity: 1;border-color:rgb(203 213 225 / var(--tw-border-opacity, 1))}.hover\:border-slate-400:hover{--tw-border-opacity: 1;border-color:rgb(148 163 184 / var(--tw-border-opacity, 1))}.hover\:bg-\[\#152e1f\]:hover{--tw-bg-opacity: 1;background-color:rgb(21 46 31 / var(--tw-bg-opacity, 1))}.hover\:bg-\[\#1f3329\]:hover{--tw-bg-opacity: 1;background-color:rgb(31 51 41 / var(--tw-bg-opacity, 1))}.hover\:bg-\[--re-green-dark\]:hover{background-color:var(--re-green-dark)}.hover\:bg-accent:hover{background-color:hsl(var(--accent))}.hover\:bg-amber-100:hover{--tw-bg-opacity: 1;background-color:rgb(254 243 199 / var(--tw-bg-opacity, 1))}.hover\:bg-amber-100\/50:hover{background-color:#fef3c780}.hover\:bg-amber-200:hover{--tw-bg-opacity: 1;background-color:rgb(253 230 138 / var(--tw-bg-opacity, 1))}.hover\:bg-amber-50:hover{--tw-bg-opacity: 1;background-color:rgb(255 251 235 / var(--tw-bg-opacity, 1))}.hover\:bg-amber-700:hover{--tw-bg-opacity: 1;background-color:rgb(180 83 9 / var(--tw-bg-opacity, 1))}.hover\:bg-blue-100:hover{--tw-bg-opacity: 1;background-color:rgb(219 234 254 / var(--tw-bg-opacity, 1))}.hover\:bg-blue-100\/30:hover{background-color:#dbeafe4d}.hover\:bg-blue-400:hover{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.hover\:bg-blue-50:hover{--tw-bg-opacity: 1;background-color:rgb(239 246 255 / var(--tw-bg-opacity, 1))}.hover\:bg-blue-700:hover{--tw-bg-opacity: 1;background-color:rgb(29 78 216 / var(--tw-bg-opacity, 1))}.hover\:bg-destructive\/90:hover{background-color:hsl(var(--destructive) / .9)}.hover\:bg-emerald-100:hover{--tw-bg-opacity: 1;background-color:rgb(209 250 229 / var(--tw-bg-opacity, 1))}.hover\:bg-emerald-700:hover{--tw-bg-opacity: 1;background-color:rgb(4 120 87 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-100:hover{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-200:hover{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-50:hover{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-50\/50:hover{background-color:#f9fafb80}.hover\:bg-gray-700:hover{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-800:hover{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-900:hover{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity, 1))}.hover\:bg-green-100:hover{--tw-bg-opacity: 1;background-color:rgb(220 252 231 / var(--tw-bg-opacity, 1))}.hover\:bg-green-50:hover{--tw-bg-opacity: 1;background-color:rgb(240 253 244 / var(--tw-bg-opacity, 1))}.hover\:bg-green-600:hover{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.hover\:bg-green-700:hover{--tw-bg-opacity: 1;background-color:rgb(21 128 61 / var(--tw-bg-opacity, 1))}.hover\:bg-indigo-700:hover{--tw-bg-opacity: 1;background-color:rgb(67 56 202 / var(--tw-bg-opacity, 1))}.hover\:bg-muted:hover{background-color:hsl(var(--muted))}.hover\:bg-muted\/30:hover{background-color:hsl(var(--muted) / .3)}.hover\:bg-muted\/50:hover{background-color:hsl(var(--muted) / .5)}.hover\:bg-orange-100:hover{--tw-bg-opacity: 1;background-color:rgb(255 237 213 / var(--tw-bg-opacity, 1))}.hover\:bg-orange-50:hover{--tw-bg-opacity: 1;background-color:rgb(255 247 237 / var(--tw-bg-opacity, 1))}.hover\:bg-orange-700:hover{--tw-bg-opacity: 1;background-color:rgb(194 65 12 / var(--tw-bg-opacity, 1))}.hover\:bg-primary:hover{background-color:hsl(var(--primary))}.hover\:bg-primary\/90:hover{background-color:hsl(var(--primary) / .9)}.hover\:bg-purple-100:hover{--tw-bg-opacity: 1;background-color:rgb(243 232 255 / var(--tw-bg-opacity, 1))}.hover\:bg-purple-50:hover{--tw-bg-opacity: 1;background-color:rgb(250 245 255 / var(--tw-bg-opacity, 1))}.hover\:bg-purple-700:hover{--tw-bg-opacity: 1;background-color:rgb(126 34 206 / var(--tw-bg-opacity, 1))}.hover\:bg-red-100:hover{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity, 1))}.hover\:bg-red-50:hover{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity, 1))}.hover\:bg-red-700:hover{--tw-bg-opacity: 1;background-color:rgb(185 28 28 / var(--tw-bg-opacity, 1))}.hover\:bg-secondary\/80:hover{background-color:hsl(var(--secondary) / .8)}.hover\:bg-sidebar-accent:hover{background-color:hsl(var(--sidebar-accent))}.hover\:bg-slate-100:hover{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity, 1))}.hover\:bg-slate-200:hover{--tw-bg-opacity: 1;background-color:rgb(226 232 240 / var(--tw-bg-opacity, 1))}.hover\:bg-slate-50:hover{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity, 1))}.hover\:bg-slate-50\/50:hover{background-color:#f8fafc80}.hover\:bg-slate-700:hover{--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity, 1))}.hover\:bg-slate-800:hover{--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity, 1))}.hover\:bg-teal-700:hover{--tw-bg-opacity: 1;background-color:rgb(15 118 110 / var(--tw-bg-opacity, 1))}.hover\:bg-white\/20:hover{background-color:#fff3}.hover\:bg-yellow-400:hover{--tw-bg-opacity: 1;background-color:rgb(250 204 21 / var(--tw-bg-opacity, 1))}.hover\:text-accent-foreground:hover{color:hsl(var(--accent-foreground))}.hover\:text-amber-700:hover{--tw-text-opacity: 1;color:rgb(180 83 9 / var(--tw-text-opacity, 1))}.hover\:text-amber-800:hover{--tw-text-opacity: 1;color:rgb(146 64 14 / var(--tw-text-opacity, 1))}.hover\:text-blue-600:hover{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity, 1))}.hover\:text-blue-700:hover{--tw-text-opacity: 1;color:rgb(29 78 216 / var(--tw-text-opacity, 1))}.hover\:text-blue-800:hover{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity, 1))}.hover\:text-destructive:hover{color:hsl(var(--destructive))}.hover\:text-foreground:hover{color:hsl(var(--foreground))}.hover\:text-gray-700:hover{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity, 1))}.hover\:text-gray-900:hover{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity, 1))}.hover\:text-green-700:hover{--tw-text-opacity: 1;color:rgb(21 128 61 / var(--tw-text-opacity, 1))}.hover\:text-green-900:hover{--tw-text-opacity: 1;color:rgb(20 83 45 / var(--tw-text-opacity, 1))}.hover\:text-muted-foreground:hover{color:hsl(var(--muted-foreground))}.hover\:text-orange-900:hover{--tw-text-opacity: 1;color:rgb(124 45 18 / var(--tw-text-opacity, 1))}.hover\:text-primary-foreground:hover{color:hsl(var(--primary-foreground))}.hover\:text-purple-600:hover{--tw-text-opacity: 1;color:rgb(147 51 234 / var(--tw-text-opacity, 1))}.hover\:text-red-300:hover{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.hover\:text-red-500:hover{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.hover\:text-red-600:hover{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity, 1))}.hover\:text-red-700:hover{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity, 1))}.hover\:text-sidebar-accent-foreground:hover{color:hsl(var(--sidebar-accent-foreground))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}.hover\:opacity-60:hover{opacity:.6}.hover\:opacity-80:hover{opacity:.8}.hover\:shadow-\[0_0_0_1px_hsl\(var\(--sidebar-accent\)\)\]:hover{--tw-shadow: 0 0 0 1px hsl(var(--sidebar-accent));--tw-shadow-colored: 0 0 0 1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-lg:hover{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-md:hover{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-sm:hover{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-xl:hover{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:ring-4:hover{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.hover\:border-gray-200:hover{border-color:#e5e7eb!important}.hover\:border-gray-300:hover{border-color:#d1d5db!important}.hover\:after\:bg-sidebar-border:hover:after{content:var(--tw-content);background-color:hsl(var(--sidebar-border))}.focus\:z-10:focus{z-index:10}.focus\:border-blue-400:focus{--tw-border-opacity: 1;border-color:rgb(96 165 250 / var(--tw-border-opacity, 1))}.focus\:border-blue-500:focus{--tw-border-opacity: 1;border-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.focus\:border-green-700:focus{--tw-border-opacity: 1;border-color:rgb(21 128 61 / var(--tw-border-opacity, 1))}.focus\:border-orange-500:focus{--tw-border-opacity: 1;border-color:rgb(249 115 22 / var(--tw-border-opacity, 1))}.focus\:border-re-green:focus{border-color:var(--re-green)}.focus\:border-red-500:focus{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.focus\:bg-accent:focus{background-color:hsl(var(--accent))}.focus\:bg-blue-600:focus{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.focus\:bg-primary:focus{background-color:hsl(var(--primary))}.focus\:bg-white:focus{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.focus\:text-accent-foreground:focus{color:hsl(var(--accent-foreground))}.focus\:text-primary-foreground:focus{color:hsl(var(--primary-foreground))}.focus\:text-red-600:focus{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity, 1))}.focus\:text-white:focus{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-\[\#2d4a3e\]:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(45 74 62 / var(--tw-ring-opacity, 1))}.focus\:ring-blue-100:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(219 234 254 / var(--tw-ring-opacity, 1))}.focus\:ring-blue-200:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(191 219 254 / var(--tw-ring-opacity, 1))}.focus\:ring-blue-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1))}.focus\:ring-blue-500\/20:focus{--tw-ring-color: rgb(59 130 246 / .2)}.focus\:ring-green-100:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(220 252 231 / var(--tw-ring-opacity, 1))}.focus\:ring-re-green:focus{--tw-ring-color: var(--re-green)}.focus\:ring-red-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity, 1))}.focus\:ring-red-600:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(220 38 38 / var(--tw-ring-opacity, 1))}.focus\:ring-ring:focus{--tw-ring-color: hsl(var(--ring))}.focus\:ring-teal-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(20 184 166 / var(--tw-ring-opacity, 1))}.focus\:ring-offset-0:focus{--tw-ring-offset-width: 0px}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.focus\:bg-white:focus{background-color:#fff!important}.focus-visible\:z-10:focus-visible{z-index:10}.focus-visible\:border-re-light-green:focus-visible{border-color:var(--re-light-green)}.focus-visible\:border-ring:focus-visible{border-color:hsl(var(--ring))}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:outline-1:focus-visible{outline-width:1px}.focus-visible\:outline-ring:focus-visible{outline-color:hsl(var(--ring))}.focus-visible\:ring-0:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-1:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-4:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-\[3px\]:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-destructive\/20:focus-visible{--tw-ring-color: hsl(var(--destructive) / .2)}.focus-visible\:ring-gray-400:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(156 163 175 / var(--tw-ring-opacity, 1))}.focus-visible\:ring-ring:focus-visible{--tw-ring-color: hsl(var(--ring))}.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color: hsl(var(--ring) / .5)}.focus-visible\:ring-offset-1:focus-visible{--tw-ring-offset-width: 1px}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width: 2px}.active\:bg-sidebar-accent:active{background-color:hsl(var(--sidebar-accent))}.active\:text-sidebar-accent-foreground:active{color:hsl(var(--sidebar-accent-foreground))}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-gray-100:disabled{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity, 1))}.disabled\:bg-gray-300:disabled{--tw-bg-opacity: 1;background-color:rgb(209 213 219 / var(--tw-bg-opacity, 1))}.disabled\:text-gray-400:disabled{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.disabled\:text-gray-500:disabled{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.disabled\:opacity-50:disabled{opacity:.5}.group\/menu-item:focus-within .group-focus-within\/menu-item\:opacity-100{opacity:1}.group:hover .group-hover\:translate-x-1{--tw-translate-x: .25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:hover .group-hover\:text-blue-600{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity, 1))}.group:hover .group-hover\:text-emerald-600{--tw-text-opacity: 1;color:rgb(5 150 105 / var(--tw-text-opacity, 1))}.group:hover .group-hover\:text-teal-600{--tw-text-opacity: 1;color:rgb(13 148 136 / var(--tw-text-opacity, 1))}.group\/menu-item:hover .group-hover\/menu-item\:opacity-100{opacity:1}.group:hover .group-hover\:shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.group:hover .group-hover\:text-re-green{color:var(--re-green)}.group:focus .group-focus\:text-blue-100{--tw-text-opacity: 1;color:rgb(219 234 254 / var(--tw-text-opacity, 1))}.group:focus .group-focus\:text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.peer\/menu-button:hover~.peer-hover\/menu-button\:text-sidebar-accent-foreground{color:hsl(var(--sidebar-accent-foreground))}.peer:disabled~.peer-disabled\:cursor-not-allowed{cursor:not-allowed}.peer:disabled~.peer-disabled\:opacity-50{opacity:.5}.peer:disabled~.peer-disabled\:opacity-70{opacity:.7}.has-\[\>svg\]\:grid-cols-\[calc\(var\(--spacing\)\*4\)_1fr\]:has(>svg){grid-template-columns:calc(var(--spacing) * 4) 1fr}.has-\[\>svg\]\:gap-x-3:has(>svg){-moz-column-gap:.75rem;column-gap:.75rem}.has-\[\>svg\]\:px-2\.5:has(>svg){padding-left:.625rem;padding-right:.625rem}.has-\[\>svg\]\:px-3:has(>svg){padding-left:.75rem;padding-right:.75rem}.has-\[\>svg\]\:px-4:has(>svg){padding-left:1rem;padding-right:1rem}.aria-disabled\:pointer-events-none[aria-disabled=true]{pointer-events:none}.aria-disabled\:opacity-50[aria-disabled=true]{opacity:.5}.aria-selected\:bg-accent[aria-selected=true]{background-color:hsl(var(--accent))}.aria-selected\:bg-primary[aria-selected=true]{background-color:hsl(var(--primary))}.aria-selected\:bg-slate-100[aria-selected=true]{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity, 1))}.aria-selected\:text-accent-foreground[aria-selected=true]{color:hsl(var(--accent-foreground))}.aria-selected\:text-muted-foreground[aria-selected=true]{color:hsl(var(--muted-foreground))}.aria-selected\:text-primary-foreground[aria-selected=true]{color:hsl(var(--primary-foreground))}.aria-selected\:opacity-100[aria-selected=true]{opacity:1}.data-\[disabled\=true\]\:pointer-events-none[data-disabled=true],.data-\[disabled\]\:pointer-events-none[data-disabled]{pointer-events:none}.data-\[vaul-drawer-direction\=bottom\]\:inset-x-0[data-vaul-drawer-direction=bottom]{left:0;right:0}.data-\[vaul-drawer-direction\=left\]\:inset-y-0[data-vaul-drawer-direction=left],.data-\[vaul-drawer-direction\=right\]\:inset-y-0[data-vaul-drawer-direction=right]{top:0;bottom:0}.data-\[vaul-drawer-direction\=top\]\:inset-x-0[data-vaul-drawer-direction=top]{left:0;right:0}.data-\[vaul-drawer-direction\=bottom\]\:bottom-0[data-vaul-drawer-direction=bottom]{bottom:0}.data-\[vaul-drawer-direction\=left\]\:left-0[data-vaul-drawer-direction=left]{left:0}.data-\[vaul-drawer-direction\=right\]\:right-0[data-vaul-drawer-direction=right]{right:0}.data-\[vaul-drawer-direction\=top\]\:top-0[data-vaul-drawer-direction=top]{top:0}.data-\[active\=true\]\:z-10[data-active=true]{z-index:10}.data-\[vaul-drawer-direction\=bottom\]\:mt-24[data-vaul-drawer-direction=bottom]{margin-top:6rem}.data-\[vaul-drawer-direction\=top\]\:mb-24[data-vaul-drawer-direction=top]{margin-bottom:6rem}.data-\[orientation\=horizontal\]\:h-4[data-orientation=horizontal]{height:1rem}.data-\[orientation\=horizontal\]\:h-full[data-orientation=horizontal]{height:100%}.data-\[orientation\=horizontal\]\:h-px[data-orientation=horizontal]{height:1px}.data-\[orientation\=vertical\]\:h-full[data-orientation=vertical]{height:100%}.data-\[panel-group-direction\=vertical\]\:h-px[data-panel-group-direction=vertical]{height:1px}.data-\[size\=default\]\:\!h-12[data-size=default]{height:3rem!important}.data-\[size\=default\]\:h-9[data-size=default]{height:2.25rem}.data-\[size\=sm\]\:h-8[data-size=sm]{height:2rem}.data-\[vaul-drawer-direction\=bottom\]\:max-h-\[80vh\][data-vaul-drawer-direction=bottom],.data-\[vaul-drawer-direction\=top\]\:max-h-\[80vh\][data-vaul-drawer-direction=top]{max-height:80vh}.data-\[orientation\=vertical\]\:min-h-44[data-orientation=vertical]{min-height:11rem}.data-\[orientation\=horizontal\]\:w-full[data-orientation=horizontal]{width:100%}.data-\[orientation\=vertical\]\:w-1\.5[data-orientation=vertical]{width:.375rem}.data-\[orientation\=vertical\]\:w-auto[data-orientation=vertical]{width:auto}.data-\[orientation\=vertical\]\:w-full[data-orientation=vertical]{width:100%}.data-\[orientation\=vertical\]\:w-px[data-orientation=vertical]{width:1px}.data-\[panel-group-direction\=vertical\]\:w-full[data-panel-group-direction=vertical]{width:100%}.data-\[vaul-drawer-direction\=left\]\:w-3\/4[data-vaul-drawer-direction=left],.data-\[vaul-drawer-direction\=right\]\:w-3\/4[data-vaul-drawer-direction=right]{width:75%}.data-\[side\=bottom\]\:translate-y-1[data-side=bottom]{--tw-translate-y: .25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[side\=left\]\:-translate-x-1[data-side=left]{--tw-translate-x: -.25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[side\=right\]\:translate-x-1[data-side=right]{--tw-translate-x: .25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[side\=top\]\:-translate-y-1[data-side=top]{--tw-translate-y: -.25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[state\=checked\]\:translate-x-\[calc\(100\%-2px\)\][data-state=checked]{--tw-translate-x: calc(100% - 2px) ;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[state\=unchecked\]\:translate-x-0[data-state=unchecked]{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes accordion-up{0%{height:var(--radix-accordion-content-height)}to{height:0}}.data-\[state\=closed\]\:animate-accordion-up[data-state=closed]{animation:accordion-up .2s ease-out}@keyframes accordion-down{0%{height:0}to{height:var(--radix-accordion-content-height)}}.data-\[state\=open\]\:animate-accordion-down[data-state=open]{animation:accordion-down .2s ease-out}.data-\[orientation\=vertical\]\:flex-col[data-orientation=vertical],.data-\[panel-group-direction\=vertical\]\:flex-col[data-panel-group-direction=vertical]{flex-direction:column}.data-\[vaul-drawer-direction\=bottom\]\:rounded-t-lg[data-vaul-drawer-direction=bottom]{border-top-left-radius:var(--radius);border-top-right-radius:var(--radius)}.data-\[vaul-drawer-direction\=top\]\:rounded-b-lg[data-vaul-drawer-direction=top]{border-bottom-right-radius:var(--radius);border-bottom-left-radius:var(--radius)}.data-\[variant\=outline\]\:border-l-0[data-variant=outline]{border-left-width:0px}.data-\[vaul-drawer-direction\=bottom\]\:border-t[data-vaul-drawer-direction=bottom]{border-top-width:1px}.data-\[vaul-drawer-direction\=left\]\:border-r[data-vaul-drawer-direction=left]{border-right-width:1px}.data-\[vaul-drawer-direction\=right\]\:border-l[data-vaul-drawer-direction=right]{border-left-width:1px}.data-\[vaul-drawer-direction\=top\]\:border-b[data-vaul-drawer-direction=top]{border-bottom-width:1px}.data-\[active\=true\]\:border-ring[data-active=true]{border-color:hsl(var(--ring))}.data-\[state\=checked\]\:border-primary[data-state=checked]{border-color:hsl(var(--primary))}.data-\[active\=true\]\:bg-accent\/50[data-active=true]{background-color:hsl(var(--accent) / .5)}.data-\[active\=true\]\:bg-sidebar-accent[data-active=true]{background-color:hsl(var(--sidebar-accent))}.data-\[selected\=true\]\:bg-accent[data-selected=true]{background-color:hsl(var(--accent))}.data-\[state\=active\]\:bg-card[data-state=active]{background-color:hsl(var(--card))}.data-\[state\=active\]\:bg-white[data-state=active]{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.data-\[state\=checked\]\:bg-primary[data-state=checked]{background-color:hsl(var(--primary))}.data-\[state\=checked\]\:bg-red-600[data-state=checked]{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.data-\[state\=on\]\:bg-accent[data-state=on],.data-\[state\=open\]\:bg-accent[data-state=open]{background-color:hsl(var(--accent))}.data-\[state\=open\]\:bg-accent\/50[data-state=open]{background-color:hsl(var(--accent) / .5)}.data-\[state\=open\]\:bg-secondary[data-state=open]{background-color:hsl(var(--secondary))}.data-\[state\=selected\]\:bg-muted[data-state=selected]{background-color:hsl(var(--muted))}.data-\[state\=unchecked\]\:bg-gray-600[data-state=unchecked]{--tw-bg-opacity: 1;background-color:rgb(75 85 99 / var(--tw-bg-opacity, 1))}.data-\[inset\]\:pl-8[data-inset]{padding-left:2rem}.data-\[active\=true\]\:font-medium[data-active=true]{font-weight:500}.data-\[active\=true\]\:text-accent-foreground[data-active=true]{color:hsl(var(--accent-foreground))}.data-\[active\=true\]\:text-sidebar-accent-foreground[data-active=true]{color:hsl(var(--sidebar-accent-foreground))}.data-\[error\=true\]\:text-destructive[data-error=true]{color:hsl(var(--destructive))}.data-\[placeholder\]\:text-muted-foreground[data-placeholder]{color:hsl(var(--muted-foreground))}.data-\[selected\=true\]\:text-accent-foreground[data-selected=true]{color:hsl(var(--accent-foreground))}.data-\[state\=active\]\:text-gray-900[data-state=active]{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity, 1))}.data-\[state\=active\]\:text-slate-900[data-state=active]{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity, 1))}.data-\[state\=checked\]\:text-primary-foreground[data-state=checked]{color:hsl(var(--primary-foreground))}.data-\[state\=on\]\:text-accent-foreground[data-state=on],.data-\[state\=open\]\:text-accent-foreground[data-state=open]{color:hsl(var(--accent-foreground))}.data-\[state\=open\]\:text-muted-foreground[data-state=open]{color:hsl(var(--muted-foreground))}.data-\[variant\=destructive\]\:text-destructive[data-variant=destructive]{color:hsl(var(--destructive))}.data-\[disabled\=true\]\:opacity-50[data-disabled=true],.data-\[disabled\]\:opacity-50[data-disabled]{opacity:.5}.data-\[state\=open\]\:opacity-100[data-state=open]{opacity:1}.data-\[state\=active\]\:shadow-md[data-state=active]{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.data-\[state\=active\]\:shadow-sm[data-state=active]{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.data-\[active\=true\]\:ring-\[3px\][data-active=true]{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.data-\[active\=true\]\:ring-ring\/50[data-active=true]{--tw-ring-color: hsl(var(--ring) / .5)}.data-\[state\=closed\]\:duration-300[data-state=closed]{transition-duration:.3s}.data-\[state\=open\]\:duration-500[data-state=open]{transition-duration:.5s}.data-\[state\=active\]\:bg-white[data-state=active]{background-color:#fff!important}.\*\:data-\[slot\=select-value\]\:flex[data-slot=select-value]>*{display:flex}.\*\:data-\[slot\=select-value\]\:items-center[data-slot=select-value]>*{align-items:center}.\*\:data-\[slot\=select-value\]\:gap-2[data-slot=select-value]>*{gap:.5rem}.\*\:data-\[slot\=alert-description\]\:text-destructive\/90[data-slot=alert-description]>*{color:hsl(var(--destructive) / .9)}.\*\:data-\[slot\=select-value\]\:line-clamp-1[data-slot=select-value]>*{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:1}.data-\[panel-group-direction\=vertical\]\:after\:left-0[data-panel-group-direction=vertical]:after{content:var(--tw-content);left:0}.data-\[panel-group-direction\=vertical\]\:after\:h-1[data-panel-group-direction=vertical]:after{content:var(--tw-content);height:.25rem}.data-\[panel-group-direction\=vertical\]\:after\:w-full[data-panel-group-direction=vertical]:after{content:var(--tw-content);width:100%}.data-\[panel-group-direction\=vertical\]\:after\:-translate-y-1\/2[data-panel-group-direction=vertical]:after{content:var(--tw-content);--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[panel-group-direction\=vertical\]\:after\:translate-x-0[data-panel-group-direction=vertical]:after{content:var(--tw-content);--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[variant\=outline\]\:first\:border-l:first-child[data-variant=outline]{border-left-width:1px}.data-\[active\=true\]\:hover\:bg-accent:hover[data-active=true],.data-\[state\=open\]\:hover\:bg-accent:hover[data-state=open]{background-color:hsl(var(--accent))}.data-\[state\=open\]\:hover\:bg-sidebar-accent:hover[data-state=open]{background-color:hsl(var(--sidebar-accent))}.data-\[state\=open\]\:hover\:text-sidebar-accent-foreground:hover[data-state=open]{color:hsl(var(--sidebar-accent-foreground))}.data-\[active\=true\]\:focus\:bg-accent:focus[data-active=true]{background-color:hsl(var(--accent))}.data-\[state\=open\]\:focus\:bg-accent:focus[data-state=open]{background-color:hsl(var(--accent))}.data-\[variant\=destructive\]\:focus\:bg-destructive\/10:focus[data-variant=destructive]{background-color:hsl(var(--destructive) / .1)}.data-\[variant\=destructive\]\:focus\:text-destructive:focus[data-variant=destructive]{color:hsl(var(--destructive))}.group[data-disabled=true] .group-data-\[disabled\=true\]\:pointer-events-none{pointer-events:none}.group[data-collapsible=offcanvas] .group-data-\[collapsible\=offcanvas\]\:left-\[calc\(var\(--sidebar-width\)\*-1\)\]{left:calc(var(--sidebar-width) * -1)}.group[data-collapsible=offcanvas] .group-data-\[collapsible\=offcanvas\]\:right-\[calc\(var\(--sidebar-width\)\*-1\)\]{right:calc(var(--sidebar-width) * -1)}.group[data-side=left] .group-data-\[side\=left\]\:-right-4{right:-1rem}.group[data-side=right] .group-data-\[side\=right\]\:left-0{left:0}.group\/navigation-menu[data-viewport=false] .group-data-\[viewport\=false\]\/navigation-menu\:top-full{top:100%}.group[data-collapsible=icon] .group-data-\[collapsible\=icon\]\:-mt-8{margin-top:-2rem}.group\/navigation-menu[data-viewport=false] .group-data-\[viewport\=false\]\/navigation-menu\:mt-1\.5{margin-top:.375rem}.group\/drawer-content[data-vaul-drawer-direction=bottom] .group-data-\[vaul-drawer-direction\=bottom\]\/drawer-content\:block{display:block}.group[data-collapsible=icon] .group-data-\[collapsible\=icon\]\:hidden{display:none}.group[data-collapsible=icon] .group-data-\[collapsible\=icon\]\:w-\[calc\(var\(--sidebar-width-icon\)\+\(--spacing\(4\)\)\)\]{width:calc(var(--sidebar-width-icon) + (--spacing(4)))}.group[data-collapsible=icon] .group-data-\[collapsible\=icon\]\:w-\[calc\(var\(--sidebar-width-icon\)\+\(--spacing\(4\)\)\+2px\)\]{width:calc(var(--sidebar-width-icon) + (--spacing(4)) + 2px)}.group[data-collapsible=offcanvas] .group-data-\[collapsible\=offcanvas\]\:w-0{width:0px}.group[data-collapsible=offcanvas] .group-data-\[collapsible\=offcanvas\]\:translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group[data-side=right] .group-data-\[side\=right\]\:rotate-180,.group[data-state=open] .group-data-\[state\=open\]\:rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group[data-collapsible=icon] .group-data-\[collapsible\=icon\]\:overflow-hidden,.group\/navigation-menu[data-viewport=false] .group-data-\[viewport\=false\]\/navigation-menu\:overflow-hidden{overflow:hidden}.group[data-variant=floating] .group-data-\[variant\=floating\]\:rounded-lg{border-radius:var(--radius)}.group\/navigation-menu[data-viewport=false] .group-data-\[viewport\=false\]\/navigation-menu\:rounded-md{border-radius:calc(var(--radius) - 2px)}.group[data-variant=floating] .group-data-\[variant\=floating\]\:border,.group\/navigation-menu[data-viewport=false] .group-data-\[viewport\=false\]\/navigation-menu\:border{border-width:1px}.group[data-side=left] .group-data-\[side\=left\]\:border-r{border-right-width:1px}.group[data-side=right] .group-data-\[side\=right\]\:border-l{border-left-width:1px}.group[data-variant=floating] .group-data-\[variant\=floating\]\:border-sidebar-border{border-color:hsl(var(--sidebar-border))}.group\/navigation-menu[data-viewport=false] .group-data-\[viewport\=false\]\/navigation-menu\:bg-popover{background-color:hsl(var(--popover))}.group\/navigation-menu[data-viewport=false] .group-data-\[viewport\=false\]\/navigation-menu\:text-popover-foreground{color:hsl(var(--popover-foreground))}.group[data-collapsible=icon] .group-data-\[collapsible\=icon\]\:opacity-0{opacity:0}.group[data-disabled=true] .group-data-\[disabled\=true\]\:opacity-50{opacity:.5}.group[data-variant=floating] .group-data-\[variant\=floating\]\:shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.group\/navigation-menu[data-viewport=false] .group-data-\[viewport\=false\]\/navigation-menu\:shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.group\/navigation-menu[data-viewport=false] .group-data-\[viewport\=false\]\/navigation-menu\:duration-200{transition-duration:.2s}.group[data-collapsible=offcanvas] .group-data-\[collapsible\=offcanvas\]\:after\:left-full:after{content:var(--tw-content);left:100%}.group[data-collapsible=offcanvas] .hover\:group-data-\[collapsible\=offcanvas\]\:bg-sidebar:hover{background-color:hsl(var(--sidebar))}.peer\/menu-button[data-size=default]~.peer-data-\[size\=default\]\/menu-button\:top-1\.5{top:.375rem}.peer\/menu-button[data-size=lg]~.peer-data-\[size\=lg\]\/menu-button\:top-2\.5{top:.625rem}.peer\/menu-button[data-size=sm]~.peer-data-\[size\=sm\]\/menu-button\:top-1{top:.25rem}.peer\/menu-button[data-active=true]~.peer-data-\[active\=true\]\/menu-button\:text-sidebar-accent-foreground{color:hsl(var(--sidebar-accent-foreground))}.dark\:border-input:is(.dark *){border-color:hsl(var(--input))}.dark\:bg-destructive\/60:is(.dark *){background-color:hsl(var(--destructive) / .6)}.dark\:bg-input\/30:is(.dark *){background-color:hsl(var(--input) / .3)}.dark\:text-muted-foreground:is(.dark *){color:hsl(var(--muted-foreground))}.dark\:hover\:bg-accent\/50:hover:is(.dark *){background-color:hsl(var(--accent) / .5)}.dark\:hover\:bg-input\/50:hover:is(.dark *){background-color:hsl(var(--input) / .5)}.dark\:focus-visible\:ring-destructive\/40:focus-visible:is(.dark *){--tw-ring-color: hsl(var(--destructive) / .4)}.dark\:data-\[state\=active\]\:border-input[data-state=active]:is(.dark *){border-color:hsl(var(--input))}.dark\:data-\[state\=active\]\:bg-input\/30[data-state=active]:is(.dark *){background-color:hsl(var(--input) / .3)}.dark\:data-\[state\=checked\]\:bg-primary[data-state=checked]:is(.dark *){background-color:hsl(var(--primary))}.dark\:data-\[state\=checked\]\:bg-primary-foreground[data-state=checked]:is(.dark *){background-color:hsl(var(--primary-foreground))}.dark\:data-\[state\=unchecked\]\:bg-card-foreground[data-state=unchecked]:is(.dark *){background-color:hsl(var(--card-foreground))}.dark\:data-\[state\=unchecked\]\:bg-input\/80[data-state=unchecked]:is(.dark *){background-color:hsl(var(--input) / .8)}.dark\:data-\[state\=active\]\:text-foreground[data-state=active]:is(.dark *){color:hsl(var(--foreground))}.dark\:data-\[variant\=destructive\]\:focus\:bg-destructive\/20:focus[data-variant=destructive]:is(.dark *){background-color:hsl(var(--destructive) / .2)}@media (min-width: 640px){.sm\:relative{position:relative}.sm\:-bottom-1{bottom:-.25rem}.sm\:-right-1{right:-.25rem}.sm\:right-6{right:1.5rem}.sm\:top-6{top:1.5rem}.sm\:order-1{order:1}.sm\:order-2{order:2}.sm\:col-span-1{grid-column:span 1 / span 1}.sm\:col-span-2{grid-column:span 2 / span 2}.sm\:mx-0{margin-left:0;margin-right:0}.sm\:mb-0{margin-bottom:0}.sm\:mb-2{margin-bottom:.5rem}.sm\:mb-3{margin-bottom:.75rem}.sm\:mb-4{margin-bottom:1rem}.sm\:mb-6{margin-bottom:1.5rem}.sm\:mb-8{margin-bottom:2rem}.sm\:ml-14{margin-left:3.5rem}.sm\:ml-2{margin-left:.5rem}.sm\:mr-2{margin-right:.5rem}.sm\:mt-2{margin-top:.5rem}.sm\:mt-3{margin-top:.75rem}.sm\:mt-4{margin-top:1rem}.sm\:mt-6{margin-top:1.5rem}.sm\:mt-8{margin-top:2rem}.sm\:block{display:block}.sm\:inline{display:inline}.sm\:flex{display:flex}.sm\:inline-flex{display:inline-flex}.sm\:grid{display:grid}.sm\:hidden{display:none}.sm\:h-10{height:2.5rem}.sm\:h-12{height:3rem}.sm\:h-16{height:4rem}.sm\:h-2{height:.5rem}.sm\:h-3{height:.75rem}.sm\:h-3\.5{height:.875rem}.sm\:h-4{height:1rem}.sm\:h-5{height:1.25rem}.sm\:h-6{height:1.5rem}.sm\:h-7{height:1.75rem}.sm\:h-8{height:2rem}.sm\:h-9{height:2.25rem}.sm\:min-h-\[120px\]{min-height:120px}.sm\:min-h-\[60px\]{min-height:60px}.sm\:w-10{width:2.5rem}.sm\:w-12{width:3rem}.sm\:w-16{width:4rem}.sm\:w-28{width:7rem}.sm\:w-3{width:.75rem}.sm\:w-3\.5{width:.875rem}.sm\:w-32{width:8rem}.sm\:w-36{width:9rem}.sm\:w-4{width:1rem}.sm\:w-5{width:1.25rem}.sm\:w-6{width:1.5rem}.sm\:w-64{width:16rem}.sm\:w-7{width:1.75rem}.sm\:w-8{width:2rem}.sm\:w-80{width:20rem}.sm\:w-96{width:24rem}.sm\:w-\[60px\]{width:60px}.sm\:w-auto{width:auto}.sm\:min-w-\[100px\]{min-width:100px}.sm\:max-w-2xl{max-width:42rem}.sm\:max-w-\[200px\]{max-width:200px}.sm\:max-w-\[425px\]{max-width:425px}.sm\:max-w-\[450px\]{max-width:450px}.sm\:max-w-\[500px\]{max-width:500px}.sm\:max-w-\[550px\]{max-width:550px}.sm\:max-w-\[600px\]{max-width:600px}.sm\:max-w-\[640px\]{max-width:640px}.sm\:max-w-lg{max-width:32rem}.sm\:max-w-md{max-width:28rem}.sm\:max-w-sm{max-width:24rem}.sm\:flex-initial{flex:0 1 auto}.sm\:flex-none{flex:none}.sm\:scale-100{--tw-scale-x: 1;--tw-scale-y: 1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.sm\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.sm\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.sm\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:flex-col{flex-direction:column}.sm\:flex-wrap{flex-wrap:wrap}.sm\:items-start{align-items:flex-start}.sm\:items-center{align-items:center}.sm\:justify-end{justify-content:flex-end}.sm\:justify-between{justify-content:space-between}.sm\:gap-0{gap:0px}.sm\:gap-1{gap:.25rem}.sm\:gap-1\.5{gap:.375rem}.sm\:gap-2{gap:.5rem}.sm\:gap-2\.5{gap:.625rem}.sm\:gap-3{gap:.75rem}.sm\:gap-4{gap:1rem}.sm\:gap-6{gap:1.5rem}.sm\:space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.sm\:space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.sm\:space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.sm\:space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.sm\:self-auto{align-self:auto}.sm\:rounded-xl{border-radius:.75rem}.sm\:p-1{padding:.25rem}.sm\:p-12{padding:3rem}.sm\:p-2{padding:.5rem}.sm\:p-2\.5{padding:.625rem}.sm\:p-3{padding:.75rem}.sm\:p-4{padding:1rem}.sm\:p-6{padding:1.5rem}.sm\:p-8{padding:2rem}.sm\:px-2{padding-left:.5rem;padding-right:.5rem}.sm\:px-3{padding-left:.75rem;padding-right:.75rem}.sm\:px-4{padding-left:1rem;padding-right:1rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:px-8{padding-left:2rem;padding-right:2rem}.sm\:py-1{padding-top:.25rem;padding-bottom:.25rem}.sm\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.sm\:py-2{padding-top:.5rem;padding-bottom:.5rem}.sm\:py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.sm\:py-3{padding-top:.75rem;padding-bottom:.75rem}.sm\:py-4{padding-top:1rem;padding-bottom:1rem}.sm\:py-5{padding-top:1.25rem;padding-bottom:1.25rem}.sm\:py-6{padding-top:1.5rem;padding-bottom:1.5rem}.sm\:py-8{padding-top:2rem;padding-bottom:2rem}.sm\:pb-0{padding-bottom:0}.sm\:pb-4{padding-bottom:1rem}.sm\:pb-6{padding-bottom:1.5rem}.sm\:pl-10{padding-left:2.5rem}.sm\:pl-2\.5{padding-left:.625rem}.sm\:pr-0{padding-right:0}.sm\:pr-2\.5{padding-right:.625rem}.sm\:pt-0{padding-top:0}.sm\:pt-3{padding-top:.75rem}.sm\:pt-4{padding-top:1rem}.sm\:text-left{text-align:left}.sm\:text-center{text-align:center}.sm\:text-right{text-align:right}.sm\:text-2xl{font-size:1.5rem;line-height:2rem}.sm\:text-3xl{font-size:1.875rem;line-height:2.25rem}.sm\:text-base{font-size:1rem;line-height:1.5rem}.sm\:text-lg{font-size:1.125rem;line-height:1.75rem}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}.sm\:text-xl{font-size:1.25rem;line-height:1.75rem}.sm\:text-xs{font-size:.75rem;line-height:1rem}.sm\:shadow-none{--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.sm\:ring-2{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.sm\:space-y-6>*+*{margin-top:1.5rem}.sm\:space-y-4>*+*{margin-top:1rem}.data-\[vaul-drawer-direction\=left\]\:sm\:max-w-sm[data-vaul-drawer-direction=left],.data-\[vaul-drawer-direction\=right\]\:sm\:max-w-sm[data-vaul-drawer-direction=right]{max-width:24rem}}@media (min-width: 768px){.md\:absolute{position:absolute}.md\:relative{position:relative}.md\:z-auto{z-index:auto}.md\:col-span-1{grid-column:span 1 / span 1}.md\:col-span-2{grid-column:span 2 / span 2}.md\:col-span-3{grid-column:span 3 / span 3}.md\:block{display:block}.md\:inline{display:inline}.md\:flex{display:flex}.md\:inline-flex{display:inline-flex}.md\:hidden{display:none}.md\:h-11{height:2.75rem}.md\:w-0{width:0px}.md\:w-28{width:7rem}.md\:w-64{width:16rem}.md\:w-\[var\(--radix-navigation-menu-viewport-width\)\]{width:var(--radix-navigation-menu-viewport-width)}.md\:w-auto{width:auto}.md\:translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:justify-between{justify-content:space-between}.md\:gap-4{gap:1rem}.md\:gap-6{gap:1.5rem}.md\:overflow-x-visible{overflow-x:visible}.md\:p-3{padding:.75rem}.md\:p-5{padding:1.25rem}.md\:p-6{padding:1.5rem}.md\:px-4{padding-left:1rem;padding-right:1rem}.md\:px-6{padding-left:1.5rem;padding-right:1.5rem}.md\:py-2{padding-top:.5rem;padding-bottom:.5rem}.md\:text-2xl{font-size:1.5rem;line-height:2rem}.md\:text-3xl{font-size:1.875rem;line-height:2.25rem}.md\:text-base{font-size:1rem;line-height:1.5rem}.md\:text-lg{font-size:1.125rem;line-height:1.75rem}.md\:text-sm{font-size:.875rem;line-height:1.25rem}.md\:text-xl{font-size:1.25rem;line-height:1.75rem}.md\:opacity-0{opacity:0}.md\:after\:hidden:after{content:var(--tw-content);display:none}.peer[data-variant=inset]~.md\:peer-data-\[variant\=inset\]\:m-2{margin:.5rem}.peer[data-variant=inset]~.md\:peer-data-\[variant\=inset\]\:ml-0{margin-left:0}.peer[data-variant=inset][data-state=collapsed]~.md\:peer-data-\[variant\=inset\]\:peer-data-\[state\=collapsed\]\:ml-2{margin-left:.5rem}.peer[data-variant=inset]~.md\:peer-data-\[variant\=inset\]\:rounded-xl{border-radius:.75rem}.peer[data-variant=inset]~.md\:peer-data-\[variant\=inset\]\:shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}}@media (min-width: 1024px){.lg\:relative{position:relative}.lg\:col-span-1{grid-column:span 1 / span 1}.lg\:col-span-2{grid-column:span 2 / span 2}.lg\:col-span-3{grid-column:span 3 / span 3}.lg\:flex{display:flex}.lg\:grid{display:grid}.lg\:hidden{display:none}.lg\:h-12{height:3rem}.lg\:h-5{height:1.25rem}.lg\:h-6{height:1.5rem}.lg\:h-9{height:2.25rem}.lg\:h-\[60vh\]{height:60vh}.lg\:max-h-\[140px\]{max-height:140px}.lg\:max-h-\[180px\]{max-height:180px}.lg\:min-h-\[100px\]{min-height:100px}.lg\:min-h-\[140px\]{min-height:140px}.lg\:min-h-\[480px\]{min-height:480px}.lg\:min-h-\[90px\]{min-height:90px}.lg\:w-12{width:3rem}.lg\:w-32{width:8rem}.lg\:w-5{width:1.25rem}.lg\:w-6{width:1.5rem}.lg\:w-auto{width:auto}.lg\:max-w-\[1000px\]{max-width:1000px}.lg\:max-w-\[800px\]{max-width:800px}.lg\:translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:grid-cols-7{grid-template-columns:repeat(7,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:content-start{align-content:flex-start}.lg\:items-start{align-items:flex-start}.lg\:items-center{align-items:center}.lg\:gap-3{gap:.75rem}.lg\:gap-4{gap:1rem}.lg\:gap-6{gap:1.5rem}.lg\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.lg\:gap-y-6{row-gap:1.5rem}.lg\:space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(0px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0px * var(--tw-space-y-reverse))}.lg\:space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.lg\:space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.lg\:space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.lg\:p-12{padding:3rem}.lg\:p-2{padding:.5rem}.lg\:p-3{padding:.75rem}.lg\:p-4{padding:1rem}.lg\:p-6{padding:1.5rem}.lg\:p-8{padding:2rem}.lg\:px-4{padding-left:1rem;padding-right:1rem}.lg\:px-6{padding-left:1.5rem;padding-right:1.5rem}.lg\:py-3{padding-top:.75rem;padding-bottom:.75rem}.lg\:py-4{padding-top:1rem;padding-bottom:1rem}.lg\:py-8{padding-top:2rem;padding-bottom:2rem}.lg\:pb-4{padding-bottom:1rem}.lg\:pt-3{padding-top:.75rem}.lg\:pt-4{padding-top:1rem}.lg\:pt-6{padding-top:1.5rem}.lg\:text-2xl{font-size:1.5rem;line-height:2rem}.lg\:text-3xl{font-size:1.875rem;line-height:2.25rem}.lg\:text-4xl{font-size:2.25rem;line-height:2.5rem}.lg\:text-5xl{font-size:3rem;line-height:1}.lg\:text-base{font-size:1rem;line-height:1.5rem}.lg\:text-lg{font-size:1.125rem;line-height:1.75rem}.lg\:text-sm{font-size:.875rem;line-height:1.25rem}.lg\:text-xl{font-size:1.25rem;line-height:1.75rem}.lg\:shadow-none{--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.lg\:space-y-4>*+*{margin-top:1rem}}@media (min-width: 1280px){.xl\:max-w-\[1920px\]{max-width:1920px}.xl\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}}.\[\&\:has\(\>\.day-range-end\)\]\:rounded-r-md:has(>.day-range-end){border-top-right-radius:calc(var(--radius) - 2px);border-bottom-right-radius:calc(var(--radius) - 2px)}.\[\&\:has\(\>\.day-range-start\)\]\:rounded-l-md:has(>.day-range-start){border-top-left-radius:calc(var(--radius) - 2px);border-bottom-left-radius:calc(var(--radius) - 2px)}.\[\&\:has\(\[aria-selected\]\)\]\:rounded-md:has([aria-selected]){border-radius:calc(var(--radius) - 2px)}.\[\&\:has\(\[aria-selected\]\)\]\:bg-accent:has([aria-selected]){background-color:hsl(var(--accent))}.first\:\[\&\:has\(\[aria-selected\]\)\]\:rounded-l-md:has([aria-selected]):first-child{border-top-left-radius:calc(var(--radius) - 2px);border-bottom-left-radius:calc(var(--radius) - 2px)}.last\:\[\&\:has\(\[aria-selected\]\)\]\:rounded-r-md:has([aria-selected]):last-child{border-top-right-radius:calc(var(--radius) - 2px);border-bottom-right-radius:calc(var(--radius) - 2px)}.\[\&\:has\(\[aria-selected\]\.day-range-end\)\]\:rounded-r-md:has([aria-selected].day-range-end){border-top-right-radius:calc(var(--radius) - 2px);border-bottom-right-radius:calc(var(--radius) - 2px)}.\[\&\:has\(\[role\=checkbox\]\)\]\:pr-0:has([role=checkbox]){padding-right:0}.\[\&\:last-child\]\:pb-6:last-child{padding-bottom:1.5rem}.\[\&\>\[role\=checkbox\]\]\:translate-y-\[2px\]>[role=checkbox]{--tw-translate-y: 2px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.\[\&\>button\]\:hidden>button{display:none}.\[\&\>div\]\:bg-green-600>div{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.\[\&\>div\]\:bg-orange-500>div{--tw-bg-opacity: 1;background-color:rgb(249 115 22 / var(--tw-bg-opacity, 1))}.\[\&\>div\]\:bg-red-600>div{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.\[\&\>span\:last-child\]\:truncate>span:last-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.\[\&\>svg\]\:pointer-events-none>svg{pointer-events:none}.\[\&\>svg\]\:size-3>svg{width:.75rem;height:.75rem}.\[\&\>svg\]\:size-3\.5>svg{width:.875rem;height:.875rem}.\[\&\>svg\]\:size-4>svg{width:1rem;height:1rem}.\[\&\>svg\]\:h-2\.5>svg{height:.625rem}.\[\&\>svg\]\:h-3>svg{height:.75rem}.\[\&\>svg\]\:w-2\.5>svg{width:.625rem}.\[\&\>svg\]\:w-3>svg{width:.75rem}.\[\&\>svg\]\:shrink-0>svg{flex-shrink:0}.\[\&\>svg\]\:translate-y-0\.5>svg{--tw-translate-y: .125rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.\[\&\>svg\]\:text-current>svg{color:currentColor}.\[\&\>svg\]\:text-muted-foreground>svg{color:hsl(var(--muted-foreground))}.\[\&\>svg\]\:text-sidebar-accent-foreground>svg{color:hsl(var(--sidebar-accent-foreground))}.\[\&\>tr\]\:last\:border-b-0:last-child>tr{border-bottom-width:0px}.\[\&\[data-panel-group-direction\=vertical\]\>div\]\:rotate-90[data-panel-group-direction=vertical]>div{--tw-rotate: 90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.\[\&\[data-state\=open\]\>svg\]\:rotate-180[data-state=open]>svg{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.\[\&_\.recharts-cartesian-axis-tick_text\]\:fill-muted-foreground .recharts-cartesian-axis-tick text{fill:hsl(var(--muted-foreground))}.\[\&_\.recharts-cartesian-grid_line\[stroke\=\'\#ccc\'\]\]\:stroke-border\/50 .recharts-cartesian-grid line[stroke="#ccc"]{stroke:hsl(var(--border) / .5)}.\[\&_\.recharts-curve\.recharts-tooltip-cursor\]\:stroke-border .recharts-curve.recharts-tooltip-cursor{stroke:hsl(var(--border))}.\[\&_\.recharts-dot\[stroke\=\'\#fff\'\]\]\:stroke-transparent .recharts-dot[stroke="#fff"]{stroke:transparent}.\[\&_\.recharts-polar-grid_\[stroke\=\'\#ccc\'\]\]\:stroke-border .recharts-polar-grid [stroke="#ccc"]{stroke:hsl(var(--border))}.\[\&_\.recharts-radial-bar-background-sector\]\:fill-muted .recharts-radial-bar-background-sector,.\[\&_\.recharts-rectangle\.recharts-tooltip-cursor\]\:fill-muted .recharts-rectangle.recharts-tooltip-cursor{fill:hsl(var(--muted))}.\[\&_\.recharts-reference-line_\[stroke\=\'\#ccc\'\]\]\:stroke-border .recharts-reference-line [stroke="#ccc"]{stroke:hsl(var(--border))}.\[\&_\.recharts-sector\[stroke\=\'\#fff\'\]\]\:stroke-transparent .recharts-sector[stroke="#fff"]{stroke:transparent}.\[\&_\.table-wrapper\]\:-mx-2 .table-wrapper{margin-left:-.5rem;margin-right:-.5rem}.\[\&_\.table-wrapper\]\:my-2 .table-wrapper{margin-top:.5rem;margin-bottom:.5rem}.\[\&_\.table-wrapper\]\:max-w-full .table-wrapper{max-width:100%}.\[\&_\.table-wrapper\]\:overflow-x-auto .table-wrapper{overflow-x:auto}.\[\&_\.table-wrapper\]\:px-2 .table-wrapper{padding-left:.5rem;padding-right:.5rem}.\[\&_\.table-wrapper_table\]\:min-w-full .table-wrapper table{min-width:100%}.\[\&_\.table-wrapper_table\]\:border-collapse .table-wrapper table{border-collapse:collapse}.\[\&_\.table-wrapper_table\]\:border .table-wrapper table{border-width:1px}.\[\&_\.table-wrapper_table\]\:border-gray-300 .table-wrapper table{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity, 1));border-color:#d1d5db!important}.\[\&_\.table-wrapper_table_td\]\:whitespace-nowrap .table-wrapper table td{white-space:nowrap}.\[\&_\.table-wrapper_table_td\]\:border .table-wrapper table td{border-width:1px}.\[\&_\.table-wrapper_table_td\]\:border-gray-300 .table-wrapper table td{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity, 1))}.\[\&_\.table-wrapper_table_td\]\:px-3 .table-wrapper table td{padding-left:.75rem;padding-right:.75rem}.\[\&_\.table-wrapper_table_td\]\:py-2 .table-wrapper table td{padding-top:.5rem;padding-bottom:.5rem}.\[\&_\.table-wrapper_table_td\]\:text-sm .table-wrapper table td{font-size:.875rem;line-height:1.25rem}.\[\&_\.table-wrapper_table_td\]\:border-gray-300 .table-wrapper table td{border-color:#d1d5db!important}.\[\&_\.table-wrapper_table_th\]\:whitespace-nowrap .table-wrapper table th{white-space:nowrap}.\[\&_\.table-wrapper_table_th\]\:border .table-wrapper table th{border-width:1px}.\[\&_\.table-wrapper_table_th\]\:border-gray-300 .table-wrapper table th{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity, 1))}.\[\&_\.table-wrapper_table_th\]\:bg-gray-50 .table-wrapper table th{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1))}.\[\&_\.table-wrapper_table_th\]\:px-3 .table-wrapper table th{padding-left:.75rem;padding-right:.75rem}.\[\&_\.table-wrapper_table_th\]\:py-2 .table-wrapper table th{padding-top:.5rem;padding-bottom:.5rem}.\[\&_\.table-wrapper_table_th\]\:text-left .table-wrapper table th{text-align:left}.\[\&_\.table-wrapper_table_th\]\:text-sm .table-wrapper table th{font-size:.875rem;line-height:1.25rem}.\[\&_\.table-wrapper_table_th\]\:font-semibold .table-wrapper table th{font-weight:600}.\[\&_\.table-wrapper_table_th\]\:border-gray-300 .table-wrapper table th{border-color:#d1d5db!important}.\[\&_\.table-wrapper_table_tr\:nth-child\(even\)\]\:bg-gray-50 .table-wrapper table tr:nth-child(2n){--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1))}.\[\&_\[cmdk-group-heading\]\]\:px-2 [cmdk-group-heading]{padding-left:.5rem;padding-right:.5rem}.\[\&_\[cmdk-group-heading\]\]\:py-1\.5 [cmdk-group-heading]{padding-top:.375rem;padding-bottom:.375rem}.\[\&_\[cmdk-group-heading\]\]\:text-xs [cmdk-group-heading]{font-size:.75rem;line-height:1rem}.\[\&_\[cmdk-group-heading\]\]\:font-medium [cmdk-group-heading]{font-weight:500}.\[\&_\[cmdk-group-heading\]\]\:text-muted-foreground [cmdk-group-heading]{color:hsl(var(--muted-foreground))}.\[\&_\[cmdk-group\]\:not\(\[hidden\]\)_\~\[cmdk-group\]\]\:pt-0 [cmdk-group]:not([hidden])~[cmdk-group]{padding-top:0}.\[\&_\[cmdk-group\]\]\:px-2 [cmdk-group]{padding-left:.5rem;padding-right:.5rem}.\[\&_\[cmdk-input-wrapper\]_svg\]\:h-5 [cmdk-input-wrapper] svg{height:1.25rem}.\[\&_\[cmdk-input-wrapper\]_svg\]\:w-5 [cmdk-input-wrapper] svg{width:1.25rem}.\[\&_\[cmdk-input\]\]\:h-12 [cmdk-input]{height:3rem}.\[\&_\[cmdk-item\]\]\:px-2 [cmdk-item]{padding-left:.5rem;padding-right:.5rem}.\[\&_\[cmdk-item\]\]\:py-3 [cmdk-item]{padding-top:.75rem;padding-bottom:.75rem}.\[\&_\[cmdk-item\]_svg\]\:h-5 [cmdk-item] svg{height:1.25rem}.\[\&_\[cmdk-item\]_svg\]\:w-5 [cmdk-item] svg{width:1.25rem}.\[\&_em\]\:italic em{font-style:italic}.\[\&_h1\]\:my-2 h1{margin-top:.5rem;margin-bottom:.5rem}.\[\&_h1\]\:text-xl h1{font-size:1.25rem;line-height:1.75rem}.\[\&_h1\]\:font-bold h1{font-weight:700}.\[\&_h2\]\:my-2 h2{margin-top:.5rem;margin-bottom:.5rem}.\[\&_h2\]\:text-lg h2{font-size:1.125rem;line-height:1.75rem}.\[\&_h2\]\:font-semibold h2{font-weight:600}.\[\&_h3\]\:my-1 h3{margin-top:.25rem;margin-bottom:.25rem}.\[\&_h3\]\:text-base h3{font-size:1rem;line-height:1.5rem}.\[\&_h3\]\:font-semibold h3{font-weight:600}.\[\&_li\]\:my-1 li{margin-top:.25rem;margin-bottom:.25rem}.\[\&_li\]\:pl-2 li{padding-left:.5rem}.\[\&_ol\]\:my-2 ol{margin-top:.5rem;margin-bottom:.5rem}.\[\&_ol\]\:ml-6 ol{margin-left:1.5rem}.\[\&_ol\]\:list-outside ol{list-style-position:outside}.\[\&_ol\]\:list-decimal ol{list-style-type:decimal}.\[\&_p\]\:my-1 p{margin-top:.25rem;margin-bottom:.25rem}.\[\&_p\]\:leading-relaxed p{line-height:1.625}.\[\&_span\[style\*\=\'background\'\]\]\:rounded span[style*=background]{border-radius:.25rem}.\[\&_span\[style\*\=\'background\'\]\]\:px-0\.5 span[style*=background]{padding-left:.125rem;padding-right:.125rem}.\[\&_strong\]\:font-bold strong{font-weight:700}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-4 svg:not([class*=size-]){width:1rem;height:1rem}.\[\&_svg\:not\(\[class\*\=\'text-\'\]\)\]\:text-muted-foreground svg:not([class*=text-]){color:hsl(var(--muted-foreground))}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&_table\]\:my-2 table{margin-top:.5rem;margin-bottom:.5rem}.\[\&_table\]\:w-full table{width:100%}.\[\&_table\]\:border-collapse table{border-collapse:collapse}.\[\&_table\]\:border table{border-width:1px}.\[\&_table\]\:border-gray-300 table{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity, 1));border-color:#d1d5db!important}.\[\&_table_td\]\:border table td{border-width:1px}.\[\&_table_td\]\:border-gray-300 table td{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity, 1))}.\[\&_table_td\]\:px-3 table td{padding-left:.75rem;padding-right:.75rem}.\[\&_table_td\]\:py-2 table td{padding-top:.5rem;padding-bottom:.5rem}.\[\&_table_td\]\:text-sm table td{font-size:.875rem;line-height:1.25rem}.\[\&_table_td\]\:border-gray-300 table td{border-color:#d1d5db!important}.\[\&_table_th\]\:border table th{border-width:1px}.\[\&_table_th\]\:border-gray-300 table th{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity, 1))}.\[\&_table_th\]\:bg-gray-50 table th{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1))}.\[\&_table_th\]\:px-3 table th{padding-left:.75rem;padding-right:.75rem}.\[\&_table_th\]\:py-2 table th{padding-top:.5rem;padding-bottom:.5rem}.\[\&_table_th\]\:text-left table th{text-align:left}.\[\&_table_th\]\:text-sm table th{font-size:.875rem;line-height:1.25rem}.\[\&_table_th\]\:font-semibold table th{font-weight:600}.\[\&_table_th\]\:border-gray-300 table th{border-color:#d1d5db!important}.\[\&_table_tr\:nth-child\(even\)\]\:bg-gray-50 table tr:nth-child(2n){--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1))}.\[\&_tr\:last-child\]\:border-0 tr:last-child{border-width:0px}.\[\&_tr\]\:border-b tr{border-bottom-width:1px}.\[\&_u\]\:underline u{text-decoration-line:underline}.\[\&_ul\]\:my-2 ul{margin-top:.5rem;margin-bottom:.5rem}.\[\&_ul\]\:ml-6 ul{margin-left:1.5rem}.\[\&_ul\]\:list-outside ul{list-style-position:outside}.\[\&_ul\]\:list-disc ul{list-style-type:disc}[data-side=left][data-collapsible=offcanvas] .\[\[data-side\=left\]\[data-collapsible\=offcanvas\]_\&\]\:-right-2{right:-.5rem}[data-side=left][data-state=collapsed] .\[\[data-side\=left\]\[data-state\=collapsed\]_\&\]\:cursor-e-resize{cursor:e-resize}[data-side=right][data-collapsible=offcanvas] .\[\[data-side\=right\]\[data-collapsible\=offcanvas\]_\&\]\:-left-2{left:-.5rem}[data-side=right][data-state=collapsed] .\[\[data-side\=right\]\[data-state\=collapsed\]_\&\]\:cursor-w-resize{cursor:w-resize}a.\[a\&\]\:hover\:bg-accent:hover{background-color:hsl(var(--accent))}a.\[a\&\]\:hover\:bg-destructive\/90:hover{background-color:hsl(var(--destructive) / .9)}a.\[a\&\]\:hover\:bg-primary\/90:hover{background-color:hsl(var(--primary) / .9)}a.\[a\&\]\:hover\:bg-secondary\/90:hover{background-color:hsl(var(--secondary) / .9)}a.\[a\&\]\:hover\:text-accent-foreground:hover{color:hsl(var(--accent-foreground))}svg{display:inline-block;vertical-align:middle}button svg{flex-shrink:0}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility}@media (max-width: 768px){html{overflow-x:hidden}}.sidebar-toggle{transition:all .3s ease-in-out}button:hover svg{transform:scale(1.05);transition:transform .2s ease}.table-wrapper{overflow-x:auto;max-width:100%;margin:8px 0} diff --git a/build/assets/index-CMYYGiJ3.js b/build/assets/index-CMYYGiJ3.js deleted file mode 100644 index a11d08d..0000000 --- a/build/assets/index-CMYYGiJ3.js +++ /dev/null @@ -1,64 +0,0 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/conclusionApi-B8GrXg3V.js","assets/radix-vendor-CLtqm-Ae.js","assets/charts-vendor-CmYZJIYl.js","assets/utils-vendor-BTBPSQfW.js","assets/ui-vendor-DgwXkk2Y.js","assets/socket-vendor-TjCxX7sJ.js","assets/redux-vendor-tbZCm13o.js","assets/router-vendor-HW_ujxKo.js"])))=>i.map(i=>d[i]); -var Zf=Object.defineProperty;var Jf=(t,s,a)=>s in t?Zf(t,s,{enumerable:!0,configurable:!0,writable:!0,value:a}):t[s]=a;var Ll=(t,s,a)=>Jf(t,typeof s!="symbol"?s+"":s,a);import{j as e,S as Ph,R as Eh,I as _h,F as Mh,a as qf,T as eb,P as tb,C as sb,b as ab,O as Lh,c as Oh,d as rb,e as nb,f as ib,D as lb,g as ob,A as cb,h as db,i as mb,k as ub,l as xb,m as hb,n as pb,V as gb,o as fb,p as bb,q as jb,r as yb,s as vb,t as Nb,u as wb,v as Cb,w as Sb,x as Ab,y as Tb,z as kb,B as Db,E as Rb,G as Fb,H as Ib,J as Pb,K as Eb,L as _b,M as Mb,N as Lb,Q as uc,U as xc,W as Ob,X as $h,Y as Uh,Z as Bh,_ as Vh,$ as $b,a0 as Ub,a1 as Bb,a2 as Vb,a3 as zb,a4 as Hb,a5 as Gb,a6 as Wb,a7 as Yb,a8 as cn,a9 as gr,aa as Zn,ab as Qb,ac as Kb,ad as Xb,ae as Zb}from"./radix-vendor-CLtqm-Ae.js";import{a as Jb,r as x,d as zh,e as dn,B as Io,C as Yi,X as Qi,Y as Ki,T as mn,L as Xi,f as Ea,P as Hh,g as Gh,h as Wh,i as qb,j as du,k as cl,l as dl,b as ej}from"./charts-vendor-CmYZJIYl.js";import{a as ml,f as Yh,s as ma,e as hc,b as mu,c as tj,d as Oa,g as Jn,i as pc,h as Qh,j as ea,k as oa,l as Ol,m as Qa,n as sj,o as Po,p as aj,q as st,r as Eo,t as rj,u as nj,v as ij,w as lj,x as $l,y as uu,z as oj,A as Kh,B as Xh,C as gc,D as fc,E as cj,F as dj,G as Zh,H as Jr,I as Ul,J as mj,K as uj,L as xj}from"./utils-vendor-BTBPSQfW.js";import{u as W,A as js,S as fs,C as Je,a as ps,L as ze,X as yt,H as hj,b as Jh,U as Ht,F as Fe,c as Be,d as bn,R as Mt,e as jn,f as Ka,P as ms,g as pj,h as gj,B as _s,i as dr,j as Bl,k as bc,l as $a,m as ul,n as pn,o as ft,p as Ua,q as Et,I as Hr,T as ts,r as it,s as Ia,t as fj,v as ss,M as Ys,w as xl,x as Bt,y as _o,D as Tt,Z as hl,z as qt,E as bj,G as jj,J as yj,K as vj,N as Nj,O as wj,Q as Cj,V as Sj,W as Aj,Y as qh,_ as Tj,$ as kj,a0 as ep,a1 as va,a2 as oi,a3 as mr,a4 as Zi,a5 as xu,a6 as wi,a7 as Ji,a8 as tp,a9 as St,aa as Dj,ab as Qs,ac as jc,ad as ws,ae as pl,af as sp,ag as Nt,ah as Rj,ai as Fj,aj as ap,ak as gl,al as rp,am as Ij,an as yc,ao as ys,ap as Da,aq as np,ar as fl,as as bl,at as Zt,au as Pj,av as Ej,aw as ip,ax as lp,ay as Mi,az as or,aA as vc,aB as jl,aC as qn,aD as op,aE as cp,aF as Nc,aG as yl,aH as vl,aI as Mo,aJ as _j,aK as Vs,aL as dp,aM as qi,aN as Mj,aO as Lj,aP as Oj,aQ as Nl,aR as wl,aS as wc,aT as $j,aU as hu,aV as Ba,aW as Gr,aX as Uj,aY as sn,aZ as pu,a_ as Bj,a$ as Vj,b0 as mp,b1 as gu,b2 as up,b3 as zj,b4 as Hj,b5 as Gj,b6 as Wj,b7 as xp,b8 as Yj,b9 as Qj,ba as Kj,bb as Cl,bc as Xj,bd as Zj,be as Jj,bf as qj}from"./ui-vendor-DgwXkk2Y.js";import{l as ey}from"./socket-vendor-TjCxX7sJ.js";import{c as yn,a as ty}from"./redux-vendor-tbZCm13o.js";import{u as Cs,a as Va,b as hp,c as sy,B as ay,R as ry,d as kt,O as ny}from"./router-vendor-HW_ujxKo.js";(function(){const s=document.createElement("link").relList;if(s&&s.supports&&s.supports("modulepreload"))return;for(const n of document.querySelectorAll('link[rel="modulepreload"]'))r(n);new MutationObserver(n=>{for(const i of n)if(i.type==="childList")for(const l of i.addedNodes)l.tagName==="LINK"&&l.rel==="modulepreload"&&r(l)}).observe(document,{childList:!0,subtree:!0});function a(n){const i={};return n.integrity&&(i.integrity=n.integrity),n.referrerPolicy&&(i.referrerPolicy=n.referrerPolicy),n.crossOrigin==="use-credentials"?i.credentials="include":n.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function r(n){if(n.ep)return;n.ep=!0;const i=a(n);fetch(n.href,i)}})();var Lo={},fu=Jb;Lo.createRoot=fu.createRoot,Lo.hydrateRoot=fu.hydrateRoot;var pp={exports:{}},gp={};var ci=x;function iy(t,s){return t===s&&(t!==0||1/t===1/s)||t!==t&&s!==s}var ly=typeof Object.is=="function"?Object.is:iy,oy=ci.useSyncExternalStore,cy=ci.useRef,dy=ci.useEffect,my=ci.useMemo,uy=ci.useDebugValue;gp.useSyncExternalStoreWithSelector=function(t,s,a,r,n){var i=cy(null);if(i.current===null){var l={hasValue:!1,value:null};i.current=l}else l=i.current;i=my(function(){function c(h){if(!d){if(d=!0,m=h,h=r(h),n!==void 0&&l.hasValue){var g=l.value;if(n(g,h))return p=g}return p=h}if(g=p,ly(m,h))return g;var f=r(h);return n!==void 0&&n(g,f)?(m=h,g):(m=h,p=f)}var d=!1,m,p,u=a===void 0?null:a;return[function(){return c(s())},u===null?void 0:function(){return c(u())}]},[s,a,r,n]);var o=oy(t,i[0],i[1]);return dy(function(){l.hasValue=!0,l.value=o},[o]),uy(o),o};pp.exports=gp;var xy=pp.exports;function hy(t){t()}function py(){let t=null,s=null;return{clear(){t=null,s=null},notify(){hy(()=>{let a=t;for(;a;)a.callback(),a=a.next})},get(){const a=[];let r=t;for(;r;)a.push(r),r=r.next;return a},subscribe(a){let r=!0;const n=s={callback:a,next:null,prev:s};return n.prev?n.prev.next=n:t=n,function(){!r||t===null||(r=!1,n.next?n.next.prev=n.prev:s=n.prev,n.prev?n.prev.next=n.next:t=n.next)}}}}var bu={notify(){},get:()=>[]};function gy(t,s){let a,r=bu,n=0,i=!1;function l(f){m();const b=r.subscribe(f);let j=!1;return()=>{j||(j=!0,b(),p())}}function o(){r.notify()}function c(){g.onStateChange&&g.onStateChange()}function d(){return i}function m(){n++,a||(a=t.subscribe(c),r=py())}function p(){n--,a&&n===0&&(a(),a=void 0,r.clear(),r=bu)}function u(){i||(i=!0,m())}function h(){i&&(i=!1,p())}const g={addNestedSub:l,notifyNestedSubs:o,handleChangeWrapper:c,isSubscribed:d,trySubscribe:u,tryUnsubscribe:h,getListeners:()=>r};return g}var fy=()=>typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u",by=fy(),jy=()=>typeof navigator<"u"&&navigator.product==="ReactNative",yy=jy(),vy=()=>by||yy?x.useLayoutEffect:x.useEffect,Ny=vy(),Vl=Symbol.for("react-redux-context"),zl=typeof globalThis<"u"?globalThis:{};function wy(){if(!x.createContext)return{};const t=zl[Vl]??(zl[Vl]=new Map);let s=t.get(x.createContext);return s||(s=x.createContext(null),t.set(x.createContext,s)),s}var ur=wy();function Cy(t){const{children:s,context:a,serverState:r,store:n}=t,i=x.useMemo(()=>{const c=gy(n);return{store:n,subscription:c,getServerState:r?()=>r:void 0}},[n,r]),l=x.useMemo(()=>n.getState(),[n]);Ny(()=>{const{subscription:c}=i;return c.onStateChange=c.notifyNestedSubs,c.trySubscribe(),l!==n.getState()&&c.notifyNestedSubs(),()=>{c.tryUnsubscribe(),c.onStateChange=void 0}},[i,l]);const o=a||ur;return x.createElement(o.Provider,{value:i},s)}var Sy=Cy;function Cc(t=ur){return function(){return x.useContext(t)}}var fp=Cc();function bp(t=ur){const s=t===ur?fp:Cc(t),a=()=>{const{store:r}=s();return r};return Object.assign(a,{withTypes:()=>a}),a}var Ay=bp();function Ty(t=ur){const s=t===ur?Ay:bp(t),a=()=>s().dispatch;return Object.assign(a,{withTypes:()=>a}),a}var ky=Ty(),Dy=(t,s)=>t===s;function Ry(t=ur){const s=t===ur?fp:Cc(t),a=(r,n={})=>{const{equalityFn:i=Dy}=typeof n=="function"?{equalityFn:n}:n,l=s(),{store:o,subscription:c,getServerState:d}=l;x.useRef(!0);const m=x.useCallback({[r.name](u){return r(u)}}[r.name],[r]),p=xy.useSyncExternalStoreWithSelector(c.addNestedSub,o.getState,d||o.getState,m,i);return x.useDebugValue(p),p};return Object.assign(a,{withTypes:()=>a}),a}var Fy=Ry(),Oo=function(t,s){return Oo=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(a,r){a.__proto__=r}||function(a,r){for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(a[n]=r[n])},Oo(t,s)};function Iy(t,s){if(typeof s!="function"&&s!==null)throw new TypeError("Class extends value "+String(s)+" is not a constructor or null");Oo(t,s);function a(){this.constructor=t}t.prototype=s===null?Object.create(s):(a.prototype=s.prototype,new a)}var el=function(){return el=Object.assign||function(s){for(var a,r=1,n=arguments.length;r0&&g[g.length-1])||S[0]!==6&&S[0]!==2)){b=0;continue}if(S[0]===3&&(!g||S[1]>g[0]&&S[1]0?setTimeout(h,N):h(null)}}window.addEventListener("storage",j),m.addToWaiting(j);var y=setTimeout(j,Math.max(0,p-Date.now()))})];case 1:return u.sent(),[2]}})})},m.addToWaiting=function(p){this.removeFromWaiting(p),m.waiters!==void 0&&m.waiters.push(p)},m.removeFromWaiting=function(p){m.waiters!==void 0&&(m.waiters=m.waiters.filter(function(u){return u!==p}))},m.notifyWaiters=function(){m.waiters!==void 0&&m.waiters.slice().forEach(function(p){return p()})},m.prototype.releaseLock=function(p){return a(this,void 0,void 0,function(){return r(this,function(u){switch(u.label){case 0:return[4,this.releaseLock__private__(p)];case 1:return[2,u.sent()]}})})},m.prototype.releaseLock__private__=function(p){return a(this,void 0,void 0,function(){var u,h,g,f;return r(this,function(b){switch(b.label){case 0:return u=this.storageHandler===void 0?l:this.storageHandler,h=i+"-"+p,(g=u.getItemSync(h))===null?[2]:(f=JSON.parse(g)).id!==this.id?[3,2]:[4,kr.default().lock(f.iat)];case 1:b.sent(),this.acquiredIatSet.delete(f.iat),u.removeItemSync(h),kr.default().unlock(f.iat),m.notifyWaiters(),b.label=2;case 2:return[2]}})})},m.lockCorrector=function(p){for(var u=Date.now()-5e3,h=p,g=[],f=0;;){var b=h.keySync(f);if(b===null)break;g.push(b),f++}for(var j=!1,y=0;y.")},_y=el(el({},Ey),{buildAuthorizeUrl:Zs,buildLogoutUrl:Zs,getAccessTokenSilently:Zs,getAccessTokenWithPopup:Zs,getIdTokenClaims:Zs,loginWithRedirect:Zs,loginWithPopup:Zs,connectAccountWithRedirect:Zs,logout:Zs,handleRedirectCallback:Zs,getDpopNonce:Zs,setDpopNonce:Zs,generateDpopProof:Zs,createFetcher:Zs}),My=x.createContext(_y);(function(t){Iy(s,t);function s(a,r){var n=t.call(this,r??a)||this;return n.error=a,n.error_description=r,Object.setPrototypeOf(n,s.prototype),n}return s})(Error);var Ly=function(t){return t===void 0&&(t=My),x.useContext(t)};const Hl="idToken",Gl="userData",Oy=()=>!0;class tt{static setAccessToken(s){}static getAccessToken(){return null}static setRefreshToken(s){}static getRefreshToken(){return null}static setIdToken(s){sessionStorage.setItem(Hl,s)}static getIdToken(){return sessionStorage.getItem(Hl)}static setUserData(s){localStorage.setItem(Gl,JSON.stringify(s))}static getUserData(){const s=localStorage.getItem(Gl);if(!s)return null;try{return JSON.parse(s)}catch{return null}}static clearAll(){try{sessionStorage.setItem("__logout_in_progress__","true"),sessionStorage.setItem("__force_logout__","true")}catch(s){console.warn("Could not set logout flags:",s)}try{localStorage.removeItem(Gl),sessionStorage.removeItem(Hl)}catch(s){console.warn("Error clearing user data:",s)}{try{sessionStorage.setItem("__logout_in_progress__","true"),sessionStorage.setItem("__force_logout__","true")}catch{}return}}static hasAccessToken(){return!!this.getUserData()}static hasRefreshToken(){return!!this.getUserData()}static isLocalhost(){return window.location.hostname==="localhost"||window.location.hostname==="127.0.0.1"||window.location.hostname===""}static isProduction(){return Oy()}static setAuthError(s){s?sessionStorage.setItem("__auth_error__",s):sessionStorage.removeItem("__auth_error__")}static getAuthError(){return sessionStorage.getItem("__auth_error__")}}function Ci(t,s=5){if(!t)return!0;try{const a=t.split(".");if(a.length!==3||!a[1])return!0;const n=JSON.parse(atob(a[1])).exp*1e3,i=Date.now(),l=s*60*1e3;return n-i{if(t.data instanceof FormData){const s=t.headers;delete s["Content-Type"],s.common&&typeof s.common=="object"&&delete s.common["Content-Type"],s.post&&typeof s.post=="object"&&delete s.post["Content-Type"]}return t},t=>Promise.reject(t));ve.interceptors.response.use(t=>t,async t=>{var a,r,n,i;const s=t.config;if(t.code==="ERR_NETWORK"||t.code==="ECONNREFUSED"||(a=t.message)!=null&&a.includes("ERR_CONNECTION_REFUSED"),((r=t.response)==null?void 0:r.status)===401){if(((i=(n=t.response)==null?void 0:n.data)==null?void 0:i.errorCode)==="SESSION_SUPERSEDED"){const l=tt.getIdToken(),o=sessionStorage.getItem("auth_provider")||(l!=null&&l.includes("tanflow")?"tanflow":"okta");return tt.setAuthError("SESSION_SUPERSEDED"),W.error("You have been logged out because an active session was detected from another device.",{duration:2e3,id:"session-superseded-toast"}),setTimeout(async()=>{sessionStorage.setItem("__logout_in_progress__","true"),sessionStorage.setItem("__force_logout__","true"),o==="tanflow"&&l?Np(l):wp(l||void 0)},1e3),Promise.reject(t)}if(!s._retry){s._retry=!0;const l=!0;try{const o=tt.getRefreshToken(),c=await ml.post(`${Tc}/auth/refresh`,l?{}:{refreshToken:o},{withCredentials:!0}),m=(c.data.data||c.data).accessToken;return ve(s)}catch(o){return tt.clearAll(),window.location.href="/",Promise.reject(o)}}}return Promise.reject(t)});async function Hy(t,s){const r=(await ve.post("/auth/login",{username:t,password:s},{withCredentials:!0})).data,n=r.data||r;return n.user&&tt.setUserData(n.user),n.accessToken&&n.refreshToken&&(tt.setAccessToken(n.accessToken),tt.setRefreshToken(n.refreshToken)),n}async function Gy(t,s){var a,r;try{const n=await ve.post("/auth/token-exchange",{code:t,redirectUri:s},{responseType:"json",headers:{"Content-Type":"application/json",Accept:"application/json"}});if(Array.isArray(n.data))throw console.error("❌ Response is an array (buffer issue):",{arrayLength:n.data.length,firstFew:n.data.slice(0,10),rawResponse:n}),new Error("Invalid response format: received array instead of JSON. Check Content-Type header.");const i=n.data,l=i.data||i;return l.user&&tt.setUserData(l.user),l.idToken&&tt.setIdToken(l.idToken),l.accessToken&&l.refreshToken&&(tt.setAccessToken(l.accessToken),tt.setRefreshToken(l.refreshToken)),l}catch(n){throw console.error("❌ Token exchange failed:",{message:n.message,response:(a=n.response)==null?void 0:a.data,status:(r=n.response)==null?void 0:r.status,code:t?`${t.substring(0,10)}...`:"MISSING",redirectUri:s}),n}}async function Wy(){var r;const t={},a=(await ve.post("/auth/refresh",t)).data;if((r=a.data)!=null&&r.accessToken||a.accessToken,a.success!==!1)return"cookie-based-auth";throw new Error("Failed to refresh token")}async function Bn(){const s=(await ve.get("/auth/me")).data;return s.data||s}async function Yy(){var t,s;try{await ml.post(`${Tc}/auth/logout`,{},{withCredentials:!0})}catch(a){console.error("📡 Logout API error:",a),console.error("📡 Error details:",{message:a.message,status:(t=a.response)==null?void 0:t.status,data:(s=a.response)==null?void 0:s.data})}}let Sr=null;function Qy(){return"https://reflow-uat.royalenfield.com"}function Vr(t){const s=Qy();return Sr||(Sr=ey(s,{withCredentials:!0,transports:["websocket","polling"],path:"/socket.io",reconnection:!0,reconnectionDelay:1e3,reconnectionAttempts:5}),Sr.on("connect",()=>{}),Sr.on("connect_error",a=>{console.error("[Socket] Connection error:",a.message)}),Sr.on("disconnect",a=>{}),Sr)}function Uo(t,s,a){a?t.emit("join:request",{requestId:s,userId:a}):t.emit("join:request",s)}function Cp(t,s){t.emit("leave:request",s)}function kc(t,s){t.emit("join:user",{userId:s})}const Ky=/^(localhost|127\.0\.0\.1)$/i;function Xy(){return typeof window>"u"?"":window.location.hostname||""}function Sp(){return Ky.test(Xy())}function Fn(){return!Sp()}function rr(t){return String(t||"").trim()}function Zy(t){const s=(t||"").toLowerCase();return s.includes("uat")||s.includes("dev")}function Jy(){const t=typeof window<"u"?window.location.hostname:"",s=Sp(),a=!s&&Zy(t),r=rr("https://dev-830839.oktapreview.com"),n=rr("0oa2jgzvrpdwx2iqd0h8"),i=rr(void 0),l=rr(void 0),o=rr(void 0),c=rr(void 0),d=rr(void 0),m=rr(void 0);return s?{domain:i||r,clientId:l||n}:a?{domain:o||r,clientId:c||n}:{domain:d||r,clientId:m||n}}const Ap=x.createContext(void 0);function qy({children:t}){const[s,a]=x.useState(!1),[r,n]=x.useState(!0),[i,l]=x.useState(null),[o,c]=x.useState(null),[d,m]=x.useState(!1),p=()=>{const A="https://reflow-uat.royalenfield.com".trim(),w="".trim(),I="".trim(),k=A.replace(/\/$/,""),T=window.location.hostname,v=/^(localhost|127\.0\.0\.1)$/i.test(T);return v&&I?I:w||(v?`${k}/login/callback`:/localhost|127\.0\.0\.1/i.test(A)?`${k}/login/callback`:`${(A||k).replace(/\/$/,"")}/login/callback`)};x.useEffect(()=>{const A=sessionStorage.getItem("__logout_in_progress__"),w=sessionStorage.getItem("__force_logout__");if(A==="true"||w==="true"){sessionStorage.removeItem("__logout_in_progress__"),sessionStorage.removeItem("__force_logout__"),tt.clearAll();try{localStorage.clear(),sessionStorage.clear()}catch(L){console.error("Error clearing storage:",L)}a(!1),l(null),n(!1),c(null);return}const I=new URLSearchParams(window.location.search),k=I.has("code"),T=sessionStorage.getItem("__logout_type__");if((I.has("logout")||I.has("okta_logged_out")||I.has("tanflow_logged_out")||T)&&!k){console.log("🚪 Logout parameter detected in URL, clearing all tokens and backend session"),Yy().catch(L=>console.warn("🚪 Final backend logout cleanup failed:",L)),tt.clearAll(),sessionStorage.removeItem("__logout_type__"),sessionStorage.removeItem("auth_provider"),sessionStorage.removeItem("tanflow_auth_state"),sessionStorage.removeItem("__logout_in_progress__"),sessionStorage.removeItem("__force_logout__"),sessionStorage.removeItem("tanflow_logged_out"),localStorage.clear(),a(!1),l(null),n(!1);try{sessionStorage.setItem("__force_reauth_after_logout__","true")}catch(L){console.warn("Could not set force reauth flag:",L)}window.history.replaceState({},document.title,"/");return}if(window.location.pathname==="/login/callback"||window.location.pathname==="/login/tanflow/callback")return;const _=tt.getAccessToken(),P=tt.getRefreshToken(),B=tt.getUserData(),R=_||P||B;if(Fn())d?n(!1):g();else{if(!R){a(!1),l(null),n(!1);return}d?n(!1):g()}},[d]),x.useEffect(()=>{if(!s)return;const A=Fn(),w=async()=>{if(A)try{await N()}catch(T){console.error("Silent refresh failed:",T)}else{const T=tt.getAccessToken();if(T&&Ci(T,5))try{await N()}catch(v){console.error("Silent refresh failed:",v)}}},I=A?10*60*1e3:5*60*1e3,k=setInterval(w,I);return()=>clearInterval(k)},[s]),x.useEffect(()=>{if(!s||!(i!=null&&i.userId))return;const A=Vr();kc(A,i.userId);const w=I=>{console.log("📡 [Socket] Session superseded event received:",I),tt.setAuthError("SESSION_SUPERSEDED"),W.error("Session Expired",{description:"You have been logged out because another session was started on a different device.",duration:4e3,id:"session-superseded-socket"}),setTimeout(()=>{j()},1e3)};return A.on("SESSION_SUPERSEDED",w),()=>{A.off("SESSION_SUPERSEDED",w)}},[s,i==null?void 0:i.userId]);const u=x.useRef(!1),h=x.useRef(null);x.useEffect(()=>{if(u.current||window.location.pathname!=="/login/callback")return;(async()=>{var B,R,M,L;const w=new URLSearchParams(window.location.search);if(sessionStorage.getItem("auth_provider")==="tanflow")return;const k=sessionStorage.getItem("__logout_type__");if((w.has("logout")||w.has("tanflow_logged_out")||w.has("okta_logged_out")||k)&&!w.get("code")){console.log("🚪 Logout redirect detected in callback, redirecting to home");const D=new URLSearchParams;(w.has("tanflow_logged_out")||k==="tanflow")&&D.set("tanflow_logged_out","true"),(w.has("okta_logged_out")||k==="okta")&&D.set("okta_logged_out","true"),w.has("logout")&&D.set("logout",w.get("logout")||Date.now().toString());const C=D.toString()?`/?${D.toString()}`:"/?logout="+Date.now();window.location.replace(C);return}u.current=!0;const v=w.get("code"),_=w.get("error");if(window.history.replaceState({},document.title,"/login/callback"),_){c(new Error(`Authentication error: ${_}`)),n(!1),sessionStorage.removeItem("auth_provider");return}if(!v){n(!1),sessionStorage.removeItem("auth_provider");return}const P=sessionStorage.getItem("__okta_auth_code_consumed__");if(P&&P===v){c(new Error("Authorization code already used. Please login again.")),n(!1),sessionStorage.removeItem("auth_provider");return}if(h.current!==v){h.current=v,sessionStorage.setItem("__okta_auth_code_consumed__",v);try{n(!0),a(!1),c(null);const D=p(),C=await Gy(v,D);l(C.user),a(!0),c(null),sessionStorage.removeItem("auth_provider"),window.history.replaceState({},document.title,"/")}catch(D){console.error("❌ Token exchange error in AuthContext:",D);const C=String(((R=(B=D==null?void 0:D.response)==null?void 0:B.data)==null?void 0:R.error)||"").toUpperCase(),G=String(((L=(M=D==null?void 0:D.response)==null?void 0:M.data)==null?void 0:L.message)||(D==null?void 0:D.message)||"").toLowerCase();if(C==="RELOGIN_REQUIRED"||G.includes("invalid or has expired")||G.includes("invalid_grant")||G.includes("authorization code already used")){a(!1),l(null),c(new Error("Login session expired. Please sign in again.")),sessionStorage.removeItem("auth_provider"),window.location.replace("/?relogin_required=1");return}c(D),a(!1),l(null),sessionStorage.removeItem("auth_provider"),u.current=!1}finally{n(!1)}}})()},[]);const g=async()=>{var w;if(d){n(!1);return}const A=Fn();try{if(n(!0),A){const T=tt.getUserData();try{const v=await Bn();l(v),tt.setUserData(v),a(!0)}catch(v){if(((w=v==null?void 0:v.response)==null?void 0:w.status)===401)try{await N();const _=await Bn();l(_),tt.setUserData(_),a(!0)}catch{tt.clearAll(),a(!1),l(null)}else v!=null&&v.isConnectionError?T?(l(T),a(!0)):(a(!1),l(null)):(tt.clearAll(),a(!1),l(null))}return}const I=tt.getAccessToken(),k=tt.getUserData();if(!I){a(!1),l(null),n(!1);return}if(Ci(I))try{await N();const T=tt.getAccessToken();if(T&&!Ci(T)){const v=tt.getUserData();if(v)l(v),a(!0);else try{const _=await Bn();l(_),tt.setUserData(_),a(!0)}catch{tt.clearAll(),a(!1),l(null)}}else tt.clearAll(),a(!1),l(null)}catch{tt.clearAll(),a(!1),l(null)}else if(k)l(k),a(!0);else try{const T=await Bn();l(T),tt.setUserData(T),a(!0)}catch{tt.clearAll(),a(!1),l(null)}}catch(I){console.error("Error checking auth status:",I),c(I),tt.clearAll(),a(!1),l(null)}finally{n(!1)}},f=async()=>{try{c(null);const{domain:A,clientId:w}=Jy();if(!A||!w)throw new Error("Missing Okta configuration (VITE_OKTA_DOMAIN / VITE_OKTA_CLIENT_ID).");const I=p(),k="code",T="openid profile email",v=Math.random().toString(36).substring(7);sessionStorage.setItem("auth_provider","okta");const _=new URLSearchParams(window.location.search),P=_.has("logout")||_.has("okta_logged_out")||_.has("tanflow_logged_out"),B=sessionStorage.getItem("__force_reauth_after_logout__")==="true",R=P||B;let M=`${A}/oauth2/default/v1/authorize?client_id=${w}&redirect_uri=${encodeURIComponent(I)}&response_type=${k}&scope=${encodeURIComponent(T)}&state=${v}`;R&&(M+="&prompt=login",sessionStorage.removeItem("__force_reauth_after_logout__")),window.location.href=M}catch(A){throw c(A),A}},b=async(A,w)=>{c(null),n(!0);try{const I=await Hy(A,w);l(I.user),a(!0),window.history.replaceState({},document.title,"/")}catch(I){throw c(I),a(!1),l(null),I}finally{n(!1)}},j=async()=>{try{const A=tt.getIdToken(),w=sessionStorage.getItem("auth_provider")||(A&&A.includes("tanflow")?"tanflow":null)||"okta";sessionStorage.setItem("__logout_in_progress__","true"),sessionStorage.setItem("__force_logout__","true"),m(!0),a(!1),l(null),c(null),n(!0);const I=sessionStorage.getItem("__logout_in_progress__"),k=sessionStorage.getItem("__force_logout__"),T=sessionStorage.getItem("auth_provider");if(tt.clearAll(),I&&sessionStorage.setItem("__logout_in_progress__",I),k&&sessionStorage.setItem("__force_logout__",k),A&&tt.setIdToken(A),T&&sessionStorage.setItem("auth_provider",T),await new Promise(v=>setTimeout(v,100)),w==="tanflow"&&A){console.log("🚪 Initiating Tanflow logout...");try{Np(A);return}catch(v){console.error("🚪 Tanflow logout error:",v)}}console.log("🚪 Using OKTA logout flow or fallback"),sessionStorage.removeItem("auth_provider"),wp(A||void 0)}catch(A){console.error("🚪 Logout error:",A);try{localStorage.clear(),sessionStorage.clear(),sessionStorage.setItem("__logout_in_progress__","true");const I=`${"https://reflow-uat.royalenfield.com".trim().replace(/\/$/,"")}/?okta_logged_out=true&logout=${Date.now()}`;window.location.replace(I)}catch{window.location.replace("/?logout="+Date.now())}}},y=async()=>{if(Fn()){if(s)return"cookie-based-auth";try{return await N(),s?"cookie-based-auth":null}catch{return null}}const w=tt.getAccessToken();if(w&&!Ci(w))return w;try{return await N(),tt.getAccessToken()}catch{return null}},N=async()=>{const A=Fn();try{const w=await Wy();if(A||w)return;throw new Error("Failed to refresh token")}catch(w){throw tt.clearAll(),a(!1),l(null),w}},S={isAuthenticated:s,isLoading:r,user:i,error:o,login:f,loginWithPassword:b,logout:j,getAccessTokenSilently:y,refreshTokenSilently:N};return e.jsx(Ap.Provider,{value:S,children:t})}function ev({children:t}){return e.jsx(qy,{children:t})}function us(){const t=x.useContext(Ap);if(t===void 0)throw new Error("useAuth must be used within an AuthProvider");return t}function Dr(t){return(t==null?void 0:t.role)==="ADMIN"}function In(t){return(t==null?void 0:t.role)==="MANAGEMENT"}function Sl(t){return(t==null?void 0:t.role)==="MANAGEMENT"||(t==null?void 0:t.role)==="ADMIN"}const wu=t=>typeof t=="boolean"?`${t}`:t===0?"0":t,Cu=zh,Dc=(t,s)=>a=>{var r;if((s==null?void 0:s.variants)==null)return Cu(t,a==null?void 0:a.class,a==null?void 0:a.className);const{variants:n,defaultVariants:i}=s,l=Object.keys(n).map(d=>{const m=a==null?void 0:a[d],p=i==null?void 0:i[d];if(m===null)return null;const u=wu(m)||wu(p);return n[d][u]}),o=a&&Object.entries(a).reduce((d,m)=>{let[p,u]=m;return u===void 0||(d[p]=u),d},{}),c=s==null||(r=s.compoundVariants)===null||r===void 0?void 0:r.reduce((d,m)=>{let{class:p,className:u,...h}=m;return Object.entries(h).every(g=>{let[f,b]=g;return Array.isArray(b)?b.includes({...i,...o}[f]):{...i,...o}[f]===b})?[...d,p,u]:d},[]);return Cu(t,l,c,a==null?void 0:a.class,a==null?void 0:a.className)},Rc="-",tv=t=>{const s=av(t),{conflictingClassGroups:a,conflictingClassGroupModifiers:r}=t;return{getClassGroupId:l=>{const o=l.split(Rc);return o[0]===""&&o.length!==1&&o.shift(),Tp(o,s)||sv(l)},getConflictingClassGroupIds:(l,o)=>{const c=a[l]||[];return o&&r[l]?[...c,...r[l]]:c}}},Tp=(t,s)=>{var l;if(t.length===0)return s.classGroupId;const a=t[0],r=s.nextPart.get(a),n=r?Tp(t.slice(1),r):void 0;if(n)return n;if(s.validators.length===0)return;const i=t.join(Rc);return(l=s.validators.find(({validator:o})=>o(i)))==null?void 0:l.classGroupId},Su=/^\[(.+)\]$/,sv=t=>{if(Su.test(t)){const s=Su.exec(t)[1],a=s==null?void 0:s.substring(0,s.indexOf(":"));if(a)return"arbitrary.."+a}},av=t=>{const{theme:s,prefix:a}=t,r={nextPart:new Map,validators:[]};return nv(Object.entries(t.classGroups),a).forEach(([i,l])=>{Bo(l,r,i,s)}),r},Bo=(t,s,a,r)=>{t.forEach(n=>{if(typeof n=="string"){const i=n===""?s:Au(s,n);i.classGroupId=a;return}if(typeof n=="function"){if(rv(n)){Bo(n(r),s,a,r);return}s.validators.push({validator:n,classGroupId:a});return}Object.entries(n).forEach(([i,l])=>{Bo(l,Au(s,i),a,r)})})},Au=(t,s)=>{let a=t;return s.split(Rc).forEach(r=>{a.nextPart.has(r)||a.nextPart.set(r,{nextPart:new Map,validators:[]}),a=a.nextPart.get(r)}),a},rv=t=>t.isThemeGetter,nv=(t,s)=>s?t.map(([a,r])=>{const n=r.map(i=>typeof i=="string"?s+i:typeof i=="object"?Object.fromEntries(Object.entries(i).map(([l,o])=>[s+l,o])):i);return[a,n]}):t,iv=t=>{if(t<1)return{get:()=>{},set:()=>{}};let s=0,a=new Map,r=new Map;const n=(i,l)=>{a.set(i,l),s++,s>t&&(s=0,r=a,a=new Map)};return{get(i){let l=a.get(i);if(l!==void 0)return l;if((l=r.get(i))!==void 0)return n(i,l),l},set(i,l){a.has(i)?a.set(i,l):n(i,l)}}},kp="!",lv=t=>{const{separator:s,experimentalParseClassName:a}=t,r=s.length===1,n=s[0],i=s.length,l=o=>{const c=[];let d=0,m=0,p;for(let b=0;bm?p-m:void 0;return{modifiers:c,hasImportantModifier:h,baseClassName:g,maybePostfixModifierPosition:f}};return a?o=>a({className:o,parseClassName:l}):l},ov=t=>{if(t.length<=1)return t;const s=[];let a=[];return t.forEach(r=>{r[0]==="["?(s.push(...a.sort(),r),a=[]):a.push(r)}),s.push(...a.sort()),s},cv=t=>({cache:iv(t.cacheSize),parseClassName:lv(t),...tv(t)}),dv=/\s+/,mv=(t,s)=>{const{parseClassName:a,getClassGroupId:r,getConflictingClassGroupIds:n}=s,i=[],l=t.trim().split(dv);let o="";for(let c=l.length-1;c>=0;c-=1){const d=l[c],{modifiers:m,hasImportantModifier:p,baseClassName:u,maybePostfixModifierPosition:h}=a(d);let g=!!h,f=r(g?u.substring(0,h):u);if(!f){if(!g){o=d+(o.length>0?" "+o:o);continue}if(f=r(u),!f){o=d+(o.length>0?" "+o:o);continue}g=!1}const b=ov(m).join(":"),j=p?b+kp:b,y=j+f;if(i.includes(y))continue;i.push(y);const N=n(f,g);for(let S=0;S0?" "+o:o)}return o};function uv(){let t=0,s,a,r="";for(;t{if(typeof t=="string")return t;let s,a="";for(let r=0;rp(m),t());return a=cv(d),r=a.cache.get,n=a.cache.set,i=o,o(c)}function o(c){const d=r(c);if(d)return d;const m=mv(c,a);return n(c,m),m}return function(){return i(uv.apply(null,arguments))}}const Kt=t=>{const s=a=>a[t]||[];return s.isThemeGetter=!0,s},Rp=/^\[(?:([a-z-]+):)?(.+)\]$/i,hv=/^\d+\/\d+$/,pv=new Set(["px","full","screen"]),gv=/^(\d+(\.\d+)?)?(xs|sm|md|lg|xl)$/,fv=/\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh|cq(w|h|i|b|min|max))|\b(calc|min|max|clamp)\(.+\)|^0$/,bv=/^(rgba?|hsla?|hwb|(ok)?(lab|lch))\(.+\)$/,jv=/^(inset_)?-?((\d+)?\.?(\d+)[a-z]+|0)_-?((\d+)?\.?(\d+)[a-z]+|0)/,yv=/^(url|image|image-set|cross-fade|element|(repeating-)?(linear|radial|conic)-gradient)\(.+\)$/,Ga=t=>un(t)||pv.has(t)||hv.test(t),nr=t=>vn(t,"length",kv),un=t=>!!t&&!Number.isNaN(Number(t)),Wl=t=>vn(t,"number",un),Pn=t=>!!t&&Number.isInteger(Number(t)),vv=t=>t.endsWith("%")&&un(t.slice(0,-1)),jt=t=>Rp.test(t),ir=t=>gv.test(t),Nv=new Set(["length","size","percentage"]),wv=t=>vn(t,Nv,Fp),Cv=t=>vn(t,"position",Fp),Sv=new Set(["image","url"]),Av=t=>vn(t,Sv,Rv),Tv=t=>vn(t,"",Dv),En=()=>!0,vn=(t,s,a)=>{const r=Rp.exec(t);return r?r[1]?typeof s=="string"?r[1]===s:s.has(r[1]):a(r[2]):!1},kv=t=>fv.test(t)&&!bv.test(t),Fp=()=>!1,Dv=t=>jv.test(t),Rv=t=>yv.test(t),Fv=()=>{const t=Kt("colors"),s=Kt("spacing"),a=Kt("blur"),r=Kt("brightness"),n=Kt("borderColor"),i=Kt("borderRadius"),l=Kt("borderSpacing"),o=Kt("borderWidth"),c=Kt("contrast"),d=Kt("grayscale"),m=Kt("hueRotate"),p=Kt("invert"),u=Kt("gap"),h=Kt("gradientColorStops"),g=Kt("gradientColorStopPositions"),f=Kt("inset"),b=Kt("margin"),j=Kt("opacity"),y=Kt("padding"),N=Kt("saturate"),S=Kt("scale"),A=Kt("sepia"),w=Kt("skew"),I=Kt("space"),k=Kt("translate"),T=()=>["auto","contain","none"],v=()=>["auto","hidden","clip","visible","scroll"],_=()=>["auto",jt,s],P=()=>[jt,s],B=()=>["",Ga,nr],R=()=>["auto",un,jt],M=()=>["bottom","center","left","left-bottom","left-top","right","right-bottom","right-top","top"],L=()=>["solid","dashed","dotted","double","none"],D=()=>["normal","multiply","screen","overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","hue","saturation","color","luminosity"],C=()=>["start","end","center","between","around","evenly","stretch"],G=()=>["","0",jt],U=()=>["auto","avoid","all","avoid-page","page","left","right","column"],$=()=>[un,jt];return{cacheSize:500,separator:":",theme:{colors:[En],spacing:[Ga,nr],blur:["none","",ir,jt],brightness:$(),borderColor:[t],borderRadius:["none","","full",ir,jt],borderSpacing:P(),borderWidth:B(),contrast:$(),grayscale:G(),hueRotate:$(),invert:G(),gap:P(),gradientColorStops:[t],gradientColorStopPositions:[vv,nr],inset:_(),margin:_(),opacity:$(),padding:P(),saturate:$(),scale:$(),sepia:G(),skew:$(),space:P(),translate:P()},classGroups:{aspect:[{aspect:["auto","square","video",jt]}],container:["container"],columns:[{columns:[ir]}],"break-after":[{"break-after":U()}],"break-before":[{"break-before":U()}],"break-inside":[{"break-inside":["auto","avoid","avoid-page","avoid-column"]}],"box-decoration":[{"box-decoration":["slice","clone"]}],box:[{box:["border","content"]}],display:["block","inline-block","inline","flex","inline-flex","table","inline-table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row-group","table-row","flow-root","grid","inline-grid","contents","list-item","hidden"],float:[{float:["right","left","none","start","end"]}],clear:[{clear:["left","right","both","none","start","end"]}],isolation:["isolate","isolation-auto"],"object-fit":[{object:["contain","cover","fill","none","scale-down"]}],"object-position":[{object:[...M(),jt]}],overflow:[{overflow:v()}],"overflow-x":[{"overflow-x":v()}],"overflow-y":[{"overflow-y":v()}],overscroll:[{overscroll:T()}],"overscroll-x":[{"overscroll-x":T()}],"overscroll-y":[{"overscroll-y":T()}],position:["static","fixed","absolute","relative","sticky"],inset:[{inset:[f]}],"inset-x":[{"inset-x":[f]}],"inset-y":[{"inset-y":[f]}],start:[{start:[f]}],end:[{end:[f]}],top:[{top:[f]}],right:[{right:[f]}],bottom:[{bottom:[f]}],left:[{left:[f]}],visibility:["visible","invisible","collapse"],z:[{z:["auto",Pn,jt]}],basis:[{basis:_()}],"flex-direction":[{flex:["row","row-reverse","col","col-reverse"]}],"flex-wrap":[{flex:["wrap","wrap-reverse","nowrap"]}],flex:[{flex:["1","auto","initial","none",jt]}],grow:[{grow:G()}],shrink:[{shrink:G()}],order:[{order:["first","last","none",Pn,jt]}],"grid-cols":[{"grid-cols":[En]}],"col-start-end":[{col:["auto",{span:["full",Pn,jt]},jt]}],"col-start":[{"col-start":R()}],"col-end":[{"col-end":R()}],"grid-rows":[{"grid-rows":[En]}],"row-start-end":[{row:["auto",{span:[Pn,jt]},jt]}],"row-start":[{"row-start":R()}],"row-end":[{"row-end":R()}],"grid-flow":[{"grid-flow":["row","col","dense","row-dense","col-dense"]}],"auto-cols":[{"auto-cols":["auto","min","max","fr",jt]}],"auto-rows":[{"auto-rows":["auto","min","max","fr",jt]}],gap:[{gap:[u]}],"gap-x":[{"gap-x":[u]}],"gap-y":[{"gap-y":[u]}],"justify-content":[{justify:["normal",...C()]}],"justify-items":[{"justify-items":["start","end","center","stretch"]}],"justify-self":[{"justify-self":["auto","start","end","center","stretch"]}],"align-content":[{content:["normal",...C(),"baseline"]}],"align-items":[{items:["start","end","center","baseline","stretch"]}],"align-self":[{self:["auto","start","end","center","stretch","baseline"]}],"place-content":[{"place-content":[...C(),"baseline"]}],"place-items":[{"place-items":["start","end","center","baseline","stretch"]}],"place-self":[{"place-self":["auto","start","end","center","stretch"]}],p:[{p:[y]}],px:[{px:[y]}],py:[{py:[y]}],ps:[{ps:[y]}],pe:[{pe:[y]}],pt:[{pt:[y]}],pr:[{pr:[y]}],pb:[{pb:[y]}],pl:[{pl:[y]}],m:[{m:[b]}],mx:[{mx:[b]}],my:[{my:[b]}],ms:[{ms:[b]}],me:[{me:[b]}],mt:[{mt:[b]}],mr:[{mr:[b]}],mb:[{mb:[b]}],ml:[{ml:[b]}],"space-x":[{"space-x":[I]}],"space-x-reverse":["space-x-reverse"],"space-y":[{"space-y":[I]}],"space-y-reverse":["space-y-reverse"],w:[{w:["auto","min","max","fit","svw","lvw","dvw",jt,s]}],"min-w":[{"min-w":[jt,s,"min","max","fit"]}],"max-w":[{"max-w":[jt,s,"none","full","min","max","fit","prose",{screen:[ir]},ir]}],h:[{h:[jt,s,"auto","min","max","fit","svh","lvh","dvh"]}],"min-h":[{"min-h":[jt,s,"min","max","fit","svh","lvh","dvh"]}],"max-h":[{"max-h":[jt,s,"min","max","fit","svh","lvh","dvh"]}],size:[{size:[jt,s,"auto","min","max","fit"]}],"font-size":[{text:["base",ir,nr]}],"font-smoothing":["antialiased","subpixel-antialiased"],"font-style":["italic","not-italic"],"font-weight":[{font:["thin","extralight","light","normal","medium","semibold","bold","extrabold","black",Wl]}],"font-family":[{font:[En]}],"fvn-normal":["normal-nums"],"fvn-ordinal":["ordinal"],"fvn-slashed-zero":["slashed-zero"],"fvn-figure":["lining-nums","oldstyle-nums"],"fvn-spacing":["proportional-nums","tabular-nums"],"fvn-fraction":["diagonal-fractions","stacked-fractions"],tracking:[{tracking:["tighter","tight","normal","wide","wider","widest",jt]}],"line-clamp":[{"line-clamp":["none",un,Wl]}],leading:[{leading:["none","tight","snug","normal","relaxed","loose",Ga,jt]}],"list-image":[{"list-image":["none",jt]}],"list-style-type":[{list:["none","disc","decimal",jt]}],"list-style-position":[{list:["inside","outside"]}],"placeholder-color":[{placeholder:[t]}],"placeholder-opacity":[{"placeholder-opacity":[j]}],"text-alignment":[{text:["left","center","right","justify","start","end"]}],"text-color":[{text:[t]}],"text-opacity":[{"text-opacity":[j]}],"text-decoration":["underline","overline","line-through","no-underline"],"text-decoration-style":[{decoration:[...L(),"wavy"]}],"text-decoration-thickness":[{decoration:["auto","from-font",Ga,nr]}],"underline-offset":[{"underline-offset":["auto",Ga,jt]}],"text-decoration-color":[{decoration:[t]}],"text-transform":["uppercase","lowercase","capitalize","normal-case"],"text-overflow":["truncate","text-ellipsis","text-clip"],"text-wrap":[{text:["wrap","nowrap","balance","pretty"]}],indent:[{indent:P()}],"vertical-align":[{align:["baseline","top","middle","bottom","text-top","text-bottom","sub","super",jt]}],whitespace:[{whitespace:["normal","nowrap","pre","pre-line","pre-wrap","break-spaces"]}],break:[{break:["normal","words","all","keep"]}],hyphens:[{hyphens:["none","manual","auto"]}],content:[{content:["none",jt]}],"bg-attachment":[{bg:["fixed","local","scroll"]}],"bg-clip":[{"bg-clip":["border","padding","content","text"]}],"bg-opacity":[{"bg-opacity":[j]}],"bg-origin":[{"bg-origin":["border","padding","content"]}],"bg-position":[{bg:[...M(),Cv]}],"bg-repeat":[{bg:["no-repeat",{repeat:["","x","y","round","space"]}]}],"bg-size":[{bg:["auto","cover","contain",wv]}],"bg-image":[{bg:["none",{"gradient-to":["t","tr","r","br","b","bl","l","tl"]},Av]}],"bg-color":[{bg:[t]}],"gradient-from-pos":[{from:[g]}],"gradient-via-pos":[{via:[g]}],"gradient-to-pos":[{to:[g]}],"gradient-from":[{from:[h]}],"gradient-via":[{via:[h]}],"gradient-to":[{to:[h]}],rounded:[{rounded:[i]}],"rounded-s":[{"rounded-s":[i]}],"rounded-e":[{"rounded-e":[i]}],"rounded-t":[{"rounded-t":[i]}],"rounded-r":[{"rounded-r":[i]}],"rounded-b":[{"rounded-b":[i]}],"rounded-l":[{"rounded-l":[i]}],"rounded-ss":[{"rounded-ss":[i]}],"rounded-se":[{"rounded-se":[i]}],"rounded-ee":[{"rounded-ee":[i]}],"rounded-es":[{"rounded-es":[i]}],"rounded-tl":[{"rounded-tl":[i]}],"rounded-tr":[{"rounded-tr":[i]}],"rounded-br":[{"rounded-br":[i]}],"rounded-bl":[{"rounded-bl":[i]}],"border-w":[{border:[o]}],"border-w-x":[{"border-x":[o]}],"border-w-y":[{"border-y":[o]}],"border-w-s":[{"border-s":[o]}],"border-w-e":[{"border-e":[o]}],"border-w-t":[{"border-t":[o]}],"border-w-r":[{"border-r":[o]}],"border-w-b":[{"border-b":[o]}],"border-w-l":[{"border-l":[o]}],"border-opacity":[{"border-opacity":[j]}],"border-style":[{border:[...L(),"hidden"]}],"divide-x":[{"divide-x":[o]}],"divide-x-reverse":["divide-x-reverse"],"divide-y":[{"divide-y":[o]}],"divide-y-reverse":["divide-y-reverse"],"divide-opacity":[{"divide-opacity":[j]}],"divide-style":[{divide:L()}],"border-color":[{border:[n]}],"border-color-x":[{"border-x":[n]}],"border-color-y":[{"border-y":[n]}],"border-color-s":[{"border-s":[n]}],"border-color-e":[{"border-e":[n]}],"border-color-t":[{"border-t":[n]}],"border-color-r":[{"border-r":[n]}],"border-color-b":[{"border-b":[n]}],"border-color-l":[{"border-l":[n]}],"divide-color":[{divide:[n]}],"outline-style":[{outline:["",...L()]}],"outline-offset":[{"outline-offset":[Ga,jt]}],"outline-w":[{outline:[Ga,nr]}],"outline-color":[{outline:[t]}],"ring-w":[{ring:B()}],"ring-w-inset":["ring-inset"],"ring-color":[{ring:[t]}],"ring-opacity":[{"ring-opacity":[j]}],"ring-offset-w":[{"ring-offset":[Ga,nr]}],"ring-offset-color":[{"ring-offset":[t]}],shadow:[{shadow:["","inner","none",ir,Tv]}],"shadow-color":[{shadow:[En]}],opacity:[{opacity:[j]}],"mix-blend":[{"mix-blend":[...D(),"plus-lighter","plus-darker"]}],"bg-blend":[{"bg-blend":D()}],filter:[{filter:["","none"]}],blur:[{blur:[a]}],brightness:[{brightness:[r]}],contrast:[{contrast:[c]}],"drop-shadow":[{"drop-shadow":["","none",ir,jt]}],grayscale:[{grayscale:[d]}],"hue-rotate":[{"hue-rotate":[m]}],invert:[{invert:[p]}],saturate:[{saturate:[N]}],sepia:[{sepia:[A]}],"backdrop-filter":[{"backdrop-filter":["","none"]}],"backdrop-blur":[{"backdrop-blur":[a]}],"backdrop-brightness":[{"backdrop-brightness":[r]}],"backdrop-contrast":[{"backdrop-contrast":[c]}],"backdrop-grayscale":[{"backdrop-grayscale":[d]}],"backdrop-hue-rotate":[{"backdrop-hue-rotate":[m]}],"backdrop-invert":[{"backdrop-invert":[p]}],"backdrop-opacity":[{"backdrop-opacity":[j]}],"backdrop-saturate":[{"backdrop-saturate":[N]}],"backdrop-sepia":[{"backdrop-sepia":[A]}],"border-collapse":[{border:["collapse","separate"]}],"border-spacing":[{"border-spacing":[l]}],"border-spacing-x":[{"border-spacing-x":[l]}],"border-spacing-y":[{"border-spacing-y":[l]}],"table-layout":[{table:["auto","fixed"]}],caption:[{caption:["top","bottom"]}],transition:[{transition:["none","all","","colors","opacity","shadow","transform",jt]}],duration:[{duration:$()}],ease:[{ease:["linear","in","out","in-out",jt]}],delay:[{delay:$()}],animate:[{animate:["none","spin","ping","pulse","bounce",jt]}],transform:[{transform:["","gpu","none"]}],scale:[{scale:[S]}],"scale-x":[{"scale-x":[S]}],"scale-y":[{"scale-y":[S]}],rotate:[{rotate:[Pn,jt]}],"translate-x":[{"translate-x":[k]}],"translate-y":[{"translate-y":[k]}],"skew-x":[{"skew-x":[w]}],"skew-y":[{"skew-y":[w]}],"transform-origin":[{origin:["center","top","top-right","right","bottom-right","bottom","bottom-left","left","top-left",jt]}],accent:[{accent:["auto",t]}],appearance:[{appearance:["none","auto"]}],cursor:[{cursor:["auto","default","pointer","wait","text","move","help","not-allowed","none","context-menu","progress","cell","crosshair","vertical-text","alias","copy","no-drop","grab","grabbing","all-scroll","col-resize","row-resize","n-resize","e-resize","s-resize","w-resize","ne-resize","nw-resize","se-resize","sw-resize","ew-resize","ns-resize","nesw-resize","nwse-resize","zoom-in","zoom-out",jt]}],"caret-color":[{caret:[t]}],"pointer-events":[{"pointer-events":["none","auto"]}],resize:[{resize:["none","y","x",""]}],"scroll-behavior":[{scroll:["auto","smooth"]}],"scroll-m":[{"scroll-m":P()}],"scroll-mx":[{"scroll-mx":P()}],"scroll-my":[{"scroll-my":P()}],"scroll-ms":[{"scroll-ms":P()}],"scroll-me":[{"scroll-me":P()}],"scroll-mt":[{"scroll-mt":P()}],"scroll-mr":[{"scroll-mr":P()}],"scroll-mb":[{"scroll-mb":P()}],"scroll-ml":[{"scroll-ml":P()}],"scroll-p":[{"scroll-p":P()}],"scroll-px":[{"scroll-px":P()}],"scroll-py":[{"scroll-py":P()}],"scroll-ps":[{"scroll-ps":P()}],"scroll-pe":[{"scroll-pe":P()}],"scroll-pt":[{"scroll-pt":P()}],"scroll-pr":[{"scroll-pr":P()}],"scroll-pb":[{"scroll-pb":P()}],"scroll-pl":[{"scroll-pl":P()}],"snap-align":[{snap:["start","end","center","align-none"]}],"snap-stop":[{snap:["normal","always"]}],"snap-type":[{snap:["none","x","y","both"]}],"snap-strictness":[{snap:["mandatory","proximity"]}],touch:[{touch:["auto","none","manipulation"]}],"touch-x":[{"touch-pan":["x","left","right"]}],"touch-y":[{"touch-pan":["y","up","down"]}],"touch-pz":["touch-pinch-zoom"],select:[{select:["none","text","all","auto"]}],"will-change":[{"will-change":["auto","scroll","contents","transform",jt]}],fill:[{fill:[t,"none"]}],"stroke-w":[{stroke:[Ga,nr,Wl]}],stroke:[{stroke:[t,"none"]}],sr:["sr-only","not-sr-only"],"forced-color-adjust":[{"forced-color-adjust":["auto","none"]}]},conflictingClassGroups:{overflow:["overflow-x","overflow-y"],overscroll:["overscroll-x","overscroll-y"],inset:["inset-x","inset-y","start","end","top","right","bottom","left"],"inset-x":["right","left"],"inset-y":["top","bottom"],flex:["basis","grow","shrink"],gap:["gap-x","gap-y"],p:["px","py","ps","pe","pt","pr","pb","pl"],px:["pr","pl"],py:["pt","pb"],m:["mx","my","ms","me","mt","mr","mb","ml"],mx:["mr","ml"],my:["mt","mb"],size:["w","h"],"font-size":["leading"],"fvn-normal":["fvn-ordinal","fvn-slashed-zero","fvn-figure","fvn-spacing","fvn-fraction"],"fvn-ordinal":["fvn-normal"],"fvn-slashed-zero":["fvn-normal"],"fvn-figure":["fvn-normal"],"fvn-spacing":["fvn-normal"],"fvn-fraction":["fvn-normal"],"line-clamp":["display","overflow"],rounded:["rounded-s","rounded-e","rounded-t","rounded-r","rounded-b","rounded-l","rounded-ss","rounded-se","rounded-ee","rounded-es","rounded-tl","rounded-tr","rounded-br","rounded-bl"],"rounded-s":["rounded-ss","rounded-es"],"rounded-e":["rounded-se","rounded-ee"],"rounded-t":["rounded-tl","rounded-tr"],"rounded-r":["rounded-tr","rounded-br"],"rounded-b":["rounded-br","rounded-bl"],"rounded-l":["rounded-tl","rounded-bl"],"border-spacing":["border-spacing-x","border-spacing-y"],"border-w":["border-w-s","border-w-e","border-w-t","border-w-r","border-w-b","border-w-l"],"border-w-x":["border-w-r","border-w-l"],"border-w-y":["border-w-t","border-w-b"],"border-color":["border-color-s","border-color-e","border-color-t","border-color-r","border-color-b","border-color-l"],"border-color-x":["border-color-r","border-color-l"],"border-color-y":["border-color-t","border-color-b"],"scroll-m":["scroll-mx","scroll-my","scroll-ms","scroll-me","scroll-mt","scroll-mr","scroll-mb","scroll-ml"],"scroll-mx":["scroll-mr","scroll-ml"],"scroll-my":["scroll-mt","scroll-mb"],"scroll-p":["scroll-px","scroll-py","scroll-ps","scroll-pe","scroll-pt","scroll-pr","scroll-pb","scroll-pl"],"scroll-px":["scroll-pr","scroll-pl"],"scroll-py":["scroll-pt","scroll-pb"],touch:["touch-x","touch-y","touch-pz"],"touch-x":["touch"],"touch-y":["touch"],"touch-pz":["touch"]},conflictingClassGroupModifiers:{"font-size":["leading"]}}},Iv=xv(Fv);function Le(...t){return Iv(zh(t))}const ei=Dc("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",{variants:{variant:{default:"bg-primary text-primary-foreground hover:bg-primary/90",destructive:"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",outline:"border bg-background text-foreground hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",secondary:"bg-secondary text-secondary-foreground hover:bg-secondary/80",ghost:"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",link:"text-primary underline-offset-4 hover:underline"},size:{default:"h-9 px-4 py-2 has-[>svg]:px-3",sm:"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",lg:"h-10 rounded-md px-6 has-[>svg]:px-4",icon:"size-9 rounded-md"}},defaultVariants:{variant:"default",size:"default"}}),E=x.forwardRef(({className:t,variant:s,size:a,asChild:r=!1,...n},i)=>{const l=r?Ph:"button";return e.jsx(l,{"data-slot":"button",className:Le(ei({variant:s,size:a,className:t})),ref:i,...n})});E.displayName="Button";function Z({className:t,...s}){return e.jsx("div",{"data-slot":"card",className:Le("bg-card text-card-foreground flex flex-col gap-6 rounded-xl border",t),...s})}function ce({className:t,...s}){return e.jsx("div",{"data-slot":"card-header",className:Le("@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 pt-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",t),...s})}function de({className:t,...s}){return e.jsx("h4",{"data-slot":"card-title",className:Le("leading-none",t),...s})}function we({className:t,...s}){return e.jsx("p",{"data-slot":"card-description",className:Le("text-muted-foreground",t),...s})}function J({className:t,...s}){return e.jsx("div",{"data-slot":"card-content",className:Le("px-6 [&:last-child]:pb-6",t),...s})}const Al="/assets/Re_Logo-By51taPS.png",Tu="/assets/landing_page_image-ClTD-4qZ.jpg";function Pv(){var p,u;const{login:t,isLoading:s,error:a}=us(),[r,n]=x.useState(!1),[i,l]=x.useState(null),[o,c]=x.useState(!1);x.useEffect(()=>{const h=new Image;h.src=Tu,h.onload=()=>c(!0),h.complete&&c(!0)},[]);const d=async()=>{const h=sessionStorage.getItem("__force_reauth_after_logout__");localStorage.clear(),sessionStorage.clear(),h&&sessionStorage.setItem("__force_reauth_after_logout__",h);try{sessionStorage.setItem("auth_provider","okta"),await t()}catch(g){console.error("OKTA LOGIN ERROR",g)}},m=()=>{l(null),localStorage.clear(),sessionStorage.clear(),sessionStorage.setItem("auth_provider","tanflow"),n(!0);try{By()}catch(h){const g=h instanceof Error?h.message:"Dealer login failed. Check console for details.";console.error("TANFLOW LOGIN ERROR",h),l(g),n(!1)}};return a&&console.error("Auth Error:",{message:a.message,error:a}),e.jsxs("div",{className:"min-h-screen flex items-center justify-center p-4 relative overflow-hidden",style:{backgroundImage:o?`url(${Tu})`:"none",backgroundSize:"cover",backgroundPosition:"center",backgroundRepeat:"no-repeat"},children:[!o&&e.jsx("div",{className:"absolute inset-0 bg-gradient-to-br from-slate-900 to-slate-800"}),e.jsx("div",{className:"absolute inset-0 bg-black/50 backdrop-blur-[2px]","aria-hidden":!0}),e.jsx("div",{className:"absolute inset-0 bg-black/30","aria-hidden":!0}),e.jsxs(Z,{className:"w-full max-w-md shadow-2xl relative z-10 bg-gray-900/95 border border-gray-700 text-white",children:[e.jsx(ce,{className:"space-y-1 text-center pb-6 pt-8",children:e.jsxs("div",{className:"flex flex-col items-center justify-center",children:[e.jsx("img",{src:Al,alt:"Royal Enfield",className:"h-9 w-auto max-w-[180px] object-contain mb-2"}),e.jsx("p",{className:"text-sm text-gray-400",children:"Approval Portal"})]})}),e.jsxs(J,{className:"space-y-5 pb-8 px-8",children:[a&&e.jsxs("div",{className:"bg-red-900/40 border border-red-700 text-red-200 px-4 py-3 rounded-lg text-sm",children:[e.jsx("p",{className:"font-medium",children:"Authentication Error"}),e.jsx("p",{children:a.message}),(((p=a.message)==null?void 0:p.includes("401"))||((u=a.message)==null?void 0:u.toLowerCase().includes("unauthorized")))&&e.jsxs("p",{className:"mt-2 text-xs text-red-300",children:["If you see 401 from Okta: your Okta admin must add this site’s URL to ",e.jsx("strong",{children:"Trusted Origins"})," and ensure the RE Employee application is active. Use ",e.jsx("strong",{children:"Dealer Login"})," if you are a dealer."]})]}),i&&e.jsxs("div",{className:"bg-amber-900/40 border border-amber-700 text-amber-200 px-4 py-3 rounded-lg text-sm",children:[e.jsx("p",{className:"font-medium",children:"Dealer Login (Tanflow)"}),e.jsx("p",{children:i})]}),e.jsx(E,{onClick:d,disabled:s||r,className:"w-full h-12 bg-re-red hover:bg-re-red/90 text-white font-semibold text-base border-0",size:"lg",children:s?e.jsx("div",{className:"h-5 w-5 animate-spin rounded-full border-2 border-white border-t-transparent"}):e.jsxs(e.Fragment,{children:[e.jsx(js,{className:"mr-2 h-5 w-5"}),"RE Employee Login"]})}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("span",{className:"flex-1 h-px bg-gray-600"}),e.jsx("span",{className:"text-sm text-gray-400 uppercase tracking-wide",children:"Or"}),e.jsx("span",{className:"flex-1 h-px bg-gray-600"})]}),e.jsx(E,{onClick:m,disabled:s||r,className:"w-full h-12 bg-blue-600 hover:bg-blue-700 text-white font-semibold text-base border-0",size:"lg",children:r?e.jsx("div",{className:"h-5 w-5 animate-spin rounded-full border-2 border-white border-t-transparent"}):e.jsxs(e.Fragment,{children:[e.jsx(fs,{className:"mr-2 h-5 w-5"}),"Dealer Login"]})}),e.jsxs("div",{className:"text-center pt-2",children:[e.jsx("p",{className:"text-sm text-gray-400",children:"Secure Single Sign On"}),e.jsx("p",{className:"text-xs text-gray-500 mt-1",children:"Choose your authentication provider."})]})]})]})]})}function Ip(){const{isAuthenticated:t,isLoading:s,error:a,user:r,login:n}=us(),[i,l]=x.useState("exchanging");x.useEffect(()=>{if(a){l("error");return}if(s){const d=new URLSearchParams(window.location.search).get("code");l(d&&!r?"exchanging":r&&!t?"fetching":"exchanging")}else r&&t&&l("complete")},[t,s,a,r]);const o=()=>{switch(i){case"exchanging":return"Exchanging authorization code...";case"fetching":return"Fetching your profile...";case"complete":return"Authentication successful!";case"error":return"Authentication failed";default:return"Completing authentication..."}};return e.jsxs("div",{className:"min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900",children:[e.jsx("div",{className:"absolute inset-0 bg-[url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHZpZXdCb3g9IjAgMCA2MCA2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxnIGZpbGw9IiMxZTIxMmQiIGZpbGwtb3BhY2l0eT0iMC4wNSI+PGNpcmNsZSBjeD0iMzAiIGN5PSIzMCIgcj0iMzAiLz48L2c+PC9nPjwvc3ZnPg==')] opacity-20"}),e.jsxs("div",{className:"relative z-10 text-center px-4 max-w-md w-full",children:[e.jsx("div",{className:"mb-8",children:e.jsxs("div",{className:"flex flex-col items-center justify-center",children:[e.jsx("img",{src:Al,alt:"Royal Enfield Logo",className:"h-10 w-auto max-w-[168px] object-contain mb-2"}),e.jsx("p",{className:"text-xs text-gray-400 text-center truncate",children:"Approval Portal"})]})}),e.jsxs("div",{className:"bg-white/10 backdrop-blur-xl rounded-2xl p-8 shadow-2xl border border-white/20",children:[e.jsx("div",{className:"mb-6 flex justify-center",children:i==="error"?e.jsxs("div",{className:"relative",children:[e.jsx("div",{className:"absolute inset-0 animate-ping opacity-75",children:e.jsx(Je,{className:"w-16 h-16 text-red-500"})}),e.jsx(Je,{className:"w-16 h-16 text-red-500 relative"})]}):i==="complete"?e.jsxs("div",{className:"relative",children:[e.jsx("div",{className:"absolute inset-0 animate-ping opacity-75",children:e.jsx(ps,{className:"w-16 h-16 text-green-500"})}),e.jsx(ps,{className:"w-16 h-16 text-green-500 relative"})]}):e.jsxs("div",{className:"relative",children:[e.jsx(ze,{className:"w-16 h-16 animate-spin text-re-red"}),e.jsx("div",{className:"absolute inset-0 border-4 rounded-full border-re-red/20"}),e.jsx("div",{className:"absolute inset-0 border-4 border-transparent border-t-re-red rounded-full animate-spin"})]})}),e.jsxs("div",{className:"mb-6",children:[e.jsx("h2",{className:"text-xl font-semibold text-white mb-2",children:i==="complete"?"Welcome Back!":i==="error"?"Authentication Error":"Authenticating"}),e.jsx("p",{className:"text-slate-300 text-sm",children:o()})]}),i!=="error"&&e.jsxs("div",{className:"space-y-3 mb-6",children:[e.jsxs("div",{className:`flex items-center gap-3 text-sm transition-all duration-500 ${i==="exchanging"?"text-white":"text-slate-400"}`,children:[e.jsx("div",{className:`w-2 h-2 rounded-full transition-all duration-500 ${i==="exchanging"?"bg-re-red animate-pulse":"bg-slate-600"}`}),e.jsx("span",{children:"Validating credentials"})]}),e.jsxs("div",{className:`flex items-center gap-3 text-sm transition-all duration-500 ${i==="fetching"?"text-white":"text-slate-400"}`,children:[e.jsx("div",{className:`w-2 h-2 rounded-full transition-all duration-500 ${i==="fetching"?"bg-re-red animate-pulse":"bg-slate-600"}`}),e.jsx("span",{children:"Loading your profile"})]}),i==="complete"&&e.jsxs("div",{className:"flex items-center gap-3 text-sm transition-all duration-500 text-white",children:[e.jsx("div",{className:"w-2 h-2 rounded-full transition-all duration-500 bg-green-500"}),e.jsx("span",{children:"Setting up your session"})]})]}),i==="error"&&a&&e.jsxs("div",{className:"mt-6 p-4 bg-red-500/10 border border-red-500/20 rounded-lg",children:[e.jsx("p",{className:"text-red-400 text-sm",children:a.message||"An error occurred during authentication"}),e.jsx("button",{onClick:async()=>{try{await n()}catch{window.location.href="/"}},className:"mt-4 text-sm text-red-400 hover:text-red-300 underline",children:"Retry Login"})]}),i!=="error"&&i!=="complete"&&e.jsxs("div",{className:"mt-6",children:[e.jsx("div",{className:"h-1.5 bg-slate-700/50 rounded-full overflow-hidden",children:e.jsx("div",{className:"h-full bg-re-red rounded-full animate-pulse",style:{animation:"progress 2s ease-in-out infinite"}})}),e.jsx("style",{children:` - @keyframes progress { - 0%, 100% { width: 20%; } - 50% { width: 80%; } - } - `})]})]}),e.jsx("p",{className:"mt-6 text-slate-500 text-xs",children:i==="complete"?"Loading dashboard...":"Please wait while we secure your session"})]}),e.jsxs("div",{className:"absolute inset-0 overflow-hidden pointer-events-none",children:[e.jsx("div",{className:"absolute top-1/4 left-1/4 w-96 h-96 bg-re-red/5 rounded-full blur-3xl animate-pulse"}),e.jsx("div",{className:"absolute bottom-1/4 right-1/4 w-96 h-96 bg-re-red/5 rounded-full blur-3xl animate-pulse delay-1000"})]})]})}function Ev(){const{isAuthenticated:t,isLoading:s,error:a,user:r}=us(),[n,i]=x.useState("exchanging"),[l,o]=x.useState(""),c=x.useRef(!1);x.useEffect(()=>{if(a){i("error");return}if(s){const p=new URLSearchParams(window.location.search).get("code");i(p&&!r?"exchanging":r&&!t?"fetching":"exchanging")}else r&&t&&(i("complete"),setTimeout(()=>{window.location.href="/"},1e3))},[t,s,a,r]),x.useEffect(()=>{if(c.current||window.location.pathname!=="/login/callback")return;const m=new URLSearchParams(window.location.search),p=m.get("code"),u=m.get("error");if(!p&&!u){console.log("🚪 Logout redirect detected: no code, no error - redirecting to home immediately"),c.current=!0;const f=new URLSearchParams;f.set("tanflow_logged_out","true"),f.set("logout",Date.now().toString());const b=`/?${f.toString()}`;console.log("🚪 Redirecting to:",b),window.location.replace(b);return}if(sessionStorage.getItem("auth_provider")!=="tanflow")return;(async()=>{c.current=!0;const f=new URLSearchParams(window.location.search),b=f.get("code"),j=f.get("state"),y=f.get("error");if(window.history.replaceState({},document.title,"/login/callback"),y){i("error"),sessionStorage.removeItem("auth_provider"),sessionStorage.removeItem("tanflow_auth_state");return}const N=sessionStorage.getItem("tanflow_auth_state");if(j&&j!==N){i("error"),sessionStorage.removeItem("auth_provider"),sessionStorage.removeItem("tanflow_auth_state");return}if(!b){i("error"),sessionStorage.removeItem("auth_provider"),sessionStorage.removeItem("tanflow_auth_state");return}try{i("exchanging");const S=await Vy(b,j||"");sessionStorage.removeItem("tanflow_auth_state"),i("fetching");const A=S.user||await Bn();if(A)tt.setUserData(A),i("complete"),setTimeout(()=>{window.history.replaceState({},document.title,"/"),window.location.href="/"},1e3);else throw new Error("User data not received")}catch(S){console.error("Tanflow callback error:",S),i("error"),o(S.message||"Authentication failed"),sessionStorage.removeItem("auth_provider"),sessionStorage.removeItem("tanflow_auth_state")}})()},[]);const d=()=>{switch(n){case"exchanging":return"Exchanging authorization code...";case"fetching":return"Fetching your profile...";case"complete":return"Authentication successful!";case"error":return"Authentication failed";default:return"Completing authentication..."}};return e.jsxs("div",{className:"min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900",children:[e.jsx("div",{className:"absolute inset-0 bg-[url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHZpZXdCb3g9IjAgMCA2MCA2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxnIGZpbGw9IiMxZTIxMmQiIGZpbGwtb3BhY2l0eT0iMC4wNSI+PGNpcmNsZSBjeD0iMzAiIGN5PSIzMCIgcj0iMzAiLz48L2c+PC9nPjwvc3ZnPg==')] opacity-20"}),e.jsxs("div",{className:"relative z-10 text-center px-4 max-w-md w-full",children:[e.jsx("div",{className:"mb-8",children:e.jsxs("div",{className:"flex flex-col items-center justify-center",children:[e.jsx("img",{src:Al,alt:"Royal Enfield Logo",className:"h-10 w-auto max-w-[168px] object-contain mb-2"}),e.jsx("p",{className:"text-xs text-gray-400 text-center truncate",children:"Approval Portal"})]})}),e.jsxs("div",{className:"bg-white/10 backdrop-blur-xl rounded-2xl p-8 shadow-2xl border border-white/20",children:[e.jsx("div",{className:"mb-6 flex justify-center",children:n==="error"?e.jsxs("div",{className:"relative",children:[e.jsx("div",{className:"absolute inset-0 animate-ping opacity-75",children:e.jsx(Je,{className:"w-16 h-16 text-red-500"})}),e.jsx(Je,{className:"w-16 h-16 text-red-500 relative"})]}):n==="complete"?e.jsxs("div",{className:"relative",children:[e.jsx("div",{className:"absolute inset-0 animate-ping opacity-75",children:e.jsx(ps,{className:"w-16 h-16 text-green-500"})}),e.jsx(ps,{className:"w-16 h-16 text-green-500 relative"})]}):e.jsxs("div",{className:"relative",children:[e.jsx(ze,{className:"w-16 h-16 animate-spin text-re-red"}),e.jsx("div",{className:"absolute inset-0 border-4 rounded-full border-re-red/20"}),e.jsx("div",{className:"absolute inset-0 border-4 border-transparent border-t-re-red rounded-full animate-spin"})]})}),e.jsxs("div",{className:"mb-6",children:[e.jsx("h2",{className:"text-xl font-semibold text-white mb-2",children:n==="complete"?"Welcome Back!":n==="error"?"Authentication Error":"Authenticating"}),e.jsx("p",{className:"text-slate-300 text-sm",children:d()})]}),n!=="error"&&e.jsxs("div",{className:"space-y-3 mb-6",children:[e.jsxs("div",{className:`flex items-center gap-3 text-sm transition-all duration-500 ${n==="exchanging"?"text-white":"text-slate-400"}`,children:[e.jsx("div",{className:`w-2 h-2 rounded-full transition-all duration-500 ${n==="exchanging"?"bg-re-red animate-pulse":"bg-slate-600"}`}),e.jsx("span",{children:"Validating credentials"})]}),e.jsxs("div",{className:`flex items-center gap-3 text-sm transition-all duration-500 ${n==="fetching"?"text-white":"text-slate-400"}`,children:[e.jsx("div",{className:`w-2 h-2 rounded-full transition-all duration-500 ${n==="fetching"?"bg-re-red animate-pulse":"bg-slate-600"}`}),e.jsx("span",{children:"Loading your profile"})]}),n==="complete"&&e.jsxs("div",{className:"flex items-center gap-3 text-sm transition-all duration-500 text-white",children:[e.jsx("div",{className:"w-2 h-2 rounded-full transition-all duration-500 bg-green-500"}),e.jsx("span",{children:"Setting up your session"})]})]}),n==="error"&&l&&e.jsxs("div",{className:"mt-6 p-4 bg-red-500/10 border border-red-500/20 rounded-lg",children:[e.jsx("p",{className:"text-red-400 text-sm",children:l}),e.jsx("button",{onClick:()=>{window.location.href="/"},className:"mt-4 text-sm text-red-400 hover:text-red-300 underline",children:"Return to login"})]}),n!=="error"&&n!=="complete"&&e.jsxs("div",{className:"mt-6",children:[e.jsx("div",{className:"h-1.5 bg-slate-700/50 rounded-full overflow-hidden",children:e.jsx("div",{className:"h-full bg-re-red rounded-full animate-pulse",style:{animation:"progress 2s ease-in-out infinite"}})}),e.jsx("style",{children:` - @keyframes progress { - 0%, 100% { width: 20%; } - 50% { width: 80%; } - } - `})]})]}),e.jsx("p",{className:"mt-6 text-slate-500 text-xs",children:n==="complete"?"Loading dashboard...":"Please wait while we secure your session"})]}),e.jsxs("div",{className:"absolute inset-0 overflow-hidden pointer-events-none",children:[e.jsx("div",{className:"absolute top-1/4 left-1/4 w-96 h-96 bg-re-red/5 rounded-full blur-3xl animate-pulse"}),e.jsx("div",{className:"absolute bottom-1/4 right-1/4 w-96 h-96 bg-re-red/5 rounded-full blur-3xl animate-pulse delay-1000"})]})]})}const _v=Dc("inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",{variants:{variant:{default:"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",secondary:"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",destructive:"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",outline:"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground"}},defaultVariants:{variant:"default"}});function re({className:t,variant:s,asChild:a=!1,...r}){const n=a?Ph:"span";return e.jsx(n,{"data-slot":"badge",className:Le(_v({variant:s}),t),...r})}function Mv({isOpen:t,onClose:s}){const{user:a,isAuthenticated:r,isLoading:n,error:i}=Ly();return x.useEffect(()=>{},[a,r,n,i]),t?e.jsx("div",{className:"fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4",children:e.jsxs(Z,{className:"w-full max-w-2xl max-h-[90vh] overflow-auto",children:[e.jsx(ce,{children:e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx(de,{children:"Authentication Debug Information"}),e.jsx(E,{variant:"ghost",size:"icon",onClick:s,children:e.jsx(yt,{className:"h-4 w-4"})})]})}),e.jsxs(J,{className:"space-y-4",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"font-semibold",children:"Status:"}),e.jsx(re,{variant:r?"default":"destructive",children:n?"Loading...":r?"Authenticated":"Not Authenticated"})]}),i&&e.jsxs("div",{className:"bg-red-50 border border-red-200 rounded p-3",children:[e.jsx("p",{className:"text-sm text-red-700 font-semibold",children:"Error:"}),e.jsx("p",{className:"text-sm text-red-600",children:i.message})]}),a&&e.jsxs("div",{className:"space-y-2",children:[e.jsx("h4",{className:"font-semibold",children:"User Information:"}),e.jsx("pre",{className:"bg-gray-50 p-3 rounded text-xs overflow-auto",children:JSON.stringify(a,null,2)})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx("h4",{className:"font-semibold",children:"All Auth Claims:"}),e.jsx("pre",{className:"bg-gray-50 p-3 rounded text-xs overflow-auto",children:JSON.stringify({isAuthenticated:r,isLoading:n,error:(i==null?void 0:i.message)||null,hasUser:!!a},null,2)})]})]})]})}):null}const cs=x.forwardRef(({className:t,...s},a)=>e.jsx(Eh,{ref:a,"data-slot":"avatar",className:Le("relative flex size-10 shrink-0 overflow-hidden rounded-full",t),...s}));cs.displayName=Eh.displayName;const Tl=x.forwardRef(({className:t,...s},a)=>e.jsx(_h,{ref:a,"data-slot":"avatar-image",className:Le("aspect-square size-full",t),...s}));Tl.displayName=_h.displayName;const ds=x.forwardRef(({className:t,...s},a)=>e.jsx(Mh,{ref:a,"data-slot":"avatar-fallback",className:Le("bg-muted flex size-full items-center justify-center rounded-full",t),...s}));ds.displayName=Mh.displayName;function ku({...t}){return e.jsx(qf,{"data-slot":"dropdown-menu",...t})}function Du({...t}){return e.jsx(eb,{"data-slot":"dropdown-menu-trigger",...t})}function Ru({className:t,sideOffset:s=4,...a}){return e.jsx(tb,{children:e.jsx(sb,{"data-slot":"dropdown-menu-content",sideOffset:s,className:Le("bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",t),...a})})}function Yl({className:t,inset:s,variant:a="default",...r}){return e.jsx(ab,{"data-slot":"dropdown-menu-item","data-inset":s,"data-variant":a,className:Le("focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",t),...r})}function Pp({...t}){return e.jsx(nb,{"data-slot":"alert-dialog",...t})}function Lv({...t}){return e.jsx(rb,{"data-slot":"alert-dialog-portal",...t})}const Ep=x.forwardRef(({className:t,...s},a)=>e.jsx(Lh,{"data-slot":"alert-dialog-overlay",className:Le("data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",t),...s,ref:a}));Ep.displayName=Lh.displayName;const Fc=x.forwardRef(({className:t,...s},a)=>e.jsxs(Lv,{children:[e.jsx(Ep,{}),e.jsx(Oh,{"data-slot":"alert-dialog-content",ref:a,className:Le("bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",t),...s})]}));Fc.displayName=Oh.displayName;function _p({className:t,...s}){return e.jsx("div",{"data-slot":"alert-dialog-header",className:Le("flex flex-col gap-2 text-center sm:text-left",t),...s})}function Mp({className:t,...s}){return e.jsx("div",{"data-slot":"alert-dialog-footer",className:Le("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",t),...s})}function Lp({className:t,...s}){return e.jsx(ib,{"data-slot":"alert-dialog-title",className:Le("text-lg font-semibold",t),...s})}function Op({className:t,...s}){return e.jsx(lb,{"data-slot":"alert-dialog-description",className:Le("text-muted-foreground text-sm",t),...s})}function $p({className:t,...s}){return e.jsx(cb,{className:Le(ei(),t),...s})}function Up({className:t,...s}){return e.jsx(ob,{className:Le(ei({variant:"outline"}),t),...s})}const cr={async list(t){return(await ve.get("/notifications",{params:t})).data},async getUnreadCount(){return(await ve.get("/notifications/unread-count")).data},async markAsRead(t){return(await ve.patch(`/notifications/${t}/read`)).data},async markAllAsRead(){return(await ve.post("/notifications/mark-all-read")).data},async delete(t){return(await ve.delete(`/notifications/${t}`)).data}},Ov=async()=>(await ve.get("/notifications/subscriptions")).data.data.subscriptions,Fu="https://reflow-uat.royalenfield.com/api/v1/".trim(),$v=typeof window<"u"&&/^(localhost|127\.0\.0\.1)$/i.test(window.location.hostname),Bp=(!$v&&/localhost:5000/i.test(Fu)?"":Fu.replace(/\/$/,""))||"/api/v1";async function Vp(t,s={}){const a=tt.getAccessToken(),r={...s.headers||{}};s.body instanceof FormData||(r["Content-Type"]="application/json"),a&&(r.Authorization=`Bearer ${a}`);const n=await fetch(`${Bp}${t}`,{...s,headers:r,credentials:"include"}),i=n.headers.get("content-type"),o=(i==null?void 0:i.includes("application/json"))?await n.json():{message:await n.text()||"Request failed"};if(!n.ok){const c=new Error(o.message||`Request failed ${n.status}`);throw c.response={status:n.status,data:o},c}return o}async function Ic(){var a;const t=await ve.get("/form16/permissions"),s=((a=t.data)==null?void 0:a.data)??t.data;return{canViewForm16Submission:!!(s!=null&&s.canViewForm16Submission),canView26AS:!!(s!=null&&s.canView26AS)}}async function Uv(){const{data:t}=await ve.get("/form16/26as/dashboard"),s=t&&typeof t=="object"&&"data"in t?t.data:t;if(!(s!=null&&s.overall)||!(s!=null&&s.kpi))throw new Error("Invalid Form16 dashboard response");return s}async function zp(t){const s=new URLSearchParams;t!=null&&t.financialYear&&s.set("financialYear",t.financialYear),t!=null&&t.quarter&&s.set("quarter",t.quarter);const a=s.toString(),r=a?`/form16/credit-notes?${a}`:"/form16/credit-notes",{data:n}=await ve.get(r),i=(n==null?void 0:n.data)??n;return{creditNotes:(i==null?void 0:i.creditNotes)??[],total:(i==null?void 0:i.total)??0,summary:i==null?void 0:i.summary}}async function Hp(t){const s=new URLSearchParams;t!=null&&t.financialYear&&s.set("financialYear",t.financialYear),t!=null&&t.quarter&&s.set("quarter",t.quarter);const a=s.toString(),r=a?`/form16/debit-notes?${a}`:"/form16/debit-notes",{data:n}=await ve.get(r),i=(n==null?void 0:n.data)??n;return{debitNotes:(i==null?void 0:i.debitNotes)??[],total:(i==null?void 0:i.total)??0,summary:i==null?void 0:i.summary}}async function Gp(t){const{data:s}=await ve.get(`/form16/debit-notes/${t}/sap-response`),a=s&&typeof s=="object"&&"data"in s?s.data:s;if(!(a!=null&&a.sapResponse))throw new Error("SAP response not available");return{...a,url:`/api/v1/form16/debit-notes/${t}/sap-response/csv`}}async function Pc(t){const{data:s}=await ve.get(`/form16/credit-notes/${t}/sap-response`),a=s&&typeof s=="object"&&"data"in s?s.data:s;if(!(a!=null&&a.sapResponse))throw new Error("SAP response not available");return{...a,url:`/api/v1/form16/credit-notes/${t}/sap-response/csv`}}async function Wp(t){const{data:s}=await ve.get(`/form16/requests/${encodeURIComponent(t)}/credit-note`),a=(s==null?void 0:s.data)??s;return(a==null?void 0:a.creditNote)??null}async function Bv(t){await ve.post(`/form16/requests/${encodeURIComponent(t)}/cancel-submission`)}async function Vv(t){await ve.post(`/form16/requests/${encodeURIComponent(t)}/resubmission-needed`)}async function Iu(t){const{data:s}=await ve.get(`/form16/credit-notes/${t}`),a=s&&typeof s=="object"&&"data"in s?s.data:s;if(!(a!=null&&a.creditNote))throw new Error("Credit note not found");return a}async function zv(t){const s=new FormData;s.append("document",t);const a=await Vp("/form16/extract",{method:"POST",body:s}),r=a,n=(r==null?void 0:r.data)??r,i=n==null?void 0:n.extractedData,l=n==null?void 0:n.ocrProvider;if(!i)throw new Error(a.message||"No extracted data returned");return{extractedData:i,ocrProvider:l}}async function Hv(t){const s=new FormData;s.append("document",t.file),s.append("financialYear",t.financialYear),s.append("quarter",t.quarter),s.append("form16aNumber",t.form16aNumber),s.append("tdsAmount",String(t.tdsAmount)),s.append("totalAmount",String(t.totalAmount)),s.append("tanNumber",t.tanNumber),s.append("deductorName",t.deductorName),t.version!=null&&s.append("version",String(t.version)),t.extractedData!=null&&s.append("ocrExtractedData",JSON.stringify(t.extractedData));const a=await Vp("/form16/submissions",{method:"POST",body:s}),n=a.data??a;if(!(n!=null&&n.requestNumber))throw new Error(a.message||"Invalid response from server");return n}async function Gv(t){const s=new URLSearchParams;t!=null&&t.financialYear&&s.set("financialYear",t.financialYear),t!=null&&t.quarter&&s.set("quarter",t.quarter),t!=null&&t.tanNumber&&s.set("tanNumber",t.tanNumber),t!=null&&t.search&&s.set("search",t.search),t!=null&&t.status&&s.set("status",t.status),t!=null&&t.assessmentYear&&s.set("assessmentYear",t.assessmentYear),t!=null&&t.sectionCode&&s.set("sectionCode",t.sectionCode),s.set("limit",String(t.limit)),(t==null?void 0:t.offset)!=null&&s.set("offset",String(t.offset));const a=s.toString(),r=a?`/form16/26as?${a}`:"/form16/26as",{data:n}=await ve.get(r),i=(n==null?void 0:n.data)??n;return{entries:(i==null?void 0:i.entries)??[],total:(i==null?void 0:i.total)??0,summary:(i==null?void 0:i.summary)??{totalRecords:0,booked:0,notBooked:0,pending:0,totalTaxDeducted:0}}}async function Wv(t){var l;const s=new URLSearchParams;s.set("limit",String(t.limit)),(t==null?void 0:t.offset)!=null&&s.set("offset",String(t.offset));const a=s.toString(),r=a?`/form16/26as/upload-history?${a}`:"/form16/26as/upload-history",{data:n}=await ve.get(r),i=(n==null?void 0:n.data)??n;return{history:(i==null?void 0:i.history)??[],total:typeof(i==null?void 0:i.total)=="number"?i.total:((l=i==null?void 0:i.history)==null?void 0:l.length)??0}}const Yv=5*60*1e3;function Qv(t,s){return new Promise((a,r)=>{const n=new FormData;n.append("file",t);const i=new XMLHttpRequest,l=`${Bp}/form16/26as/upload`,o=tt.getAccessToken(),c=setTimeout(()=>{i.abort(),r(new Error("Upload timed out. Try a smaller file or try again."))},Yv);i.upload.addEventListener("progress",d=>{if(d.lengthComputable&&d.total>0){const m=Math.min(100,Math.round(d.loaded/d.total*100));s==null||s(m)}else s==null||s(0)}),i.addEventListener("load",()=>{clearTimeout(c),s==null||s(100);const d=i.getResponseHeader("content-type"),m=d==null?void 0:d.includes("application/json"),p=i.responseText;let u;try{u=m?JSON.parse(p):{message:p||"Request failed"}}catch{u={message:p||"Invalid response"}}if(i.status>=200&&i.status<300){const h=u.data??u;a({imported:(h==null?void 0:h.imported)??0,errors:Array.isArray(h==null?void 0:h.errors)?h.errors:[]})}else r(new Error(u.message||`Request failed ${i.status}`))}),i.addEventListener("error",()=>{clearTimeout(c),r(new Error("Network error during upload"))}),i.addEventListener("abort",()=>{clearTimeout(c),r(new Error("Upload was cancelled or timed out"))}),i.open("POST",l),o&&i.setRequestHeader("Authorization",`Bearer ${o}`),i.withCredentials=!0,i.send(n)})}async function Kv(t){const s=new URLSearchParams;t&&s.set("financialYear",t);const a=s.toString(),r=a?`/form16/non-submitted-dealers?${a}`:"/form16/non-submitted-dealers",{data:n}=await ve.get(r),i=(n==null?void 0:n.data)??n;return{summary:(i==null?void 0:i.summary)??{totalDealers:0,nonSubmittedCount:0,neverSubmittedCount:0,overdue90Count:0},dealers:(i==null?void 0:i.dealers)??[]}}async function Xv(t){var l;const s=String(t.dealerCode??"").trim(),a=String(t.dealerId??"").trim(),r=String(t.email??"").trim();if(!s&&!a&&!r)throw new Error("Dealer identifier missing");const{data:n}=await ve.post("/form16/non-submitted-dealers/notify",{dealerCode:s||void 0,dealerId:a||void 0,email:r||void 0,financialYear:t.financialYear||void 0}),i=(l=n==null?void 0:n.data)==null?void 0:l.dealer;if(!i)throw new Error("No dealer returned");return i}async function Zv(t){const s=new URLSearchParams;t!=null&&t.status&&s.set("status",t.status),t!=null&&t.financialYear&&s.set("financialYear",t.financialYear),t!=null&&t.quarter&&s.set("quarter",t.quarter);const a=s.toString(),r=a?`/form16/dealer/submissions?${a}`:"/form16/dealer/submissions",{data:n}=await ve.get(r),i=(n==null?void 0:n.data)??n;return Array.isArray(i)?i:[]}async function Yp(){const{data:t}=await ve.get("/form16/dealer/pending-quarters"),s=(t==null?void 0:t.data)??t;return Array.isArray(s)?s:[]}function zt({children:t,currentPage:s="dashboard",onNavigate:a,onNewRequest:r,onLogout:n}){const[i,l]=x.useState(!1),[o,c]=x.useState(()=>(s==null?void 0:s.startsWith("form16"))??!1),[d,m]=x.useState(!1),[p,u]=x.useState([]),[h,g]=x.useState(0),[f,b]=x.useState(!1),[j,y]=x.useState(null),{user:N}=us(),S=x.useMemo(()=>{try{const L=tt.getUserData();return(L==null?void 0:L.jobTitle)==="Dealer"}catch(L){return console.error("[PageLayout] Error checking dealer status:",L),!1}},[]),A=(N==null?void 0:N.role)==="ADMIN",w=()=>{var L,D;try{if(N!=null&&N.displayName&&typeof N.displayName=="string"){const C=N.displayName.split(" ").filter(Boolean);return C.length>=2?`${((L=C[0])==null?void 0:L[0])||""}${((D=C[C.length-1])==null?void 0:D[0])||""}`.toUpperCase():N.displayName.substring(0,2).toUpperCase()}return N!=null&&N.email&&typeof N.email=="string"?N.email.substring(0,2).toUpperCase():"U"}catch(C){return console.error("[PageLayout] Error getting user initials:",C),"U"}},I=x.useMemo(()=>{const L=[{id:"dashboard",label:"Dashboard",icon:hj},{id:"requests",label:"All Requests",icon:Jh,adminOnly:!1}];return S||L.push({id:"my-requests",label:"My Requests",icon:Ht}),L.push({id:"open-requests",label:"Open Requests",icon:Fe},{id:"closed-requests",label:"Closed Requests",icon:Be},{id:"shared-summaries",label:"Shared Summary",icon:bn}),L},[S,A]);x.useEffect(()=>{if(!(N!=null&&N.userId)){y(null);return}let L=!0;return Ic().then(D=>{L&&y(D)}).catch(D=>{L&&(y({canViewForm16Submission:!1,canView26AS:!1}),console.warn("[PageLayout] Form 16 permissions could not be loaded – Form 16 menu will be hidden.",D))}),()=>{L=!1}},[N==null?void 0:N.userId]);const k=!!j&&(S?!!j.canViewForm16Submission:!!j.canView26AS),T=!!(j!=null&&j.canViewForm16Submission),v=!!(j!=null&&j.canView26AS),_=s==="form16-credit-notes"||s==="form16-debit-notes"||s==="form16-transactions"||s==="form16-submit"||s==="form16-pending-submissions"||s==="form16-dashboard"||s==="form16-26as"||s==="form16-non-submitted-dealers",P=o||_,B=()=>{l(!i)},R=async L=>{var D;try{if(L.isRead||(await cr.markAsRead(L.notificationId),u(C=>C.map(G=>G.notificationId===L.notificationId?{...G,isRead:!0}:G)),g(C=>Math.max(0,C-1))),L.actionUrl&&a){const C=(D=L.metadata)==null?void 0:D.requestNumber;if(C){let G=`request/${C}`;(L.notificationType==="mention"||L.notificationType==="comment"||L.notificationType==="worknote")&&(G+="?tab=worknotes"),a(G)}}b(!1)}catch(C){console.error("[PageLayout] Error handling notification click:",C)}},M=async()=>{try{await cr.markAllAsRead(),u(L=>L.map(D=>({...D,isRead:!0}))),g(0)}catch(L){console.error("[PageLayout] Error marking all as read:",L)}};return x.useEffect(()=>{const L=N==null?void 0:N.userId;if(!L)return;let D=!0;(async()=>{var U,$;try{const F=await cr.list({page:1,limit:4,unreadOnly:!1});if(!D)return;const H=((U=F.data)==null?void 0:U.notifications)||[];u(H),g((($=F.data)==null?void 0:$.unreadCount)||0)}catch(F){console.error("[PageLayout] Failed to fetch notifications:",F)}})();const G=Vr();if(G){kc(G,L);const U=$=>{D&&(u(F=>[$.notification,...F].slice(0,4)),g(F=>F+1))};return G.on("notification:new",U),()=>{D=!1,G.off("notification:new",U)}}return()=>{D=!1}},[N]),x.useEffect(()=>{const L=()=>{window.innerWidth>=768?l(!0):l(!1)};return L(),window.addEventListener("resize",L),()=>window.removeEventListener("resize",L)},[]),e.jsxs("div",{className:"min-h-screen flex w-full bg-background",children:[i&&e.jsx("div",{className:"fixed inset-0 bg-black/50 z-40 md:hidden",onClick:()=>l(!1)}),e.jsx("aside",{className:` - fixed md:relative - inset-y-0 left-0 - w-64 - transform transition-transform duration-300 ease-in-out - ${i?"translate-x-0":"-translate-x-full"} - md:translate-x-0 - ${i?"md:w-64":"md:w-0"} - z-50 md:z-auto - flex-shrink-0 - border-r border-gray-800 bg-black - flex flex-col - overflow-hidden - `,children:e.jsxs("div",{className:`w-64 h-full flex flex-col overflow-hidden ${i?"":"md:hidden"}`,children:[e.jsx("div",{className:"p-4 border-b border-gray-800 flex-shrink-0",children:e.jsxs("div",{className:"flex flex-col items-center justify-center",children:[e.jsx("img",{src:Al,alt:"Royal Enfield Logo",className:"h-10 w-auto max-w-[168px] object-contain"}),e.jsx("p",{className:"text-xs text-gray-400 text-center mt-1 truncate",children:"RE Flow"})]})}),e.jsxs("div",{className:"p-3 flex-1 overflow-y-auto",children:[e.jsxs("div",{className:"space-y-2",children:[I.filter(L=>!L.adminOnly||(N==null?void 0:N.role)==="ADMIN").map(L=>e.jsxs("button",{onClick:()=>{a==null||a(L.id),window.innerWidth<768&&l(!1)},className:`w-full flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition-colors ${s===L.id?"bg-re-green text-white font-medium":"text-gray-300 hover:bg-gray-900 hover:text-white"}`,children:[e.jsx(L.icon,{className:"w-4 h-4 shrink-0"}),e.jsx("span",{className:"truncate",children:L.label})]},L.id)),k&&e.jsxs("div",{className:"pt-2 border-t border-gray-800",children:[e.jsxs("button",{type:"button",onClick:()=>c(!P),className:`w-full flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition-colors ${_?"bg-re-green text-white font-medium":"text-gray-300 hover:bg-gray-900 hover:text-white"}`,children:[e.jsx(Mt,{className:"w-4 h-4 shrink-0"}),e.jsx("span",{className:"truncate flex-1 text-left",children:"Form 16"}),P?e.jsx(jn,{className:"w-4 h-4 shrink-0"}):e.jsx(Ka,{className:"w-4 h-4 shrink-0"})]}),P&&e.jsx("div",{className:"mt-1 ml-4 pl-2 border-l border-gray-700 space-y-0.5",children:S?e.jsx(e.Fragment,{children:T&&e.jsxs(e.Fragment,{children:[e.jsxs("button",{type:"button",onClick:()=>{a==null||a("/form16/submit"),window.innerWidth<768&&l(!1)},className:`w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-sm transition-colors ${s==="form16-submit"?"bg-re-green/80 text-white font-medium":"text-gray-400 hover:bg-gray-800 hover:text-white"}`,children:[e.jsx(Fe,{className:"w-3.5 h-3.5 shrink-0"}),e.jsx("span",{className:"truncate",children:"Submit Form 16"})]}),e.jsxs("button",{type:"button",onClick:()=>{a==null||a("/form16/pending-submissions"),window.innerWidth<768&&l(!1)},className:`w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-sm transition-colors ${s==="form16-pending-submissions"?"bg-re-green/80 text-white font-medium":"text-gray-400 hover:bg-gray-800 hover:text-white"}`,children:[e.jsx(Fe,{className:"w-3.5 h-3.5 shrink-0"}),e.jsx("span",{className:"truncate",children:"Pending Submissions"})]}),e.jsxs("button",{type:"button",onClick:()=>{a==null||a("/form16/credit-notes"),window.innerWidth<768&&l(!1)},className:`w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-sm transition-colors ${s==="form16-credit-notes"?"bg-re-green/80 text-white font-medium":"text-gray-400 hover:bg-gray-800 hover:text-white"}`,children:[e.jsx(Mt,{className:"w-3.5 h-3.5 shrink-0"}),e.jsx("span",{className:"truncate",children:"Credit Notes"})]})]})}):e.jsxs(e.Fragment,{children:[v&&e.jsxs("button",{type:"button",onClick:()=>{a==null||a("/form16/dashboard"),window.innerWidth<768&&l(!1)},className:`w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-sm transition-colors ${s==="form16-dashboard"?"bg-re-green/80 text-white font-medium":"text-gray-400 hover:bg-gray-800 hover:text-white"}`,children:[e.jsx(Fe,{className:"w-3.5 h-3.5 shrink-0"}),e.jsx("span",{className:"truncate",children:"Form16A Dashboard"})]}),v&&e.jsxs("button",{type:"button",onClick:()=>{a==null||a("/form16/26as"),window.innerWidth<768&&l(!1)},className:`w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-sm transition-colors ${s==="form16-26as"?"bg-re-green/80 text-white font-medium":"text-gray-400 hover:bg-gray-800 hover:text-white"}`,children:[e.jsx(Fe,{className:"w-3.5 h-3.5 shrink-0"}),e.jsx("span",{className:"truncate",children:"26AS Management"})]}),T&&e.jsxs(e.Fragment,{children:[e.jsxs("button",{type:"button",onClick:()=>{a==null||a("/form16/non-submitted-dealers"),window.innerWidth<768&&l(!1)},className:`w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-sm transition-colors ${s==="form16-non-submitted-dealers"?"bg-re-green/80 text-white font-medium":"text-gray-400 hover:bg-gray-800 hover:text-white"}`,children:[e.jsx(Mt,{className:"w-3.5 h-3.5 shrink-0"}),e.jsx("span",{className:"truncate",children:"Non-submitted Dealers"})]}),e.jsxs("button",{type:"button",onClick:()=>{a==null||a("/form16/transactions"),window.innerWidth<768&&l(!1)},className:`w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-sm transition-colors ${s==="form16-transactions"?"bg-re-green/80 text-white font-medium":"text-gray-400 hover:bg-gray-800 hover:text-white"}`,children:[e.jsx(Mt,{className:"w-3.5 h-3.5 shrink-0"}),e.jsx("span",{className:"truncate",children:"Transactions"})]})]})]})})]})]}),!S&&e.jsx("div",{className:"mt-6 pt-6 border-t border-gray-800 px-3",children:e.jsxs(E,{onClick:r,className:"w-full bg-re-green hover:bg-re-green/90 text-white text-sm font-medium",size:"sm",children:[e.jsx(ms,{className:"w-4 h-4 mr-2"}),"Raise New Request"]})})]})]})}),e.jsxs("div",{className:"flex-1 flex flex-col min-w-0",children:[e.jsxs("header",{className:"h-16 border-b border-gray-200 bg-white flex items-center justify-between px-6 shrink-0",children:[e.jsx("div",{className:"flex items-center gap-4 min-w-0 flex-1",children:e.jsx(E,{variant:"ghost",size:"icon",onClick:B,className:"shrink-0 h-10 w-10 sidebar-toggle",children:i?e.jsx(pj,{className:"w-5 h-5 text-gray-600"}):e.jsx(gj,{className:"w-5 h-5 text-gray-600"})})}),e.jsxs("div",{className:"flex items-center gap-4 shrink-0",children:[!S&&e.jsxs(E,{onClick:r,className:"bg-re-green hover:bg-re-green/90 text-white gap-2 hidden md:flex text-sm",size:"sm",children:[e.jsx(ms,{className:"w-4 h-4"}),"New Request"]}),e.jsxs(ku,{open:f,onOpenChange:b,children:[e.jsx(Du,{asChild:!0,children:e.jsxs(E,{variant:"ghost",size:"icon",className:"relative shrink-0 h-10 w-10",children:[e.jsx(_s,{className:"w-5 h-5"}),h>0&&e.jsx(re,{className:"absolute -top-1 -right-1 w-5 h-5 rounded-full bg-destructive text-destructive-foreground text-xs flex items-center justify-center p-0",children:h>9?"9+":h})]})}),e.jsxs(Ru,{align:"end",className:"w-96 max-h-[500px]",children:[e.jsxs("div",{className:"p-3 border-b flex items-center justify-between sticky top-0 bg-white z-10",children:[e.jsx("h4",{className:"font-semibold text-base",children:"Notifications"}),h>0&&e.jsx(E,{variant:"ghost",size:"sm",className:"text-xs text-blue-600 hover:text-blue-700 h-auto p-1",onClick:L=>{L.stopPropagation(),M()},children:"Mark all as read"})]}),e.jsx("div",{className:"max-h-[400px] overflow-y-auto",children:p.length===0?e.jsxs("div",{className:"p-6 text-center",children:[e.jsx(_s,{className:"w-12 h-12 text-gray-300 mx-auto mb-2"}),e.jsx("p",{className:"text-sm text-gray-500",children:"No notifications yet"})]}):e.jsx("div",{className:"divide-y",children:p.map(L=>e.jsx("div",{className:`p-3 hover:bg-gray-50 cursor-pointer transition-colors ${L.isRead?"":"bg-blue-50"}`,onClick:()=>R(L),children:e.jsxs("div",{className:"flex gap-2",children:[!L.isRead&&e.jsx("div",{className:"w-2 h-2 rounded-full bg-blue-600 mt-1.5 shrink-0"}),e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx("p",{className:`text-sm ${L.isRead?"font-medium":"font-semibold"}`,children:L.title}),e.jsx("p",{className:"text-xs text-muted-foreground mt-0.5 line-clamp-2",children:L.message}),e.jsx("p",{className:"text-xs text-gray-400 mt-1",children:Yh(new Date(L.createdAt),{addSuffix:!0})})]})]})},L.notificationId))})}),p.length>0&&e.jsx("div",{className:"p-2 border-t",children:e.jsx(E,{variant:"ghost",className:"w-full text-sm text-blue-600 hover:text-blue-700",onClick:()=>{b(!1),a==null||a("notifications")},children:"View all notifications"})})]})]}),e.jsxs(ku,{children:[e.jsx(Du,{asChild:!0,children:e.jsxs(cs,{className:"cursor-pointer shrink-0 h-10 w-10",children:[e.jsx(Tl,{src:(N==null?void 0:N.picture)||""}),e.jsx(ds,{className:"bg-re-green text-white text-sm",children:w()})]})}),e.jsxs(Ru,{align:"end",children:[e.jsxs(Yl,{onClick:()=>a==null?void 0:a("profile"),children:[e.jsx(Ht,{className:"w-4 h-4 mr-2"}),"Profile"]}),e.jsxs(Yl,{onClick:()=>a==null?void 0:a("settings"),children:[e.jsx(dr,{className:"w-4 h-4 mr-2"}),"Settings"]}),e.jsxs(Yl,{onClick:()=>m(!0),className:"text-red-600 focus:text-red-600",children:[e.jsx(Bl,{className:"w-4 h-4 mr-2"}),"Logout"]})]})]})]})]}),e.jsx("main",{className:"flex-1 p-2 sm:p-4 lg:p-6 overflow-auto min-w-0",children:t})]}),e.jsx(Pp,{open:d,onOpenChange:m,children:e.jsxs(Fc,{children:[e.jsxs(_p,{children:[e.jsxs(Lp,{className:"flex items-center gap-2",children:[e.jsx(Bl,{className:"w-5 h-5 text-red-600"}),"Confirm Logout"]}),e.jsx(Op,{className:"pt-2",children:"Are you sure you want to logout? You will need to sign in again to access your account."})]}),e.jsxs(Mp,{children:[e.jsx(Up,{onClick:()=>m(!1),children:"Cancel"}),e.jsxs($p,{onClick:async()=>{if(m(!1),n)try{await n()}catch(L){console.error("🔴 Error calling onLogout:",L)}else console.error("🔴 ERROR: onLogout is undefined!")},className:"bg-red-600 hover:bg-red-700 text-white focus:ring-red-600",children:[e.jsx(Bl,{className:"w-4 h-4 mr-2"}),"Logout"]})]})]})})]})}const di=()=>ky(),Nn=Fy,Jv=()=>{try{const t=localStorage.getItem("dashboard_viewAsUser");return t?JSON.parse(t):!1}catch{return!1}},qv={viewAsUser:Jv()},Qp=yn({name:"dashboard",initialState:qv,reducers:{setViewAsUser:(t,s)=>{t.viewAsUser=s.payload,localStorage.setItem("dashboard_viewAsUser",JSON.stringify(s.payload))}}}),{setViewAsUser:eN}=Qp.actions;function tN(){const[t,s]=x.useState("all"),[a,r]=x.useState(void 0),[n,i]=x.useState(void 0),[l,o]=x.useState(!1),c=x.useCallback(p=>{const u=p;s(u),u!=="custom"?(r(void 0),i(void 0),o(!1)):o(!0)},[]),d=x.useCallback(p=>{if(a&&n){if(a>n){const u=a;r(n),i(u),p(n,u)}else p(a,n);o(!1)}},[a,n]),m=x.useCallback(()=>{r(void 0),i(void 0),o(!1),s("all")},[]);return{dateRange:t,customStartDate:a,customEndDate:n,showCustomDatePicker:l,setDateRange:s,setCustomStartDate:r,setCustomEndDate:i,setShowCustomDatePicker:o,handleDateRangeChange:c,handleApplyCustomDate:d,resetCustomDates:m}}function sN(){const[t,s]=x.useState({page:1,totalPages:1,totalRecords:0}),[a,r]=x.useState({page:1,totalPages:1,totalRecords:0}),[n,i]=x.useState({page:1,totalPages:1,totalRecords:0}),[l,o]=x.useState({page:1,totalPages:1,totalRecords:0}),c=x.useCallback((b,j,y)=>{s({page:b,totalPages:j,totalRecords:y})},[]),d=x.useCallback((b,j,y)=>{r({page:b,totalPages:j,totalRecords:y})},[]),m=x.useCallback((b,j,y)=>{i({page:b,totalPages:j,totalRecords:y})},[]),p=x.useCallback((b,j,y)=>{o({page:b,totalPages:j,totalRecords:y})},[]),u=x.useCallback((b,j)=>{b>=1&&b<=t.totalPages&&(s(y=>({...y,page:b})),j(b))},[t.totalPages]),h=x.useCallback((b,j)=>{b>=1&&b<=a.totalPages&&(r(y=>({...y,page:b})),j(b))},[a.totalPages]),g=x.useCallback((b,j)=>{b>=1&&b<=n.totalPages&&(i(y=>({...y,page:b})),j(b))},[n.totalPages]),f=x.useCallback((b,j)=>{b>=1&&b<=l.totalPages&&(o(y=>({...y,page:b})),j(b))},[l.totalPages]);return{activity:t,critical:a,deadlines:n,approver:l,updateActivityPagination:c,updateCriticalPagination:d,updateDeadlinesPagination:m,updateApproverPagination:p,handleActivityPageChange:u,handleCriticalPageChange:h,handleDeadlinesPageChange:g,handleApproverPageChange:f}}class aN{async getKPIs(s,a,r,n){try{const i={dateRange:s};return s==="custom"&&a&&r&&(i.startDate=a.toISOString(),i.endDate=r.toISOString()),n&&(i.viewAsUser="true"),(await ve.get("/dashboard/kpis",{params:i})).data.data}catch(i){throw console.error("Failed to fetch KPIs:",i),i}}async getRequestStats(s,a,r,n,i,l,o,c,d,m,p,u,h){try{const g={dateRange:s};return s==="custom"&&a&&r&&(g.startDate=a,g.endDate=r),n&&n!=="all"&&(g.status=n),i&&i!=="all"&&(g.priority=i),l&&l!=="all"&&(g.templateType=l),o&&o!=="all"&&(g.department=o),c&&c!=="all"&&(g.initiator=c),d&&d!=="all"&&(g.approver=d),m&&(g.approverType=m),p&&(g.search=p),u&&u!=="all"&&(g.slaCompliance=u),h&&(g.viewAsUser="true"),(await ve.get("/dashboard/stats/requests",{params:g})).data.data}catch(g){throw console.error("Failed to fetch request stats:",g),g}}async getTATEfficiency(s){try{return(await ve.get("/dashboard/stats/tat-efficiency",{params:{dateRange:s}})).data.data}catch(a){throw console.error("Failed to fetch TAT efficiency:",a),a}}async getApproverLoad(s){try{return(await ve.get("/dashboard/stats/approver-load",{params:{dateRange:s}})).data.data}catch(a){throw console.error("Failed to fetch approver load:",a),a}}async getEngagementStats(s){try{return(await ve.get("/dashboard/stats/engagement",{params:{dateRange:s}})).data.data}catch(a){throw console.error("Failed to fetch engagement stats:",a),a}}async getAIInsights(s){try{return(await ve.get("/dashboard/stats/ai-insights",{params:{dateRange:s}})).data.data}catch(a){throw console.error("Failed to fetch AI insights:",a),a}}async getRecentActivity(s=1,a=10,r){try{const n={page:s,limit:a};r&&(n.viewAsUser="true");const i=await ve.get("/dashboard/activity/recent",{params:n});return{activities:i.data.data,pagination:i.data.pagination}}catch(n){throw console.error("Failed to fetch recent activity:",n),n}}async getCriticalRequests(s=1,a=10,r){try{const n={page:s,limit:a};r&&(n.viewAsUser="true");const i=await ve.get("/dashboard/requests/critical",{params:n});return{criticalRequests:i.data.data,pagination:i.data.pagination}}catch(n){throw console.error("Failed to fetch critical requests:",n),n}}async getUpcomingDeadlines(s=1,a=10,r){try{const n={page:s,limit:a};r&&(n.viewAsUser="true");const i=await ve.get("/dashboard/deadlines/upcoming",{params:n});return{deadlines:i.data.data,pagination:i.data.pagination}}catch(n){throw console.error("Failed to fetch upcoming deadlines:",n),n}}async getDepartmentStats(s,a,r){try{const n={dateRange:s};return s==="custom"&&a&&r&&(n.startDate=a.toISOString(),n.endDate=r.toISOString()),(await ve.get("/dashboard/stats/by-department",{params:n})).data.data}catch(n){throw console.error("Failed to fetch department stats:",n),n}}async getPriorityDistribution(s,a,r){try{const n={dateRange:s};return s==="custom"&&a&&r&&(n.startDate=a.toISOString(),n.endDate=r.toISOString()),(await ve.get("/dashboard/stats/priority-distribution",{params:n})).data.data}catch(n){throw console.error("Failed to fetch priority distribution:",n),n}}async getAIRemarkUtilization(s,a,r){try{const n={dateRange:s};return s==="custom"&&a&&r&&(n.startDate=a.toISOString(),n.endDate=r.toISOString()),(await ve.get("/dashboard/stats/ai-remark-utilization",{params:n})).data.data}catch(n){throw console.error("Failed to fetch AI remark utilization:",n),n}}async getApproverPerformance(s,a=1,r=10,n,i,l,o){try{const c={dateRange:s,page:a,limit:r||10};s==="custom"&&n&&i&&(c.startDate=n.toISOString(),c.endDate=i.toISOString()),l&&l!=="all"&&(c.priority=l),o&&o!=="all"&&(c.slaCompliance=o);const d=await ve.get("/dashboard/stats/approver-performance",{params:c});return{performance:d.data.data,pagination:d.data.pagination}}catch(c){throw console.error("Failed to fetch approver performance:",c),c}}async getLifecycleReport(s=1,a=50,r,n,i){try{const l={page:s,limit:a};r&&(l.dateRange=r),r==="custom"&&n&&i&&(l.startDate=n.toISOString(),l.endDate=i.toISOString());const o=await ve.get("/dashboard/reports/lifecycle",{params:l});return{lifecycleData:o.data.data,pagination:o.data.pagination}}catch(l){throw console.error("Failed to fetch lifecycle report:",l),l}}async getActivityLogReport(s=1,a=50,r,n,i,l,o,c,d){try{const m={page:s,limit:a,filterUserId:n,filterType:i,filterCategory:l,filterSeverity:o};r&&(m.dateRange=r),r==="custom"&&c&&d&&(m.startDate=c.toISOString(),m.endDate=d.toISOString());const p=await ve.get("/dashboard/reports/activity-log",{params:m});return{activities:p.data.data,pagination:p.data.pagination}}catch(m){throw console.error("Failed to fetch activity log report:",m),m}}async getDepartments(){try{return(await ve.get("/dashboard/metadata/departments")).data.data.departments||[]}catch(s){throw console.error("Failed to fetch departments:",s),s}}async getWorkflowAgingReport(s=7,a=1,r=50,n,i,l){try{const o={threshold:s,page:a,limit:r};n&&(o.dateRange=n),n==="custom"&&i&&l&&(o.startDate=i.toISOString(),o.endDate=l.toISOString());const c=await ve.get("/dashboard/reports/workflow-aging",{params:o});return{agingData:c.data.data,pagination:c.data.pagination}}catch(o){throw console.error("Failed to fetch workflow aging report:",o),o}}async getSingleApproverStats(s,a,r,n,i,l){try{const o={approverId:s};return a&&(o.dateRange=a),a==="custom"&&r&&n&&(o.startDate=r.toISOString(),o.endDate=n.toISOString()),i&&i!=="all"&&(o.priority=i),l&&l!=="all"&&(o.slaCompliance=l),(await ve.get("/dashboard/stats/single-approver",{params:o})).data.data}catch(o){throw console.error("Failed to fetch single approver stats:",o),o}}async getRequestsByApprover(s,a=1,r=50,n,i,l,o,c,d,m){try{const p={approverId:s,page:a,limit:r};n&&(p.dateRange=n),n==="custom"&&i&&l&&(p.startDate=i.toISOString(),p.endDate=l.toISOString()),o&&(p.status=o),c&&(p.priority=c),d&&(p.slaCompliance=d),m&&(p.search=m);const u=await ve.get("/dashboard/requests/by-approver",{params:p});return{requests:u.data.data,pagination:u.data.pagination}}catch(p){throw console.error("Failed to fetch requests by approver:",p),p}}}const Ot=new aN;function rN({isAdmin:t,viewAsUser:s=!1,userId:a,dateRange:r,customStartDate:n,customEndDate:i,onPaginationUpdate:l}){const[o,c]=x.useState(null),[d,m]=x.useState([]),[p,u]=x.useState([]),[h,g]=x.useState([]),[f,b]=x.useState([]),[j,y]=x.useState([]),[N,S]=x.useState(null),[A,w]=x.useState([]),[I,k]=x.useState(!0),[T,v]=x.useState(!1),_=x.useRef(l);_.current=l;const P=x.useCallback(async(D=!1)=>{try{D?v(!0):k(!0);const C=[Ot.getKPIs(r,n,i,s),Ot.getRecentActivity(1,10,s),Ot.getCriticalRequests(1,10,s),Ot.getUpcomingDeadlines(1,10,s)],G=!t&&a?Ot.getRequestStats(r,n==null?void 0:n.toISOString(),i==null?void 0:i.toISOString(),void 0,void 0,void 0,void 0,a,void 0,void 0,void 0,void 0,s):null,U=t?[Ot.getDepartmentStats(r,n,i),Ot.getPriorityDistribution(r,n,i),Ot.getAIRemarkUtilization(r,n,i),Ot.getApproverPerformance(r,1,10,n,i)]:[],[$,F,H]=await Promise.all([Promise.all(C),G,Promise.all(U)]),K=$[0],z=$[1],O=$[2],V=$[3];if(!t&&F&&(K.requestVolume=F),c(K),m(z.activities),_.current.activity(z.pagination.currentPage,z.pagination.totalPages,z.pagination.totalRecords),u(O.criticalRequests),_.current.critical(O.pagination.currentPage,O.pagination.totalPages,O.pagination.totalRecords),y(V.deadlines),_.current.deadlines(V.pagination.currentPage,V.pagination.totalPages,V.pagination.totalRecords),t&&H.length>=4){const Q=H[0],q=H[1],ee=H[2],te=H[3];g(Q),b(q),S(ee),w(te.performance),_.current.approver(te.pagination.currentPage,te.pagination.totalPages,te.pagination.totalRecords)}else t||(g([]),b([]),S(null),w([]))}catch(C){console.error("Failed to fetch dashboard data:",C)}finally{k(!1),v(!1)}},[t,s,a,r,n,i]),B=x.useCallback(async(D=1)=>{try{const C=await Ot.getRecentActivity(D,10,s);m(C.activities),_.current.activity(C.pagination.currentPage,C.pagination.totalPages,C.pagination.totalRecords)}catch(C){console.error("Failed to fetch recent activities:",C)}},[s]),R=x.useCallback(async(D=1)=>{try{const C=await Ot.getCriticalRequests(D,10,s);u(C.criticalRequests),_.current.critical(C.pagination.currentPage,C.pagination.totalPages,C.pagination.totalRecords)}catch(C){console.error("Failed to fetch critical requests:",C)}},[s]),M=x.useCallback(async(D=1)=>{try{const C=await Ot.getUpcomingDeadlines(D,10,s);y(C.deadlines),_.current.deadlines(C.pagination.currentPage,C.pagination.totalPages,C.pagination.totalRecords)}catch(C){console.error("Failed to fetch upcoming deadlines:",C)}},[s]),L=x.useCallback(async(D=1)=>{try{const C=await Ot.getApproverPerformance(r,D,10,n,i);w(C.performance),_.current.approver(C.pagination.currentPage,C.pagination.totalPages,C.pagination.totalRecords)}catch(C){console.error("Failed to fetch approver performance:",C)}},[r,n,i]);return{kpis:o,recentActivity:d,criticalRequests:p,departmentStats:h,priorityDistribution:f,upcomingDeadlines:j,aiRemarkUtilization:N,approverPerformance:A,loading:I,refreshing:T,fetchDashboardData:P,fetchRecentActivities:B,fetchCriticalRequests:R,fetchUpcomingDeadlines:M,fetchApproverPerformance:L}}const Wa={appName:"Royal Enfield Workflow Management",appVersion:"1.2.0",workingHours:{START_HOUR:9,END_HOUR:18,START_DAY:1,END_DAY:5,TIMEZONE:"Asia/Kolkata"},tat:{thresholds:{warning:50,critical:75,breach:100},testMode:!1},upload:{maxFileSizeMB:10,allowedFileTypes:["pdf","doc","docx","xls","xlsx","ppt","pptx","jpg","jpeg","png","gif"],maxFilesPerRequest:10},workflow:{maxApprovalLevels:10,maxParticipants:50,maxSpectators:20},workNotes:{maxMessageLength:2e3,maxAttachmentsPerNote:5,enableReactions:!0,enableMentions:!0},features:{ENABLE_AI_CONCLUSION:!0,ENABLE_TEMPLATES:!1,ENABLE_ANALYTICS:!0,ENABLE_EXPORT:!0},ui:{DEFAULT_THEME:"light",DEFAULT_LANGUAGE:"en",DATE_FORMAT:"DD/MM/YYYY",TIME_FORMAT:"12h",CURRENCY:"INR",CURRENCY_SYMBOL:"₹"}};class nN{constructor(){Ll(this,"config",null);Ll(this,"loading",null)}async getConfig(){return this.config?this.config:this.loading?this.loading:(this.loading=this.fetchConfig(),this.config=await this.loading,this.loading=null,this.config)}async fetchConfig(){var s;try{const a=await ve.get("/config"),r=((s=a.data)==null?void 0:s.data)||a.data;return{...Wa,...r,workingHours:{...Wa.workingHours,...r.workingHours},tat:{...Wa.tat,...r.tat},upload:{...Wa.upload,...r.upload},workflow:{...Wa.workflow,...r.workflow},workNotes:{...Wa.workNotes,...r.workNotes},features:{...Wa.features,...r.features},ui:{...Wa.ui,...r.ui}}}catch(a){return console.error("[ConfigService] ⚠️ Failed to fetch config from server, using defaults:",a),Wa}}async refreshConfig(){return this.config=null,this.loading=null,this.getConfig()}getCachedConfig(){return this.config}isLoaded(){return this.config!==null}}const iN=new nN;let lN=9,oN=18,cN=1,dN=5,Pu=!1;async function mN(){if(!Pu)try{const t=await iN.getConfig();lN=t.workingHours.START_HOUR,oN=t.workingHours.END_HOUR,cN=t.workingHours.START_DAY,dN=t.workingHours.END_DAY,Pu=!0}catch{console.warn("[SLA Tracker] ⚠️ Using default working hours (9 AM - 6 PM)")}}mN().catch(()=>{});function Xt(t){if(t==null||t<0||t===0)return"0 hours";const s=8;if(t<1){const o=Math.round(t*60);return o>0?`${o}m`:"0 hours"}const a=Math.floor(t/s),r=Math.floor(t%s),n=Math.round(t%1*60);if(a>0){const o=a===1?"day":"days",c=r===1?"hour":"hours",d=n===1?"min":"m";return n>0?`${a} ${o} ${r} ${c} ${n}${d}`:`${a} ${o} ${r} ${c}`}const i=r===1?"hour":"hours",l=n===1?"min":"m";return n>0?`${r} ${i} ${n}${l}`:`${r} ${i}`}async function uN(t,s,a){const r=[];let n=1,i=!0;const l=100;for(;i&&n<=l;){const u=await Ot.getApproverPerformance(t,n,100,s,a);u.performance&&u.performance.length>0?(r.push(...u.performance),n++,i=n<=u.pagination.totalPages):i=!1}const o=[["Approver Name","Total Approved","TAT Compliance (%)","Avg Response Time","Pending Count"].join(",")];r.forEach(u=>{const h=[`"${(u.approverName||"Unknown").replace(/"/g,'""')}"`,u.totalApproved||0,u.tatCompliancePercent||0,Xt(u.avgResponseHours),u.pendingCount||0];o.push(h.join(","))});const c=o.join(` -`),d=new Blob([c],{type:"text/csv;charset=utf-8;"}),m=document.createElement("a"),p=URL.createObjectURL(d);m.setAttribute("href",p),m.setAttribute("download",`approver-performance-report-${new Date().toISOString().split("T")[0]}.csv`),m.style.visibility="hidden",document.body.appendChild(m),m.click(),document.body.removeChild(m),URL.revokeObjectURL(p)}async function xN(t,s,a){const r=await Ot.getDepartmentStats(t,s,a),n=[["Department","Total Requests","Approved","Rejected","In Progress","Approval Rate (%)"].join(",")];r.forEach(d=>{const m=[`"${(d.department||"Unknown").replace(/"/g,'""')}"`,d.totalRequests||0,d.approved||0,d.rejected||0,d.inProgress||0,d.approvalRate||0];n.push(m.join(","))});const i=n.join(` -`),l=new Blob([i],{type:"text/csv;charset=utf-8;"}),o=document.createElement("a"),c=URL.createObjectURL(l);o.setAttribute("href",c),o.setAttribute("download",`department-workflow-summary-${new Date().toISOString().split("T")[0]}.csv`),o.style.visibility="hidden",document.body.appendChild(o),o.click(),document.body.removeChild(o),URL.revokeObjectURL(c)}function hN(){const[t,s]=x.useState(!1),[a,r]=x.useState(!1),n=x.useCallback(async(l,o,c)=>{try{s(!0),await xN(l,o,c)}catch(d){console.error("Failed to export department stats:",d),alert("Failed to export department statistics. Please try again.")}finally{s(!1)}},[]),i=x.useCallback(async(l,o,c)=>{try{r(!0),await uN(l,o,c)}catch(d){console.error("Failed to export approver performance:",d),alert("Failed to export approver performance data. Please try again.")}finally{r(!1)}},[]);return{exportingDeptStats:t,exportingApproverPerformance:a,handleExportDepartmentStats:n,handleExportApproverPerformance:i}}function os({className:t,...s}){return e.jsx(db,{"data-slot":"switch",className:Le("peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-switch-background focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",t),...s,children:e.jsx(mb,{"data-slot":"switch-thumb",className:Le("bg-card dark:data-[state=unchecked]:bg-card-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0")})})}function se({className:t,...s}){return e.jsx(ub,{"data-slot":"label",className:Le("flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",t),...s})}function pN({isAdmin:t,effectiveIsAdmin:s,viewAsUser:a,onToggleView:r,quickActions:n,userDisplayName:i,userEmail:l}){const o=i||(l==null?void 0:l.split("@")[0])||"User";return e.jsxs(Z,{className:"relative overflow-hidden shadow-xl border-0","data-testid":"dashboard-hero",children:[e.jsx("div",{className:"absolute inset-0 bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900"}),e.jsxs(J,{className:"relative z-10 p-4 sm:p-6 lg:p-12",children:[t&&e.jsx("div",{className:"absolute top-4 right-4 sm:top-6 sm:right-6 z-20","data-testid":"view-toggle",children:e.jsxs("div",{className:"flex items-center gap-1.5 sm:gap-2 p-1.5 sm:p-2 bg-white/10 backdrop-blur-sm rounded-lg border border-white/20 shadow-lg",children:[e.jsxs("div",{className:`flex items-center gap-1 sm:gap-1.5 px-1.5 sm:px-2 py-0.5 sm:py-1 rounded transition-all cursor-pointer ${a?"opacity-60 hover:opacity-80":"bg-red-600/20 border border-red-600/50"}`,onClick:()=>r(!1),children:[e.jsx(bc,{className:`w-3 h-3 sm:w-3.5 sm:h-3.5 ${a?"text-gray-300":"text-red-600"}`}),e.jsx(se,{htmlFor:"view-toggle-switch",className:`text-[10px] sm:text-xs font-medium cursor-pointer whitespace-nowrap ${a?"text-gray-300":"text-red-600"}`,children:"Org"})]}),e.jsx(os,{id:"view-toggle-switch",checked:a,onCheckedChange:r,className:"data-[state=checked]:bg-red-600 data-[state=unchecked]:bg-gray-600 shrink-0 scale-90 sm:scale-100","data-testid":"view-toggle-switch"}),e.jsxs("div",{className:`flex items-center gap-1 sm:gap-1.5 px-1.5 sm:px-2 py-0.5 sm:py-1 rounded transition-all cursor-pointer ${a?"bg-red-600/20 border border-red-600/50":"opacity-60 hover:opacity-80"}`,onClick:()=>r(!0),children:[e.jsx(Ht,{className:`w-3 h-3 sm:w-3.5 sm:h-3.5 ${a?"text-red-600":"text-gray-300"}`}),e.jsx(se,{htmlFor:"view-toggle-switch",className:`text-[10px] sm:text-xs font-medium cursor-pointer whitespace-nowrap ${a?"text-red-600":"text-gray-300"}`,children:"Personal"})]})]})}),e.jsx("div",{className:"flex flex-col lg:flex-row items-start lg:items-center justify-between gap-4 sm:gap-6",children:e.jsxs("div",{className:`text-white w-full lg:w-auto ${t?"pt-12 sm:pt-0":""}`,children:[e.jsx("div",{className:"flex items-center gap-3 sm:gap-4 mb-4 sm:mb-6",children:e.jsxs("div",{className:"pr-2 sm:pr-0",children:[e.jsxs("h1",{className:"text-2xl sm:text-3xl lg:text-4xl font-bold mb-1 sm:mb-2 text-white","data-testid":"hero-title",children:["Welcome, ",o,"!"]}),e.jsx("p",{className:"text-sm sm:text-lg lg:text-xl text-gray-200","data-testid":"hero-subtitle",children:s?"Organization-wide analytics and insights":"Track your requests and approvals"})]})}),e.jsx("div",{className:"flex flex-wrap gap-2 sm:gap-4 mt-4 sm:mt-8","data-testid":"quick-actions",children:n.map((c,d)=>e.jsxs(E,{onClick:c.action,className:`${c.color} text-white border-0 shadow-lg hover:shadow-xl transition-all duration-200`,size:window.innerWidth<640?"sm":"lg","data-testid":`quick-action-${c.label.toLowerCase().replace(/\s+/g,"-")}`,children:[e.jsx(c.icon,{className:"w-4 h-4 sm:w-5 sm:h-5 mr-1 sm:mr-2"}),c.label]},d))})]})})]})]})}function He({...t}){return e.jsx(xb,{"data-slot":"select",...t})}function Ge({...t}){return e.jsx(gb,{"data-slot":"select-value",...t})}function We({className:t,size:s="default",children:a,...r}){return e.jsxs(hb,{"data-slot":"select-trigger","data-size":s,className:Le("border-gray-400 data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground bg-white text-gray-900 flex w-full items-center justify-between gap-2 rounded-md border px-3 py-2 text-sm whitespace-nowrap transition-all outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4","hover:border-gray-500","focus-visible:border-re-light-green focus-visible:ring-0 focus-visible:outline-none","aria-invalid:ring-destructive/20 aria-invalid:border-destructive",t),...r,children:[a,e.jsx(pb,{asChild:!0,children:e.jsx(jn,{className:"size-4 opacity-50"})})]})}function Ye({className:t,children:s,position:a="popper",...r}){return e.jsx(fb,{children:e.jsxs(bb,{"data-slot":"select-content",className:Le("bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",a==="popper"&&"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",t),position:a,...r,children:[e.jsx(gN,{}),e.jsx(jb,{className:Le("p-1",a==="popper"&&"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"),children:s}),e.jsx(fN,{})]})})}function X({className:t,children:s,...a}){return e.jsxs(yb,{"data-slot":"select-item",className:Le("focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",t),...a,children:[e.jsx("span",{className:"absolute right-2 flex size-3.5 items-center justify-center",children:e.jsx(vb,{children:e.jsx($a,{className:"size-4"})})}),e.jsx(Nb,{children:s})]})}function gN({className:t,...s}){return e.jsx(wb,{"data-slot":"select-scroll-up-button",className:Le("flex cursor-default items-center justify-center py-1",t),...s,children:e.jsx(ul,{className:"size-4"})})}function fN({className:t,...s}){return e.jsx(Cb,{"data-slot":"select-scroll-down-button",className:Le("flex cursor-default items-center justify-center py-1",t),...s,children:e.jsx(jn,{className:"size-4"})})}function Jt({className:t,orientation:s="horizontal",decorative:a=!0,...r}){return e.jsx(Sb,{"data-slot":"separator-root",decorative:a,orientation:s,className:Le("bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",t),...r})}function aa({...t}){return e.jsx(Ab,{"data-slot":"popover",...t})}function ra({...t}){return e.jsx(Tb,{"data-slot":"popover-trigger",...t})}function na({className:t,align:s="center",sideOffset:a=4,...r}){return e.jsx(kb,{children:e.jsx(Db,{"data-slot":"popover-content",align:s,sideOffset:a,className:Le("bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",t),...r})})}var ct=function(){return ct=Object.assign||function(s){for(var a,r=1,n=arguments.length;r1&&(c||!d),p=s>1&&(d||!c),u=function(){r&&i(r)},h=function(){n&&i(n)};return e.jsx(XN,{displayMonth:t.displayMonth,hideNext:m,hidePrevious:p,nextMonth:n,previousMonth:r,onPreviousClick:u,onNextClick:h})}function ZN(t){var s,a=Yt(),r=a.classNames,n=a.disableNavigation,i=a.styles,l=a.captionLayout,o=a.components,c=(s=o==null?void 0:o.CaptionLabel)!==null&&s!==void 0?s:Zp,d;return n?d=e.jsx(c,{id:t.id,displayMonth:t.displayMonth}):l==="dropdown"?d=e.jsx(Eu,{displayMonth:t.displayMonth,id:t.id}):l==="dropdown-buttons"?d=e.jsxs(e.Fragment,{children:[e.jsx(Eu,{displayMonth:t.displayMonth,displayIndex:t.displayIndex,id:t.id}),e.jsx(_u,{displayMonth:t.displayMonth,displayIndex:t.displayIndex,id:t.id})]}):d=e.jsxs(e.Fragment,{children:[e.jsx(c,{id:t.id,displayMonth:t.displayMonth,displayIndex:t.displayIndex}),e.jsx(_u,{displayMonth:t.displayMonth,id:t.id})]}),e.jsx("div",{className:r.caption,style:i.caption,children:d})}function JN(t){var s=Yt(),a=s.footer,r=s.styles,n=s.classNames.tfoot;return a?e.jsx("tfoot",{className:n,style:r.tfoot,children:e.jsx("tr",{children:e.jsx("td",{colSpan:8,children:a})})}):e.jsx(e.Fragment,{})}function qN(t,s,a){for(var r=a?gc(new Date):fc(new Date,{locale:t,weekStartsOn:s}),n=[],i=0;i<7;i++){var l=oa(r,i);n.push(l)}return n}function ew(){var t=Yt(),s=t.classNames,a=t.styles,r=t.showWeekNumber,n=t.locale,i=t.weekStartsOn,l=t.ISOWeek,o=t.formatters.formatWeekdayName,c=t.labels.labelWeekday,d=qN(n,i,l);return e.jsxs("tr",{style:a.head_row,className:s.head_row,children:[r&&e.jsx("td",{style:a.head_cell,className:s.head_cell}),d.map(function(m,p){return e.jsx("th",{scope:"col",className:s.head_cell,style:a.head_cell,"aria-label":c(m,{locale:n}),children:o(m,{locale:n})},p)})]})}function tw(){var t,s=Yt(),a=s.classNames,r=s.styles,n=s.components,i=(t=n==null?void 0:n.HeadRow)!==null&&t!==void 0?t:ew;return e.jsx("thead",{style:r.head,className:a.head,children:e.jsx(i,{})})}function sw(t){var s=Yt(),a=s.locale,r=s.formatters.formatDay;return e.jsx(e.Fragment,{children:r(t.date,{locale:a})})}var Ec=x.createContext(void 0);function aw(t){if(!mi(t.initialProps)){var s={selected:void 0,modifiers:{disabled:[]}};return e.jsx(Ec.Provider,{value:s,children:t.children})}return e.jsx(rw,{initialProps:t.initialProps,children:t.children})}function rw(t){var s=t.initialProps,a=t.children,r=s.selected,n=s.min,i=s.max,l=function(d,m,p){var u,h;(u=s.onDayClick)===null||u===void 0||u.call(s,d,m,p);var g=!!(m.selected&&n&&(r==null?void 0:r.length)===n);if(!g){var f=!!(!m.selected&&i&&(r==null?void 0:r.length)===i);if(!f){var b=r?Kp([],r):[];if(m.selected){var j=b.findIndex(function(y){return ea(d,y)});b.splice(j,1)}else b.push(d);(h=s.onSelect)===null||h===void 0||h.call(s,b,d,m,p)}}},o={disabled:[]};r&&o.disabled.push(function(d){var m=i&&r.length>i-1,p=r.some(function(u){return ea(u,d)});return!!(m&&!p)});var c={selected:r,onDayClick:l,modifiers:o};return e.jsx(Ec.Provider,{value:c,children:a})}function _c(){var t=x.useContext(Ec);if(!t)throw new Error("useSelectMultiple must be used within a SelectMultipleProvider");return t}function nw(t,s){var a=s||{},r=a.from,n=a.to;return r&&n?ea(n,t)&&ea(r,t)?void 0:ea(n,t)?{from:n,to:void 0}:ea(r,t)?void 0:Eo(r,t)?{from:t,to:n}:{from:r,to:t}:n?Eo(t,n)?{from:n,to:t}:{from:t,to:n}:r?Qh(t,r)?{from:t,to:r}:{from:r,to:t}:{from:t,to:void 0}}var Mc=x.createContext(void 0);function iw(t){if(!ui(t.initialProps)){var s={selected:void 0,modifiers:{range_start:[],range_end:[],range_middle:[],disabled:[]}};return e.jsx(Mc.Provider,{value:s,children:t.children})}return e.jsx(lw,{initialProps:t.initialProps,children:t.children})}function lw(t){var s=t.initialProps,a=t.children,r=s.selected,n=r||{},i=n.from,l=n.to,o=s.min,c=s.max,d=function(h,g,f){var b,j;(b=s.onDayClick)===null||b===void 0||b.call(s,h,g,f);var y=nw(h,r);(j=s.onSelect)===null||j===void 0||j.call(s,y,h,g,f)},m={range_start:[],range_end:[],range_middle:[],disabled:[]};if(i?(m.range_start=[i],l?(m.range_end=[l],ea(i,l)||(m.range_middle=[{after:i,before:l}])):m.range_end=[i]):l&&(m.range_start=[l],m.range_end=[l]),o&&(i&&!l&&m.disabled.push({after:Ol(i,o-1),before:oa(i,o-1)}),i&&l&&m.disabled.push({after:i,before:oa(i,o-1)}),!i&&l&&m.disabled.push({after:Ol(l,o-1),before:oa(l,o-1)})),c){if(i&&!l&&(m.disabled.push({before:oa(i,-c+1)}),m.disabled.push({after:oa(i,c-1)})),i&&l){var p=Qa(l,i)+1,u=c-p;m.disabled.push({before:Ol(i,u)}),m.disabled.push({after:oa(l,u)})}!i&&l&&(m.disabled.push({before:oa(l,-c+1)}),m.disabled.push({after:oa(l,c-1)}))}return e.jsx(Mc.Provider,{value:{selected:r,onDayClick:d,modifiers:m},children:a})}function Lc(){var t=x.useContext(Mc);if(!t)throw new Error("useSelectRange must be used within a SelectRangeProvider");return t}function Li(t){return Array.isArray(t)?Kp([],t):t!==void 0?[t]:[]}function ow(t){var s={};return Object.entries(t).forEach(function(a){var r=a[0],n=a[1];s[r]=Li(n)}),s}var Ra;(function(t){t.Outside="outside",t.Disabled="disabled",t.Selected="selected",t.Hidden="hidden",t.Today="today",t.RangeStart="range_start",t.RangeEnd="range_end",t.RangeMiddle="range_middle"})(Ra||(Ra={}));var cw=Ra.Selected,Ya=Ra.Disabled,dw=Ra.Hidden,mw=Ra.Today,Ql=Ra.RangeEnd,Kl=Ra.RangeMiddle,Xl=Ra.RangeStart,uw=Ra.Outside;function xw(t,s,a){var r,n=(r={},r[cw]=Li(t.selected),r[Ya]=Li(t.disabled),r[dw]=Li(t.hidden),r[mw]=[t.today],r[Ql]=[],r[Kl]=[],r[Xl]=[],r[uw]=[],r);return t.fromDate&&n[Ya].push({before:t.fromDate}),t.toDate&&n[Ya].push({after:t.toDate}),mi(t)?n[Ya]=n[Ya].concat(s.modifiers[Ya]):ui(t)&&(n[Ya]=n[Ya].concat(a.modifiers[Ya]),n[Xl]=a.modifiers[Xl],n[Kl]=a.modifiers[Kl],n[Ql]=a.modifiers[Ql]),n}var eg=x.createContext(void 0);function hw(t){var s=Yt(),a=_c(),r=Lc(),n=xw(s,a,r),i=ow(s.modifiers),l=ct(ct({},n),i);return e.jsx(eg.Provider,{value:l,children:t.children})}function tg(){var t=x.useContext(eg);if(!t)throw new Error("useModifiers must be used within a ModifiersProvider");return t}function pw(t){return!!(t&&typeof t=="object"&&"before"in t&&"after"in t)}function gw(t){return!!(t&&typeof t=="object"&&"from"in t)}function fw(t){return!!(t&&typeof t=="object"&&"after"in t)}function bw(t){return!!(t&&typeof t=="object"&&"before"in t)}function jw(t){return!!(t&&typeof t=="object"&&"dayOfWeek"in t)}function yw(t,s){var a,r=s.from,n=s.to;if(r&&n){var i=Qa(n,r)<0;i&&(a=[n,r],r=a[0],n=a[1]);var l=Qa(t,r)>=0&&Qa(n,t)>=0;return l}return n?ea(n,t):r?ea(r,t):!1}function vw(t){return Zh(t)}function Nw(t){return Array.isArray(t)&&t.every(Zh)}function ww(t,s){return s.some(function(a){if(typeof a=="boolean")return a;if(vw(a))return ea(t,a);if(Nw(a))return a.includes(t);if(gw(a))return yw(t,a);if(jw(a))return a.dayOfWeek.includes(t.getDay());if(pw(a)){var r=Qa(a.before,t),n=Qa(a.after,t),i=r>0,l=n<0,o=Eo(a.before,a.after);return o?l&&i:i||l}return fw(a)?Qa(t,a.after)>0:bw(a)?Qa(a.before,t)>0:typeof a=="function"?a(t):!1})}function Oc(t,s,a){var r=Object.keys(s).reduce(function(i,l){var o=s[l];return ww(t,o)&&i.push(l),i},[]),n={};return r.forEach(function(i){return n[i]=!0}),a&&!pc(t,a)&&(n.outside=!0),n}function Cw(t,s){for(var a=ma(t[0]),r=hc(t[t.length-1]),n,i,l=a;l<=r;){var o=Oc(l,s),c=!o.disabled&&!o.hidden;if(!c){l=oa(l,1);continue}if(o.selected)return l;o.today&&!i&&(i=l),n||(n=l),l=oa(l,1)}return i||n}var Sw=365;function sg(t,s){var a=s.moveBy,r=s.direction,n=s.context,i=s.modifiers,l=s.retry,o=l===void 0?{count:0,lastFocused:t}:l,c=n.weekStartsOn,d=n.fromDate,m=n.toDate,p=n.locale,u={day:oa,week:Po,month:Oa,year:rj,startOfWeek:function(b){return n.ISOWeek?gc(b):fc(b,{locale:p,weekStartsOn:c})},endOfWeek:function(b){return n.ISOWeek?Kh(b):Xh(b,{locale:p,weekStartsOn:c})}},h=u[a](t,r==="after"?1:-1);r==="before"&&d?h=nj([d,h]):r==="after"&&m&&(h=ij([m,h]));var g=!0;if(i){var f=Oc(h,i);g=!f.disabled&&!f.hidden}return g?h:o.count>Sw?o.lastFocused:sg(h,{moveBy:a,direction:r,context:n,modifiers:i,retry:ct(ct({},o),{count:o.count+1})})}var ag=x.createContext(void 0);function Aw(t){var s=xi(),a=tg(),r=x.useState(),n=r[0],i=r[1],l=x.useState(),o=l[0],c=l[1],d=Cw(s.displayMonths,a),m=n??(o&&s.isDateDisplayed(o))?o:d,p=function(){c(n),i(void 0)},u=function(b){i(b)},h=Yt(),g=function(b,j){if(n){var y=sg(n,{moveBy:b,direction:j,context:h,modifiers:a});ea(n,y)||(s.goToDate(y,n),u(y))}},f={focusedDay:n,focusTarget:m,blur:p,focus:u,focusDayAfter:function(){return g("day","after")},focusDayBefore:function(){return g("day","before")},focusWeekAfter:function(){return g("week","after")},focusWeekBefore:function(){return g("week","before")},focusMonthBefore:function(){return g("month","before")},focusMonthAfter:function(){return g("month","after")},focusYearBefore:function(){return g("year","before")},focusYearAfter:function(){return g("year","after")},focusStartOfWeek:function(){return g("startOfWeek","before")},focusEndOfWeek:function(){return g("endOfWeek","after")}};return e.jsx(ag.Provider,{value:f,children:t.children})}function $c(){var t=x.useContext(ag);if(!t)throw new Error("useFocusContext must be used within a FocusProvider");return t}function Tw(t,s){var a=tg(),r=Oc(t,a,s);return r}var Uc=x.createContext(void 0);function kw(t){if(!kl(t.initialProps)){var s={selected:void 0};return e.jsx(Uc.Provider,{value:s,children:t.children})}return e.jsx(Dw,{initialProps:t.initialProps,children:t.children})}function Dw(t){var s=t.initialProps,a=t.children,r=function(i,l,o){var c,d,m;if((c=s.onDayClick)===null||c===void 0||c.call(s,i,l,o),l.selected&&!s.required){(d=s.onSelect)===null||d===void 0||d.call(s,void 0,i,l,o);return}(m=s.onSelect)===null||m===void 0||m.call(s,i,i,l,o)},n={selected:s.selected,onDayClick:r};return e.jsx(Uc.Provider,{value:n,children:a})}function rg(){var t=x.useContext(Uc);if(!t)throw new Error("useSelectSingle must be used within a SelectSingleProvider");return t}function Rw(t,s){var a=Yt(),r=rg(),n=_c(),i=Lc(),l=$c(),o=l.focusDayAfter,c=l.focusDayBefore,d=l.focusWeekAfter,m=l.focusWeekBefore,p=l.blur,u=l.focus,h=l.focusMonthBefore,g=l.focusMonthAfter,f=l.focusYearBefore,b=l.focusYearAfter,j=l.focusStartOfWeek,y=l.focusEndOfWeek,N=function(D){var C,G,U,$;kl(a)?(C=r.onDayClick)===null||C===void 0||C.call(r,t,s,D):mi(a)?(G=n.onDayClick)===null||G===void 0||G.call(n,t,s,D):ui(a)?(U=i.onDayClick)===null||U===void 0||U.call(i,t,s,D):($=a.onDayClick)===null||$===void 0||$.call(a,t,s,D)},S=function(D){var C;u(t),(C=a.onDayFocus)===null||C===void 0||C.call(a,t,s,D)},A=function(D){var C;p(),(C=a.onDayBlur)===null||C===void 0||C.call(a,t,s,D)},w=function(D){var C;(C=a.onDayMouseEnter)===null||C===void 0||C.call(a,t,s,D)},I=function(D){var C;(C=a.onDayMouseLeave)===null||C===void 0||C.call(a,t,s,D)},k=function(D){var C;(C=a.onDayPointerEnter)===null||C===void 0||C.call(a,t,s,D)},T=function(D){var C;(C=a.onDayPointerLeave)===null||C===void 0||C.call(a,t,s,D)},v=function(D){var C;(C=a.onDayTouchCancel)===null||C===void 0||C.call(a,t,s,D)},_=function(D){var C;(C=a.onDayTouchEnd)===null||C===void 0||C.call(a,t,s,D)},P=function(D){var C;(C=a.onDayTouchMove)===null||C===void 0||C.call(a,t,s,D)},B=function(D){var C;(C=a.onDayTouchStart)===null||C===void 0||C.call(a,t,s,D)},R=function(D){var C;(C=a.onDayKeyUp)===null||C===void 0||C.call(a,t,s,D)},M=function(D){var C;switch(D.key){case"ArrowLeft":D.preventDefault(),D.stopPropagation(),a.dir==="rtl"?o():c();break;case"ArrowRight":D.preventDefault(),D.stopPropagation(),a.dir==="rtl"?c():o();break;case"ArrowDown":D.preventDefault(),D.stopPropagation(),d();break;case"ArrowUp":D.preventDefault(),D.stopPropagation(),m();break;case"PageUp":D.preventDefault(),D.stopPropagation(),D.shiftKey?f():h();break;case"PageDown":D.preventDefault(),D.stopPropagation(),D.shiftKey?b():g();break;case"Home":D.preventDefault(),D.stopPropagation(),j();break;case"End":D.preventDefault(),D.stopPropagation(),y();break}(C=a.onDayKeyDown)===null||C===void 0||C.call(a,t,s,D)},L={onClick:N,onFocus:S,onBlur:A,onKeyDown:M,onKeyUp:R,onMouseEnter:w,onMouseLeave:I,onPointerEnter:k,onPointerLeave:T,onTouchCancel:v,onTouchEnd:_,onTouchMove:P,onTouchStart:B};return L}function Fw(){var t=Yt(),s=rg(),a=_c(),r=Lc(),n=kl(t)?s.selected:mi(t)?a.selected:ui(t)?r.selected:void 0;return n}function Iw(t){return Object.values(Ra).includes(t)}function Pw(t,s){var a=[t.classNames.day];return Object.keys(s).forEach(function(r){var n=t.modifiersClassNames[r];if(n)a.push(n);else if(Iw(r)){var i=t.classNames["day_".concat(r)];i&&a.push(i)}}),a}function Ew(t,s){var a=ct({},t.styles.day);return Object.keys(s).forEach(function(r){var n;a=ct(ct({},a),(n=t.modifiersStyles)===null||n===void 0?void 0:n[r])}),a}function _w(t,s,a){var r,n,i,l=Yt(),o=$c(),c=Tw(t,s),d=Rw(t,c),m=Fw(),p=!!(l.onDayClick||l.mode!=="default");x.useEffect(function(){var w;c.outside||o.focusedDay&&p&&ea(o.focusedDay,t)&&((w=a.current)===null||w===void 0||w.focus())},[o.focusedDay,t,a,p,c.outside]);var u=Pw(l,c).join(" "),h=Ew(l,c),g=!!(c.outside&&!l.showOutsideDays||c.hidden),f=(i=(n=l.components)===null||n===void 0?void 0:n.DayContent)!==null&&i!==void 0?i:sw,b=e.jsx(f,{date:t,displayMonth:s,activeModifiers:c}),j={style:h,className:u,children:b,role:"gridcell"},y=o.focusTarget&&ea(o.focusTarget,t)&&!c.outside,N=o.focusedDay&&ea(o.focusedDay,t),S=ct(ct(ct({},j),(r={disabled:c.disabled,role:"gridcell"},r["aria-selected"]=c.selected,r.tabIndex=N||y?0:-1,r)),d),A={isButton:p,isHidden:g,activeModifiers:c,selectedDays:m,buttonProps:S,divProps:j};return A}function Mw(t){var s=x.useRef(null),a=_w(t.date,t.displayMonth,s);return a.isHidden?e.jsx("div",{role:"gridcell"}):a.isButton?e.jsx(tl,ct({name:"day",ref:s},a.buttonProps)):e.jsx("div",ct({},a.divProps))}function Lw(t){var s=t.number,a=t.dates,r=Yt(),n=r.onWeekNumberClick,i=r.styles,l=r.classNames,o=r.locale,c=r.labels.labelWeekNumber,d=r.formatters.formatWeekNumber,m=d(Number(s),{locale:o});if(!n)return e.jsx("span",{className:l.weeknumber,style:i.weeknumber,children:m});var p=c(Number(s),{locale:o}),u=function(h){n(s,a,h)};return e.jsx(tl,{name:"week-number","aria-label":p,className:l.weeknumber,style:i.weeknumber,onClick:u,children:m})}function Ow(t){var s,a,r=Yt(),n=r.styles,i=r.classNames,l=r.showWeekNumber,o=r.components,c=(s=o==null?void 0:o.Day)!==null&&s!==void 0?s:Mw,d=(a=o==null?void 0:o.WeekNumber)!==null&&a!==void 0?a:Lw,m;return l&&(m=e.jsx("td",{className:i.cell,style:n.cell,children:e.jsx(d,{number:t.weekNumber,dates:t.dates})})),e.jsxs("tr",{className:i.row,style:n.row,children:[m,t.dates.map(function(p){return e.jsx("td",{className:i.cell,style:n.cell,role:"presentation",children:e.jsx(c,{displayMonth:t.displayMonth,date:p})},aj(p))})]})}function Mu(t,s,a){for(var r=a!=null&&a.ISOWeek?Kh(s):Xh(s,a),n=a!=null&&a.ISOWeek?gc(t):fc(t,a),i=Qa(r,n),l=[],o=0;o<=i;o++)l.push(oa(n,o));var c=l.reduce(function(d,m){var p=a!=null&&a.ISOWeek?cj(m):dj(m,a),u=d.find(function(h){return h.weekNumber===p});return u?(u.dates.push(m),d):(d.push({weekNumber:p,dates:[m]}),d)},[]);return c}function $w(t,s){var a=Mu(ma(t),hc(t),s);if(s!=null&&s.useFixedWeeks){var r=sj(t,s);if(r<6){var n=a[a.length-1],i=n.dates[n.dates.length-1],l=Po(i,6-r),o=Mu(Po(i,1),l,s);a.push.apply(a,o)}}return a}function Uw(t){var s,a,r,n=Yt(),i=n.locale,l=n.classNames,o=n.styles,c=n.hideHead,d=n.fixedWeeks,m=n.components,p=n.weekStartsOn,u=n.firstWeekContainsDate,h=n.ISOWeek,g=$w(t.displayMonth,{useFixedWeeks:!!d,ISOWeek:h,locale:i,weekStartsOn:p,firstWeekContainsDate:u}),f=(s=m==null?void 0:m.Head)!==null&&s!==void 0?s:tw,b=(a=m==null?void 0:m.Row)!==null&&a!==void 0?a:Ow,j=(r=m==null?void 0:m.Footer)!==null&&r!==void 0?r:JN;return e.jsxs("table",{id:t.id,className:l.table,style:o.table,role:"grid","aria-labelledby":t["aria-labelledby"],children:[!c&&e.jsx(f,{}),e.jsx("tbody",{className:l.tbody,style:o.tbody,children:g.map(function(y){return e.jsx(b,{displayMonth:t.displayMonth,dates:y.dates,weekNumber:y.weekNumber},y.weekNumber)})}),e.jsx(j,{displayMonth:t.displayMonth})]})}function Bw(){return!!(typeof window<"u"&&window.document&&window.document.createElement)}var Vw=Bw()?x.useLayoutEffect:x.useEffect,Zl=!1,zw=0;function Lu(){return"react-day-picker-".concat(++zw)}function Hw(t){var s,a=t??(Zl?Lu():null),r=x.useState(a),n=r[0],i=r[1];return Vw(function(){n===null&&i(Lu())},[]),x.useEffect(function(){Zl===!1&&(Zl=!0)},[]),(s=t??n)!==null&&s!==void 0?s:void 0}function Gw(t){var s,a,r=Yt(),n=r.dir,i=r.classNames,l=r.styles,o=r.components,c=xi().displayMonths,d=Hw(r.id?"".concat(r.id,"-").concat(t.displayIndex):void 0),m=r.id?"".concat(r.id,"-grid-").concat(t.displayIndex):void 0,p=[i.month],u=l.month,h=t.displayIndex===0,g=t.displayIndex===c.length-1,f=!h&&!g;n==="rtl"&&(s=[h,g],g=s[0],h=s[1]),h&&(p.push(i.caption_start),u=ct(ct({},u),l.caption_start)),g&&(p.push(i.caption_end),u=ct(ct({},u),l.caption_end)),f&&(p.push(i.caption_between),u=ct(ct({},u),l.caption_between));var b=(a=o==null?void 0:o.Caption)!==null&&a!==void 0?a:ZN;return e.jsxs("div",{className:p.join(" "),style:u,children:[e.jsx(b,{id:d,displayMonth:t.displayMonth,displayIndex:t.displayIndex}),e.jsx(Uw,{id:m,"aria-labelledby":d,displayMonth:t.displayMonth})]},t.displayIndex)}function Ww(t){var s=Yt(),a=s.classNames,r=s.styles;return e.jsx("div",{className:a.months,style:r.months,children:t.children})}function Yw(t){var s,a,r=t.initialProps,n=Yt(),i=$c(),l=xi(),o=x.useState(!1),c=o[0],d=o[1];x.useEffect(function(){n.initialFocus&&i.focusTarget&&(c||(i.focus(i.focusTarget),d(!0)))},[n.initialFocus,c,i.focus,i.focusTarget,i]);var m=[n.classNames.root,n.className];n.numberOfMonths>1&&m.push(n.classNames.multiple_months),n.showWeekNumber&&m.push(n.classNames.with_weeknumber);var p=ct(ct({},n.styles.root),n.style),u=Object.keys(r).filter(function(g){return g.startsWith("data-")}).reduce(function(g,f){var b;return ct(ct({},g),(b={},b[f]=r[f],b))},{}),h=(a=(s=r.components)===null||s===void 0?void 0:s.Months)!==null&&a!==void 0?a:Ww;return e.jsx("div",ct({className:m.join(" "),style:p,dir:n.dir,id:n.id,nonce:r.nonce,title:r.title,lang:r.lang},u,{children:e.jsx(h,{children:l.displayMonths.map(function(g,f){return e.jsx(Gw,{displayIndex:f,displayMonth:g},f)})})}))}function Qw(t){var s=t.children,a=bN(t,["children"]);return e.jsx(LN,{initialProps:a,children:e.jsx(YN,{children:e.jsx(kw,{initialProps:a,children:e.jsx(aw,{initialProps:a,children:e.jsx(iw,{initialProps:a,children:e.jsx(hw,{children:e.jsx(Aw,{children:s})})})})})})})}function Kw(t){return e.jsx(Qw,ct({},t,{children:e.jsx(Yw,{initialProps:t})}))}function Oi({className:t,classNames:s,showOutsideDays:a=!0,...r}){return e.jsx(Kw,{showOutsideDays:a,className:Le("p-3",t),classNames:{months:"flex flex-col sm:flex-row gap-2",month:"flex flex-col gap-4",caption:"flex justify-center pt-1 relative items-center w-full",caption_label:"text-sm font-medium",nav:"flex items-center gap-1",nav_button:Le(ei({variant:"outline"}),"size-7 bg-transparent p-0 opacity-50 hover:opacity-100"),nav_button_previous:"absolute left-1",nav_button_next:"absolute right-1",table:"w-full border-collapse space-x-1",head_row:"flex",head_cell:"text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]",row:"flex w-full mt-2",cell:Le("relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-range-end)]:rounded-r-md",r.mode==="range"?"[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md":"[&:has([aria-selected])]:rounded-md"),day:Le(ei({variant:"ghost"}),"size-8 p-0 font-normal aria-selected:opacity-100"),day_range_start:"day-range-start aria-selected:bg-primary aria-selected:text-primary-foreground",day_range_end:"day-range-end aria-selected:bg-primary aria-selected:text-primary-foreground",day_selected:"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",day_today:"bg-accent text-accent-foreground",day_outside:"day-outside text-muted-foreground aria-selected:text-muted-foreground",day_disabled:"text-muted-foreground opacity-50",day_range_middle:"aria-selected:bg-accent aria-selected:text-accent-foreground",day_hidden:"invisible",...s},components:{IconLeft:({className:n,...i})=>e.jsx(pn,{className:Le("size-4",n),...i}),IconRight:({className:n,...i})=>e.jsx(Ka,{className:Le("size-4",n),...i})},...r})}function ta({value:t,onChange:s,minDate:a,maxDate:r,placeholderText:n="dd/mm/yyyy",disabled:i=!1,className:l,wrapperClassName:o,error:c=!1,displayFormat:d="dd/MM/yyyy",id:m}){const[p,u]=x.useState(!1),h=x.useMemo(()=>{if(t){if(t instanceof Date)return Jr(t)?t:void 0;if(typeof t=="string")try{const j=Ul(t,"yyyy-MM-dd",new Date);return Jr(j)?j:void 0}catch{return}}},[t]),g=x.useMemo(()=>{if(a){if(a instanceof Date)return Jr(a)?a:void 0;if(typeof a=="string"){const j=Ul(a,"yyyy-MM-dd",new Date);return Jr(j)?j:void 0}}},[a]),f=x.useMemo(()=>{if(r){if(r instanceof Date)return Jr(r)?r:void 0;if(typeof r=="string"){const j=Ul(r,"yyyy-MM-dd",new Date);return Jr(j)?j:void 0}}},[r]),b=j=>{if(u(!1),!!s){if(!j){s(null);return}s(st(j,"yyyy-MM-dd"))}};return e.jsx("div",{className:Le("relative",o),children:e.jsxs(aa,{open:p,onOpenChange:u,children:[e.jsx(ra,{asChild:!0,children:e.jsxs(E,{id:m,disabled:i,variant:"outline",className:Le("w-full justify-start text-left font-normal",!h&&"text-muted-foreground",c&&"border-destructive ring-destructive/20",l),children:[e.jsx(ft,{className:"mr-2 h-4 w-4"}),h?st(h,d):e.jsx("span",{children:n})]})}),e.jsx(na,{className:"w-auto p-0",align:"start",children:e.jsx(Oi,{mode:"single",selected:h,onSelect:b,disabled:j=>!!(g&&jf),initialFocus:!0})})]})})}function Xw({isAdmin:t,dateRange:s,customStartDate:a,customEndDate:r,showCustomDatePicker:n,refreshing:i,onDateRangeChange:l,onCustomStartDateChange:o,onCustomEndDateChange:c,onShowCustomDatePickerChange:d,onApplyCustomDate:m,onResetCustomDates:p,onRefresh:u}){return e.jsx(Z,{className:"shadow-md","data-testid":"dashboard-filters-bar",children:e.jsx(J,{className:"p-4",children:e.jsxs("div",{className:"flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(Ua,{className:"w-5 h-5 text-muted-foreground"}),e.jsx("h3",{className:"font-semibold text-gray-900",children:"Filters"}),t&&e.jsx(re,{variant:"outline",className:"bg-purple-50 text-purple-700 border-purple-200","data-testid":"management-badge",children:"Management View"})]}),e.jsxs("div",{className:"flex flex-wrap items-center gap-3 w-full sm:w-auto",children:[t?e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(ft,{className:"w-4 h-4 text-muted-foreground"}),e.jsxs(He,{value:s,onValueChange:l,"data-testid":"date-range-select",children:[e.jsx(We,{className:"w-[140px]",children:e.jsx(Ge,{placeholder:"Select period"})}),e.jsxs(Ye,{children:[e.jsx(X,{value:"all",children:"All Time"}),e.jsx(X,{value:"today",children:"Today"}),e.jsx(X,{value:"week",children:"This Week"}),e.jsx(X,{value:"month",children:"This Month"}),e.jsx(X,{value:"last7days",children:"Last 7 Days"}),e.jsx(X,{value:"last30days",children:"Last 30 Days"}),e.jsx(X,{value:"custom",children:"Custom Range"})]})]}),s==="custom"&&e.jsxs(aa,{open:n,onOpenChange:d,children:[e.jsx(ra,{asChild:!0,children:e.jsxs(E,{variant:"outline",size:"sm",className:"gap-2","data-testid":"custom-date-trigger",children:[e.jsx(ft,{className:"w-4 h-4"}),a&&r?`${st(a,"MMM d, yyyy")} - ${st(r,"MMM d, yyyy")}`:"Select dates"]})}),e.jsx(na,{className:"w-auto p-4",align:"start",sideOffset:8,"data-testid":"custom-date-picker",children:e.jsxs("div",{className:"space-y-4",children:[e.jsxs("div",{className:"space-y-3",children:[e.jsxs("div",{className:"space-y-2",children:[e.jsx(se,{htmlFor:"start-date",className:"text-sm font-medium",children:"Start Date"}),e.jsx(ta,{value:a||null,onChange:h=>{const g=h?new Date(h):void 0;g?(o(g),r&&g>r&&c(g)):o(void 0)},maxDate:new Date,placeholderText:"dd/mm/yyyy",className:"w-full","data-testid":"start-date-input"})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(se,{htmlFor:"end-date",className:"text-sm font-medium",children:"End Date"}),e.jsx(ta,{value:r||null,onChange:h=>{const g=h?new Date(h):void 0;g?(c(g),a&&gl(h),"data-testid":"date-range-select-user",children:[e.jsx(We,{className:"w-[140px]",children:e.jsx(Ge,{placeholder:"Select period"})}),e.jsxs(Ye,{children:[e.jsx(X,{value:"all",children:"All Time"}),e.jsx(X,{value:"today",children:"Today"}),e.jsx(X,{value:"week",children:"This Week"}),e.jsx(X,{value:"month",children:"This Month"}),e.jsx(X,{value:"last7days",children:"Last 7 Days"}),e.jsx(X,{value:"last30days",children:"Last 30 Days"}),e.jsx(X,{value:"custom",children:"Custom Range"})]})]}),s==="custom"&&e.jsxs(aa,{open:n,onOpenChange:d,children:[e.jsx(ra,{asChild:!0,children:e.jsxs(E,{variant:"outline",size:"sm",className:"gap-2","data-testid":"custom-date-trigger",children:[e.jsx(ft,{className:"w-4 h-4"}),a&&r?`${st(a,"MMM d, yyyy")} - ${st(r,"MMM d, yyyy")}`:"Select dates"]})}),e.jsx(na,{className:"w-auto p-4",align:"start",sideOffset:8,"data-testid":"custom-date-picker",children:e.jsxs("div",{className:"space-y-4",children:[e.jsxs("div",{className:"space-y-3",children:[e.jsxs("div",{className:"space-y-2",children:[e.jsx(se,{htmlFor:"start-date-user",className:"text-sm font-medium",children:"Start Date"}),e.jsx(ta,{value:a||null,onChange:h=>{const g=h?new Date(h):void 0;g?(o(g),r&&g>r&&c(g)):o(void 0)},maxDate:new Date,placeholderText:"dd/mm/yyyy",className:"w-full","data-testid":"start-date-input-user"})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(se,{htmlFor:"end-date-user",className:"text-sm font-medium",children:"End Date"}),e.jsx(ta,{value:r||null,onChange:h=>{const g=h?new Date(h):void 0;g?(c(g),a&&g