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..7026f1f
--- /dev/null
+++ b/RE-CPC-CDC_POSTMAN_COLLECTION/RE_Workflow_CPC_CDC_API.postman_collection.json
@@ -0,0 +1,983 @@
+{
+ "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## Dashboard SPA parity (pagination, booking search, rejected filter)\nThe **CPC-CSD Dashboard** in `re-workflow-fe/src/pages/CpcCdc/Dashboard.jsx` drives list behaviour against this API:\n\n| UI area | HTTP | Notes |\n|---------|------|-------|\n| Submissions table (paginated) | `GET {{apiRoot}}/cpc-csd/documents/recent` | **`page`** (1-based), **`limit`** (e.g. 10–50), **`sortBy`** (default `createdAt`), **`order`** (`asc` / `desc`). |\n| Search by booking / claim ID | Same | Query **`search`**: case-insensitive substring on **`booking_id`**, **`claim_id`**, **`document_type`**, and document **`id`** (recent-documents path sets `searchIncludeId`). |\n| “Rejected / mismatch” tab | Same | Query **`status=UNSUCCESSFUL`**: server expands to `MISMATCH`, `REJECTED`, `UNSUCCESSFUL`, `NEED_MANUAL` (see `appendCpcDocumentFilters` in `src/services/cpc-cdc/utils.ts`). |\n| “All submissions” tab | Same | Omit **`status`** or use **`ALL`** — no status filter. |\n| Summary stat cards | `GET .../documents/analytics` | Global counts: **`totalDocs`**, **`distribution`** (per `validation_status`), **`passRate`**, **`dailyVolume`**, **`topMismatchFields`**. |\n\n**List response shape** (200): `{ \"items\": [ /* CpcDocument rows + summary */ ], \"meta\": { \"total\": number, \"page\": number, \"limit\": number, \"pages\": number } }`. Legacy responses that are a bare array are still tolerated by some clients; prefer **`items` + `meta`**.\n\n**Pagination caveat:** The API paginates **document rows**. The UI may **group** rows by `(claim_id, attempt_no)`; a multi-file upload can theoretically span two pages if files sort apart — use a larger **`limit`** when testing full batches.\n\n## Reference\n- Repo: `re-workflow-be/docs/CPC-CDC.md`\n- Filter implementation: `re-workflow-be/src/services/cpc-cdc/utils.ts` (`appendCpcDocumentFilters`)\n- List + analytics handlers: `re-workflow-be/src/controllers/CpcCdcController.ts` (`getRecentDocuments`, `getAnalytics`)",
+ "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": "Run **before** any CPC-CSD call.\n\n1. **`GET Health`** — proves the process is listening (no token).\n2. **`GET Auth me`** — proves **`{{accessToken}}`** is valid for **`{{apiRoot}}`**.\n\nIf **401** on `auth/me`, refresh the JWT in environment variable **`accessToken`** (no `Bearer ` prefix — Postman adds it).",
+ "item": [
+ {
+ "name": "GET Health (no auth)",
+ "description": "Kubernetes / load-balancer style probe; does not touch the database.",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": "{{hostUrl}}/health",
+ "description": "**Auth:** none.\n\n**200:** Plain JSON or text body indicating the Node process is up. **Use:** quick sanity check that **`{{hostUrl}}`** points at the correct host/port.",
+ "auth": {
+ "type": "noauth"
+ }
+ }
+ },
+ {
+ "name": "GET Auth me",
+ "description": "Resolves the current user from the JWT used by the rest of the collection.",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": "{{apiRoot}}/auth/me",
+ "description": "**Auth:** Bearer `{{accessToken}}` (collection-level auth also applies).\n\n**200:** User profile (email, role, …) — exact shape from main RE Workflow auth module.\n\n**401:** Token missing, expired, or wrong signing key — update **`accessToken`**.",
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "{{accessToken}}",
+ "type": "string"
+ }
+ ]
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "02_CPC_CSD_API",
+ "description": "**Canonical mount:** `{{apiRoot}}/cpc-csd/...` (legacy alias **`/cpc-cdc/...`** — same handlers). **Compat:** `{{hostUrl}}/api/documents/*` and uploads under `{{hostUrl}}/api/...` — see `docs/CPC-CDC.md`.\n\nThis folder is split into:\n- **GET Permissions** — feature gate / viewer check.\n- **02a_Dashboard_lists_and_filters** — analytics + paginated recent list + claim history (matches SPA dashboard behaviour).\n- **02b_Single_document** — fetch/update/delete one document by UUID.\n- **02c_Excel_reports** — per-claim and master exports.\n- **POST Bare file upload** — raw `file` → GCS URL (not under `cpc-csd`).",
+ "item": [
+ {
+ "name": "GET Permissions",
+ "description": "Returns which CPC-CSD capabilities the authenticated user may use (viewer vs admin). Call after **`GET Auth me`** if the UI gates routes.",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": "{{apiRoot}}/cpc-csd/permissions",
+ "description": "**Auth:** Bearer `{{accessToken}}`.\n\n**200:** JSON permission flags used by the frontend route guard.",
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "{{accessToken}}",
+ "type": "string"
+ }
+ ]
+ }
+ }
+ },
+ {
+ "name": "02a_Dashboard_lists_and_filters",
+ "description": "Endpoints aligned with **`Dashboard.jsx`**:\n\n1. **`GET .../documents/analytics`** — global counters for summary cards (`totalDocs`, `distribution` keyed by `validation_status`, `passRate`, `dailyVolume`, `topMismatchFields`).\n2. **`GET .../documents/recent`** — paginated rows + `meta` for the submissions table; supports **`search`** (booking / claim / type / id) and **`status`** (e.g. **`UNSUCCESSFUL`** for rejected/mismatch tab).\n3. **`GET .../documents/history`** — all attempts for a single **`claimId`**.\n\nEnvironment keys: **`recentPage`**, **`recentLimit`**, **`recentSearch`**, **`recentStatus`**, **`recentType`**, **`recentSortBy`**, **`recentOrder`**.",
+ "item": [
+ {
+ "name": "GET Documents analytics",
+ "description": "Aggregate metrics across **all** CPC documents visible to the user (not limited to the current list page). Used by the dashboard stat cards.",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": "{{apiRoot}}/cpc-csd/documents/analytics",
+ "description": "**Auth:** Bearer `{{accessToken}}`.\n\n**200 — typical JSON keys:**\n| Field | Meaning |\n|-------|--------|\n| `totalDocs` | Count of documents in scope. |\n| `distribution` | Object map: `validation_status` → count (e.g. `MATCH`, `MISMATCH`, `PENDING`, …). |\n| `passRate` | Percentage derived from successful vs total. |\n| `topMismatchFields` | Ranked field names from `mismatch_reasons` on failed rows. |\n| `dailyVolume` | Upload counts per calendar day (recent window). |\n\n**Errors:** `500` with `error_code` / `error_message` on failure.",
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "{{accessToken}}",
+ "type": "string"
+ }
+ ]
+ }
+ }
+ },
+ {
+ "name": "GET Documents history by claim",
+ "description": "Timeline of uploads / attempts for one **`claim_id`** (same logical claim as used on multipart upload). Set env **`claimIdCpc`** (or CSD) to the claim you want to inspect.",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": "{{apiRoot}}/cpc-csd/documents/history?claimId={{claimIdCpc}}",
+ "description": "**Query (required):**\n| Param | Example | Meaning |\n|-------|---------|--------|\n| `claimId` | `{{claimIdCpc}}` | Claim / booking family identifier (e.g. `CPC-POSTMAN-0001`). |\n\n**Auth:** Bearer `{{accessToken}}`.\n\n**200:** Grouped attempt structure used by the History UI.",
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "{{accessToken}}",
+ "type": "string"
+ }
+ ]
+ }
+ }
+ },
+ {
+ "name": "GET Documents recent (paginated + search + filters)",
+ "description": "Primary dashboard list. Combines **pagination**, optional **booking/claim search**, **validation status bucket**, and **document type** filter. Mirrors `GET {{hostUrl}}/api/documents/recent` on the legacy compat mount.",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": {
+ "raw": "{{apiRoot}}/cpc-csd/documents/recent?page={{recentPage}}&limit={{recentLimit}}&search={{recentSearch}}&status={{recentStatus}}&type={{recentType}}&sortBy={{recentSortBy}}&order={{recentOrder}}",
+ "query": [
+ {
+ "key": "page",
+ "value": "{{recentPage}}",
+ "description": "**1-based** page index. Increase to walk older rows."
+ },
+ {
+ "key": "limit",
+ "value": "{{recentLimit}}",
+ "description": "Page size (documents per page). Dashboard uses 10–50."
+ },
+ {
+ "key": "search",
+ "value": "{{recentSearch}}",
+ "description": "Optional. Case-insensitive `ILIKE` on **`booking_id`**, **`claim_id`**, **`document_type`**, and document **`id`** (UUID). Partial values OK (e.g. `CPC-114` or suffix of UUID). Leave empty in env to disable."
+ },
+ {
+ "key": "status",
+ "value": "{{recentStatus}}",
+ "description": "Optional filter. **`ALL`** or empty = no filter. **`SUCCESSFUL`** → `MATCH` + `SUCCESSFUL` + `APPROVED`. **`UNSUCCESSFUL`** → `MISMATCH` + `REJECTED` + `UNSUCCESSFUL` + `NEED_MANUAL` (use this for “Rejected / mismatch” dashboard tab). Any other value is applied as exact `validation_status`."
+ },
+ {
+ "key": "type",
+ "value": "{{recentType}}",
+ "description": "Optional document family: **`AADHAAR`**, **`CPC_AUTH`**, **`CSD_PO`**, **`RETAIL_INVOICE`**, **`ALL`**, or empty. Server maps friendly names to `document_type` patterns."
+ },
+ {
+ "key": "sortBy",
+ "value": "{{recentSortBy}}",
+ "description": "One of: `id`, `bookingId`, `createdAt`, `documentType`, `validationStatus`, `claimId`, `matchPercentage`. Default in UI: `createdAt`."
+ },
+ {
+ "key": "order",
+ "value": "{{recentOrder}}",
+ "description": "`asc` or `desc` (case-insensitive). Default newest-first: `desc`."
+ }
+ ]
+ },
+ "description": "**Auth:** Bearer `{{accessToken}}`.\n\n**200 — body:**\n```json\n{\n \"items\": [ { /* Sequelize row + summary */ } ],\n \"meta\": {\n \"total\": 0,\n \"page\": 1,\n \"limit\": 15,\n \"pages\": 0\n }\n}\n```\n\n- **`items`**: each element includes `bookingId`, `claimId`, `documentType`, `validationStatus`, `matchPercentage`, `createdAt`, `summary`, etc.\n- **`meta.total`**: total rows matching filters (for pagination UI).\n\n**Test script:** on success, saves **`items[0].id`** to **`cpcDocumentId`** when present so **02b_Single_document** requests resolve immediately.",
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "{{accessToken}}",
+ "type": "string"
+ }
+ ]
+ }
+ },
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "exec": [
+ "pm.test('Recent: 200 JSON', function () {",
+ " pm.response.to.have.status(200);",
+ " pm.response.to.be.json;",
+ "});",
+ "if (pm.response.code === 200) {",
+ " try {",
+ " const j = pm.response.json();",
+ " const items = j.items || [];",
+ " if (j.meta) {",
+ " pm.test('Recent: meta has pagination fields', function () {",
+ " pm.expect(j.meta).to.include.keys('total', 'page', 'limit', 'pages');",
+ " });",
+ " }",
+ " if (items.length && items[0].id != null) {",
+ " pm.environment.set('cpcDocumentId', String(items[0].id));",
+ " }",
+ " } catch (e) { /* ignore */ }",
+ "}"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "02b_Single_document",
+ "description": "Operations on **`{{cpcDocumentId}}`** (UUID primary key of `cpc_documents`).\n\n**Prerequisite:** set **`cpcDocumentId`** in the environment, or run **GET Documents recent** in folder **02a** so the test script populates it from the first row.",
+ "item": [
+ {
+ "name": "GET Document by id",
+ "description": "Full document JSON including audit / field breakdown used on the review page.",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": "{{apiRoot}}/cpc-csd/documents/{{cpcDocumentId}}",
+ "description": "**Path:** `:id` = UUID (**`{{cpcDocumentId}}`**).\n\n**200:** Document + related `auditLogs` / `field_results` shape per `getDocumentById`.\n\n**404:** `DOCUMENT_NOT_FOUND`.",
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "{{accessToken}}",
+ "type": "string"
+ }
+ ]
+ }
+ }
+ },
+ {
+ "name": "GET Document file binary",
+ "description": "Streams the **original upload** bytes (GCS download or local disk) with correct `Content-Type` for iframe / `
` preview.",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": "{{apiRoot}}/cpc-csd/documents/{{cpcDocumentId}}/file",
+ "description": "**200:** binary body.\n\n**404 / 502:** missing file or GCS read failure — see JSON error body.",
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "{{accessToken}}",
+ "type": "string"
+ }
+ ]
+ }
+ }
+ },
+ {
+ "name": "PUT Document status",
+ "description": "Manual adjudication: set validation status and optional remarks / corrected extracted fields.",
+ "request": {
+ "method": "PUT",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{{putStatusBodyJson}}"
+ },
+ "url": "{{apiRoot}}/cpc-csd/documents/{{cpcDocumentId}}/status",
+ "description": "**Body:** JSON from env **`putStatusBodyJson`** — typically `{ \"status\": \"APPROVED\", \"remarks\": \"...\", \"correctedFields\": { ... } }` (exact keys per API / controller).\n\n**Auth:** Bearer `{{accessToken}}`.",
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "{{accessToken}}",
+ "type": "string"
+ }
+ ]
+ }
+ }
+ },
+ {
+ "name": "DELETE Document",
+ "description": "Hard-delete the `cpc_documents` row (use only in test / admin workflows).",
+ "request": {
+ "method": "DELETE",
+ "header": [],
+ "url": "{{apiRoot}}/cpc-csd/documents/{{cpcDocumentId}}",
+ "description": "**204 / 200** depending on implementation — confirm in controller. **404** if id unknown.",
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "{{accessToken}}",
+ "type": "string"
+ }
+ ]
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "02c_Excel_reports",
+ "description": "Download **`.xlsx`** reports. Two URL families exist on the mount: full **`/v1/ocr/report/...`** and shorter **`/report/...`** aliases.\n\n**Per-claim:** replace **`{{claimIdCpc}}`** in the path; optional **`reportAttemptQuery`** (e.g. `?attempt=2`).\n\n**Master:** optional filters **`search`**, **`status`**, **`type`** via env **`masterReport*`**.",
+ "item": [
+ {
+ "name": "GET Report Excel per claim",
+ "description": "Primary per-claim export path (matches legacy **`/api/v1/ocr/report/:claimId/download`**).",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": "{{apiRoot}}/cpc-csd/v1/ocr/report/{{claimIdCpc}}/download{{reportAttemptQuery}}",
+ "description": "**Path:** `claimId` = logical claim (e.g. **`{{claimIdCpc}}`**).\n\n**Query:** append **`{{reportAttemptQuery}}`** — empty string or `?attempt=2`.\n\n**200:** Excel binary — save response to file in Postman.",
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "{{accessToken}}",
+ "type": "string"
+ }
+ ]
+ }
+ }
+ },
+ {
+ "name": "GET Report Excel master",
+ "description": "All claims (within permission scope) with optional **`search`**, **`status`**, **`type`** filters.",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": "{{apiRoot}}/cpc-csd/v1/ocr/report/all/download?search={{masterReportSearch}}&status={{masterReportStatus}}&type={{masterReportType}}",
+ "description": "**Query (all optional):** `search`, `status`, `type` — driven by env **`masterReportSearch`**, **`masterReportStatus`**, **`masterReportType`**.",
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "{{accessToken}}",
+ "type": "string"
+ }
+ ]
+ }
+ }
+ },
+ {
+ "name": "GET Report per claim (alt path)",
+ "description": "Shorter alias on the **`cpc-csd`** router (no `/v1/ocr` segment).",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": "{{apiRoot}}/cpc-csd/report/{{claimIdCpc}}/download{{reportAttemptQuery}}",
+ "description": "Same semantics as **GET Report Excel per claim** — different URL shape only.",
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "{{accessToken}}",
+ "type": "string"
+ }
+ ]
+ }
+ }
+ },
+ {
+ "name": "GET Report master (alt path)",
+ "description": "Shorter master export; add query string manually if you need the same filters as the `/v1/ocr/report/all/download` variant.",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": "{{apiRoot}}/cpc-csd/report/all/download",
+ "description": "**Note:** This sample URL has **no** query params; extend in Postman Params tab with `search`, `status`, `type` when needed.",
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "{{accessToken}}",
+ "type": "string"
+ }
+ ]
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "POST Bare file upload (GCS)",
+ "description": "Minimal upload: **no** `claim_id`, **no** validation pipeline — stores bytes to GCS and returns a URL. For full CPC validation use **`POST .../v1/ocr/upload`** in folders **03–07**.",
+ "request": {
+ "method": "POST",
+ "header": [],
+ "body": {
+ "mode": "formdata",
+ "formdata": [
+ {
+ "key": "file",
+ "type": "file",
+ "src": [],
+ "description": "Single multipart part named **`file`** (required)."
+ }
+ ]
+ },
+ "url": "{{hostUrl}}/api/upload",
+ "description": "**Auth:** Bearer `{{accessToken}}`.\n\n**200:** JSON including **`gcsUrl`** (or equivalent) pointing at the uploaded object.\n\n**Note:** Path is **`{{hostUrl}}/api/upload`** — not under **`/cpc-csd`**. Documented in `docs/CPC-CDC.md` as compat surface.",
+ "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..56a1b03
--- /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** (integer, **1-based**). Increment to fetch the next page; reset to `1` when you change `recentSearch`, `recentStatus`, or `recentType`."
+ },
+ {
+ "key": "recentLimit",
+ "value": "15",
+ "type": "default",
+ "enabled": true,
+ "description": "`GET .../documents/recent` — **limit** (page size, number of **document rows** per page). The SPA dashboard offers 10 / 15 / 30 / 50. Larger pages reduce the chance a multi-file CPC batch is split across pages."
+ },
+ {
+ "key": "recentSearch",
+ "value": "",
+ "type": "default",
+ "enabled": true,
+ "description": "Optional **`search`** query: case-insensitive substring on **`booking_id`**, **`claim_id`**, **`document_type`**, and document **`id`** (UUID). Examples: `CPC-114`, `POSTMAN`, part of a UUID. Leave **empty** to list without text filter (matches Dashboard debounced booking search)."
+ },
+ {
+ "key": "recentStatus",
+ "value": "",
+ "type": "default",
+ "enabled": true,
+ "description": "Optional **`status`** filter. **Empty** or omit in URL = all statuses.\n\n| Value | Server behaviour |\n|-------|------------------|\n| *(empty)* | No status filter — “All submissions”. |\n| `SUCCESSFUL` | `MATCH`, `SUCCESSFUL`, `APPROVED`. |\n| `UNSUCCESSFUL` | `MISMATCH`, `REJECTED`, `UNSUCCESSFUL`, `NEED_MANUAL` — use for **“Rejected / mismatch”** tab parity. |\n| `ALL` | Explicit no-op filter. |\n| Any other string | Treated as exact **`validation_status`** value. |\n\nImplementation: `appendCpcDocumentFilters` in `re-workflow-be/src/services/cpc-cdc/utils.ts`."
+ },
+ {
+ "key": "recentType",
+ "value": "",
+ "type": "default",
+ "enabled": true,
+ "description": "Optional **`type`** (document family). **Empty** = all types.\n\nSupported tokens include **`AADHAAR`**, **`CPC_AUTH`**, **`CSD_PO`**, **`RETAIL_INVOICE`**, **`ALL`** — server maps to `document_type` `ILIKE` patterns (see same `appendCpcDocumentFilters`)."
+ },
+ {
+ "key": "recentSortBy",
+ "value": "createdAt",
+ "type": "default",
+ "enabled": true,
+ "description": "`sortBy` query — must be one of: **`id`**, **`bookingId`**, **`createdAt`**, **`documentType`**, **`validationStatus`**, **`claimId`**, **`matchPercentage`**. Invalid values fall back to **`createdAt`** in the controller."
+ },
+ {
+ "key": "recentOrder",
+ "value": "desc",
+ "type": "default",
+ "enabled": true,
+ "description": "`order` query — **`asc`** or **`desc`** (case-insensitive). **`desc`** = newest first (dashboard default)."
+ },
+ {
+ "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-20T12: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/build/assets/conclusionApi-B8GrXg3V.js b/build/assets/conclusionApi-Dwb-WsdL.js
similarity index 81%
rename from build/assets/conclusionApi-B8GrXg3V.js
rename to build/assets/conclusionApi-Dwb-WsdL.js
index 78c42f5..c6906a6 100644
--- a/build/assets/conclusionApi-B8GrXg3V.js
+++ b/build/assets/conclusionApi-Dwb-WsdL.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-r8G8cQlR.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{u.stopPropagation(),d==null||d()};return e.jsxs(Z,{className:"hover:shadow-lg transition-all duration-200 shadow-md cursor-pointer h-full flex flex-col",onClick:c,"data-testid":o,children:[e.jsxs(ce,{className:"flex flex-row items-center justify-between space-y-0 pb-2",children:[e.jsx(de,{className:"text-sm font-medium text-muted-foreground","data-testid":`${o}-title`,children:t}),e.jsxs("div",{className:"flex items-center gap-2",children:[m&&d&&e.jsx("button",{type:"button",onClick:p,className:"p-1.5 rounded-md text-gray-500 hover:text-gray-700 hover:bg-gray-100 transition-colors","data-testid":`${o}-justify-button`,title:"View detailed breakdown of numbers","aria-label":"View detailed breakdown",children:e.jsx(Hr,{className:"h-4 w-4"})}),e.jsx("div",{className:`p-1.5 sm:p-2 rounded-lg ${r}`,"data-testid":`${o}-icon-wrapper`,children:e.jsx(a,{className:`h-4 w-4 sm:h-4 sm:w-4 ${n}`,"data-testid":`${o}-icon`})})]})]}),e.jsxs(J,{className:"flex flex-col flex-1 py-3",children:[e.jsx("div",{className:"text-xl sm:text-2xl font-bold text-gray-900 mb-2","data-testid":`${o}-value`,children:s}),i&&e.jsx("div",{className:"text-xs text-muted-foreground mb-2","data-testid":`${o}-subtitle`,children:i}),l&&e.jsx("div",{className:"flex-1 flex flex-col","data-testid":`${o}-children`,children:l})]})]})}function Ds({label:t,value:s,bgColor:a,textColor:r,testId:n="stat-card",children:i,onClick:l}){return e.jsxs("div",{className:`${a} rounded-lg p-2 sm:p-3 ${l?"cursor-pointer hover:shadow-md transition-shadow":""}`,"data-testid":n,onClick:l,children:[e.jsx("p",{className:"text-xs text-gray-600 mb-1 leading-tight","data-testid":`${n}-label`,children:t}),e.jsx("p",{className:`text-lg sm:text-xl font-bold ${r}`,"data-testid":`${n}-value`,children:s}),i]})}function ia({className:t,value:s,indicatorClassName:a,...r}){return e.jsx(Rb,{"data-slot":"progress",className:Le("bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",t),...r,children:e.jsx(Fb,{"data-slot":"progress-indicator",className:Le("bg-primary h-full w-full flex-1 transition-all",a),style:{transform:`translateX(-${100-(s||0)}%)`}})})}function Zw({kpis:t,priorityDistribution:s,dateRange:a,customStartDate:r,customEndDate:n,onKPIClick:i}){const l=()=>({dateRange:a,startDate:r,endDate:n});return e.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6","data-testid":"admin-kpi-cards",children:[e.jsxs(_r,{title:"Total Requests",value:(t==null?void 0:t.requestVolume.totalRequests)||0,icon:Fe,iconBgColor:"bg-blue-50",iconColor:"text-blue-600",testId:"kpi-total-requests",onClick:()=>i(l()),children:[e.jsxs("div",{className:"grid grid-cols-2 gap-2 mb-2",children:[e.jsx(Ds,{label:"Approved",value:(t==null?void 0:t.requestVolume.approvedRequests)||0,bgColor:"bg-green-50",textColor:"text-green-600",testId:"stat-approved",onClick:o=>{o.stopPropagation(),i({...l(),status:"approved"})}}),e.jsx(Ds,{label:"Rejected",value:(t==null?void 0:t.requestVolume.rejectedRequests)||0,bgColor:"bg-red-50",textColor:"text-red-600",testId:"stat-rejected",onClick:o=>{o.stopPropagation(),i({...l(),status:"rejected"})}})]}),e.jsxs("div",{className:"grid grid-cols-2 gap-2 mb-2",children:[e.jsx(Ds,{label:"Pending",value:(t==null?void 0:t.requestVolume.openRequests)||0,bgColor:"bg-orange-50",textColor:"text-orange-600",testId:"stat-pending",onClick:o=>{o.stopPropagation(),i({...l(),status:"pending"})}}),e.jsx(Ds,{label:"Closed",value:(t==null?void 0:t.requestVolume.closedRequests)||0,bgColor:"bg-gray-50",textColor:"text-gray-600",testId:"stat-closed",onClick:o=>{o.stopPropagation(),i({...l(),status:"closed"})}})]}),(t==null?void 0:t.requestVolume.pausedRequests)!==void 0&&e.jsx("div",{className:"grid grid-cols-2 gap-2",children:e.jsx(Ds,{label:"Paused",value:t.requestVolume.pausedRequests||0,bgColor:"bg-orange-100",textColor:"text-orange-700",testId:"stat-paused",onClick:o=>{o.stopPropagation(),i({...l(),status:"paused"})}})})]}),e.jsx(_r,{title:"SLA Compliance",value:`${(t==null?void 0:t.tatEfficiency.avgTATCompliance)||0}%`,icon:ts,iconBgColor:"bg-green-50",iconColor:"text-green-600",testId:"kpi-sla-compliance",onClick:()=>i(l()),children:e.jsxs("div",{className:"flex flex-col flex-1",children:[e.jsx(ia,{value:(t==null?void 0:t.tatEfficiency.avgTATCompliance)||0,className:"h-2 mb-2","data-testid":"sla-progress-bar"}),e.jsxs("div",{className:"grid grid-cols-2 gap-2 mt-auto",children:[e.jsx(Ds,{label:"Compliant",value:(t==null?void 0:t.tatEfficiency.compliantWorkflows)||0,bgColor:"bg-green-50",textColor:"text-green-600",testId:"stat-compliant",onClick:o=>{o.stopPropagation(),i({...l(),slaCompliance:"compliant"})}}),e.jsx(Ds,{label:"Breached",value:(t==null?void 0:t.tatEfficiency.delayedWorkflows)||0,bgColor:"bg-red-50",textColor:"text-red-600",testId:"stat-breached",onClick:o=>{o.stopPropagation(),i({...l(),slaCompliance:"breached"})}})]})]})}),e.jsx(_r,{title:"Avg Cycle Time",value:t!=null&&t.tatEfficiency.avgCycleTimeHours?Xt(t.tatEfficiency.avgCycleTimeHours):"0 hours",icon:it,iconBgColor:"bg-purple-50",iconColor:"text-purple-600",subtitle:`≈ ${(t==null?void 0:t.tatEfficiency.avgCycleTimeDays.toFixed(1))||0} working days`,testId:"kpi-avg-cycle-time",onClick:()=>i(l()),children:e.jsx("div",{className:"flex flex-col flex-1",children:e.jsxs("div",{className:"grid grid-cols-2 gap-2 mt-auto",children:[e.jsx(Ds,{label:"Express",value:(()=>{const o=s.find(d=>d.priority==="express"),c=o?Number(o.avgCycleTimeHours):0;return c>0?Xt(c):"N/A"})(),bgColor:"bg-orange-50",textColor:"text-orange-600",testId:"stat-express-time",onClick:o=>{o.stopPropagation(),i({...l(),priority:"express"})}}),e.jsx(Ds,{label:"Standard",value:(()=>{const o=s.find(d=>d.priority==="standard"),c=o?Number(o.avgCycleTimeHours):0;return c>0?Xt(c):"N/A"})(),bgColor:"bg-blue-50",textColor:"text-blue-600",testId:"stat-standard-time",onClick:o=>{o.stopPropagation(),i({...l(),priority:"standard"})}})]})})})]})}function Jw({kpis:t,criticalRequests:s,dateRange:a,customStartDate:r,customEndDate:n,onKPIClick:i,onNavigate:l,userId:o,userDisplayName:c}){const d=()=>({dateRange:a,startDate:r,endDate:n}),m=()=>{if(!o)return;const u=new URLSearchParams;u.set("approverId",o),u.set("approverName",c||"My Performance"),u.set("dateRange",a),a==="custom"&&r&&n&&(u.set("startDate",r.toISOString()),u.set("endDate",n.toISOString())),l==null||l(`/approver-performance?${u.toString()}`)},p=t&&t.requestVolume.totalRequests>0?t.requestVolume.approvedRequests/t.requestVolume.totalRequests*100:0;return e.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 sm:gap-4 items-stretch","data-testid":"user-kpi-cards",children:[e.jsx(_r,{title:"My Requests (Submitted)",value:(t==null?void 0:t.requestVolume.totalRequests)||0,icon:Fe,iconBgColor:"bg-blue-50",iconColor:"text-blue-600",testId:"kpi-my-requests",onClick:()=>i(d()),children:e.jsxs("div",{className:"grid grid-cols-3 gap-1.5 sm:gap-2",children:[e.jsx(Ds,{label:"Approved",value:(t==null?void 0:t.requestVolume.approvedRequests)||0,bgColor:"bg-green-50",textColor:"text-green-600",testId:"stat-user-approved",onClick:u=>{u.stopPropagation(),i({...d(),status:"approved"})}}),e.jsx(Ds,{label:"Pending",value:(t==null?void 0:t.requestVolume.openRequests)||0,bgColor:"bg-orange-50",textColor:"text-orange-600",testId:"stat-user-pending",onClick:u=>{u.stopPropagation(),i({...d(),status:"pending"})}}),e.jsx(Ds,{label:"Paused",value:(t==null?void 0:t.requestVolume.pausedRequests)||0,bgColor:"bg-amber-50",textColor:"text-amber-600",testId:"stat-user-paused",onClick:u=>{u.stopPropagation(),i({...d(),status:"paused"})}}),e.jsx(Ds,{label:"Rejected",value:(t==null?void 0:t.requestVolume.rejectedRequests)||0,bgColor:"bg-red-50",textColor:"text-red-600",testId:"stat-user-rejected",onClick:u=>{u.stopPropagation(),i({...d(),status:"rejected"})}}),e.jsx(Ds,{label:"Closed",value:(t==null?void 0:t.requestVolume.closedRequests)||0,bgColor:"bg-blue-50",textColor:"text-blue-600",testId:"stat-user-closed",onClick:u=>{u.stopPropagation(),i({...d(),status:"closed"})}})]})}),e.jsx(_r,{title:"Awaiting My Approval",value:(t==null?void 0:t.approverLoad.pendingActions)||0,icon:it,iconBgColor:"bg-orange-50",iconColor:"text-orange-600",subtitle:"at current level",testId:"kpi-pending-actions",onClick:()=>i({...d(),targetPage:"open-requests",status:"pending"}),onJustifyClick:m,showJustifyButton:!!o,children:e.jsxs("div",{className:"grid grid-cols-2 gap-2 mt-auto",children:[e.jsx(Ds,{label:"Approved Today",value:(t==null?void 0:t.approverLoad.completedToday)||0,bgColor:"bg-blue-50",textColor:"text-blue-600",testId:"stat-today",onClick:u=>{u.stopPropagation(),i({...d(),targetPage:"open-requests",status:"pending"})}}),e.jsx(Ds,{label:"This Week",value:(t==null?void 0:t.approverLoad.completedThisWeek)||0,bgColor:"bg-green-50",textColor:"text-green-600",testId:"stat-week",onClick:u=>{u.stopPropagation(),i({...d(),targetPage:"open-requests",status:"pending"})}})]})}),e.jsx(_r,{title:"Critical Alerts",value:s.length,icon:Ia,iconBgColor:"bg-red-50",iconColor:"text-red-600",testId:"kpi-user-critical",onClick:()=>i({...d(),targetPage:"open-requests"}),onJustifyClick:m,showJustifyButton:!!o,children:e.jsxs("div",{className:"grid grid-cols-2 gap-2 mt-auto",children:[e.jsx(Ds,{label:"Breached",value:s.filter(u=>u.breachCount>0).length,bgColor:"bg-orange-50",textColor:"text-red-600",testId:"stat-user-breached",onClick:u=>{u.stopPropagation(),i({...d(),targetPage:"open-requests"})}}),e.jsx(Ds,{label:"Warning",value:s.filter(u=>u.breachCount===0).length,bgColor:"bg-yellow-50",textColor:"text-orange-600",testId:"stat-user-warning",onClick:u=>{u.stopPropagation(),i({...d(),targetPage:"open-requests"})}})]})}),e.jsx(_r,{title:"Success Rate",value:`${p.toFixed(0)}%`,icon:Be,iconBgColor:"bg-green-50",iconColor:"text-green-600",subtitle:`of ${(t==null?void 0:t.requestVolume.totalRequests)||0} requests approved`,testId:"kpi-success-rate",children:e.jsxs("div",{className:"space-y-4 mt-3 flex flex-col flex-1",children:[e.jsxs("div",{className:"space-y-3",children:[e.jsx(ia,{value:p,className:"h-4 bg-gray-200 [&>div]:bg-green-600","data-testid":"success-rate-progress"}),e.jsxs("div",{className:"flex justify-between items-center text-xs",children:[e.jsx("span",{className:"text-muted-foreground",children:"Rejected"}),e.jsx("span",{className:"font-semibold text-red-600",children:(t==null?void 0:t.requestVolume.rejectedRequests)||0})]})]}),e.jsxs("div",{className:"grid grid-cols-2 gap-2 mt-auto",children:[e.jsxs("div",{className:"bg-green-50 rounded-lg p-2 text-center",children:[e.jsx("div",{className:"text-xs text-muted-foreground mb-1",children:"Approved"}),e.jsx("div",{className:"text-lg font-semibold text-green-600",children:(t==null?void 0:t.requestVolume.approvedRequests)||0})]}),e.jsxs("div",{className:"bg-red-50 rounded-lg p-2 text-center",children:[e.jsx("div",{className:"text-xs text-muted-foreground mb-1",children:"Rejected"}),e.jsx("div",{className:"text-lg font-semibold text-red-600",children:(t==null?void 0:t.requestVolume.rejectedRequests)||0})]})]})]})})]})}const qw=t=>{if(!t.originalTATHours||t.originalTATHours===0)return 0;const s=t.originalTATHours,a=t.totalTATHours;if(a<=0)return 100;const n=(s-a)/s*100;return Math.min(100,Math.max(0,Math.round(n)))},e1=t=>{if(t.totalTATHours===void 0||t.totalTATHours===null)return"N/A";const s=t.totalTATHours;if(s<=0){const a=Math.abs(s);return a<1?"Breached":a<24?`${Math.round(a)}h overdue`:`${Math.round(a/24)}d overdue`}return s<1?`${Math.round(s*60)}min left`:s<24?`${Math.round(s)}h left`:`${Math.round(s/24)}d left`};function t1({alert:t,onNavigate:s,testId:a="critical-alert-card"}){const r=qw(t);return e.jsxs("div",{className:"p-3 sm:p-4 bg-red-50 rounded-lg sm:rounded-xl border border-red-100 hover:shadow-md transition-all duration-200 cursor-pointer",onClick:()=>s==null?void 0:s(t.requestNumber),"data-testid":`${a}-${t.requestId}`,children:[e.jsxs("div",{className:"flex items-start justify-between gap-2 mb-2 sm:mb-3",children:[e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsxs("div",{className:"flex items-center gap-1 sm:gap-2 mb-1 flex-wrap",children:[e.jsx("p",{className:"font-semibold text-xs sm:text-sm text-gray-900","data-testid":`${a}-request-number`,children:t.requestNumber}),t.priority==="express"&&e.jsx(fj,{className:"h-3 w-3 text-red-500 flex-shrink-0","data-testid":`${a}-priority-icon`}),t.breachCount>0&&e.jsx(re,{variant:"destructive",className:"text-xs","data-testid":`${a}-breach-count`,children:t.breachCount})]}),e.jsx("p",{className:"text-xs sm:text-sm text-gray-700 line-clamp-2","data-testid":`${a}-title`,children:t.title})]}),e.jsx(re,{variant:"outline",className:"text-xs bg-white border-red-200 text-red-700 font-medium whitespace-nowrap","data-testid":`${a}-remaining-time`,children:e1(t)})]}),e.jsxs("div",{className:"space-y-1 sm:space-y-2",children:[e.jsxs("div",{className:"flex justify-between text-xs text-gray-600",children:[e.jsx("span",{children:"TAT Used"}),e.jsxs("span",{className:"font-medium","data-testid":`${a}-progress-percentage`,children:[r,"%"]})]}),e.jsx(ia,{value:r,className:`h-1.5 sm:h-2 ${r>=80?"[&>div]:bg-red-600":r>=50?"[&>div]:bg-orange-500":"[&>div]:bg-green-600"}`,"data-testid":`${a}-progress-bar`})]})]})}function s1(t){return t.filter(s=>{const a=(s.breachCount||0)>0,r=s.isCritical===!0,n=s.status;return n?(a||r)&&(n==="pending"||n==="in-progress"||n==="PENDING"||n==="IN_PROGRESS"):a||r})}function a1(t){return t.filter(s=>{const a=Number(s.tatPercentageUsed)||0;return(Number(s.remainingHours)||0)>0&&a<100})}function r1(t){if(t<=0)return"Just breached";if(t<1)return`${Math.round(t*60)} min`;const s=8;if(t0?`${n}h ${i}m`:`${n}h`}const a=Math.floor(t/s),r=t%s;if(r>0){const n=Math.floor(r),i=Math.round((r-n)*60);return i>0?`${a}d ${n}h ${i}m`:`${a}d ${n}h`}return`${a}d`}function n1(t){return t>=95?"bg-green-100 text-green-700":t>=90?"bg-blue-100 text-blue-700":t>=85?"bg-orange-100 text-orange-700":"bg-red-100 text-red-700"}function ng(t,s,a=3){const r=[];let n=Math.max(1,t-Math.floor(a/2)),i=Math.min(s,n+a-1);i-n0?"animate-pulse":""}`,"data-testid":"critical-count-badge",children:a.totalRecords})]})}),e.jsx(J,{className:`overflow-y-auto flex-1 p-4 ${a.totalPages>1?"max-h-[calc(100%-140px)]":"max-h-[calc(100%-80px)]"}`,children:e.jsx("div",{className:"space-y-3 sm:space-y-4",children:s.length===0?e.jsxs("div",{className:"text-center py-6 sm:py-8 text-muted-foreground","data-testid":"no-critical-alerts",children:[e.jsx(Be,{className:"w-10 h-10 sm:w-12 sm:h-12 mx-auto mb-2 text-green-500"}),e.jsx("p",{className:"text-sm",children:"No critical alerts"}),e.jsx("p",{className:"text-xs",children:"All requests are within TAT"})]}):s.map(i=>e.jsx(t1,{alert:i,onNavigate:l=>n==null?void 0:n(`request/${l}`),testId:"dashboard-critical-alert"},i.requestId))})}),a.totalPages>1&&s.length>0&&e.jsx("div",{className:"border-t bg-gray-50 px-4 py-2 flex-shrink-0",children:e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsxs("div",{className:"text-xs text-muted-foreground text-center",children:["Page ",a.page," of ",a.totalPages," (",a.totalRecords," total)"]}),e.jsxs("div",{className:"flex items-center justify-center gap-1 flex-wrap",children:[e.jsx(E,{variant:"outline",size:"sm",onClick:()=>r(a.page-1),disabled:a.page===1,className:"h-7 w-7 p-0","data-testid":"critical-pagination-prev",children:e.jsx(js,{className:"h-3 w-3 rotate-180"})}),ng(a.page,a.totalPages,3).map(i=>e.jsx(E,{variant:i===a.page?"default":"outline",size:"sm",onClick:()=>r(i),className:`h-7 w-7 p-0 text-xs ${i===a.page?"bg-red-600 text-white hover:bg-red-700":""}`,"data-testid":`critical-pagination-page-${i}`,children:i},i)),e.jsx(E,{variant:"outline",size:"sm",onClick:()=>r(a.page+1),disabled:a.page===a.totalPages,className:"h-7 w-7 p-0","data-testid":"critical-pagination-next",children:e.jsx(js,{className:"h-3 w-3"})})]})]})})]})}const l1=t=>{switch(t.toLowerCase()){case"express":return"bg-orange-100 text-orange-800 border-orange-200";case"standard":return"bg-blue-100 text-blue-800 border-blue-200";case"high":return"bg-red-100 text-red-800 border-red-200";case"medium":return"bg-orange-100 text-orange-800 border-orange-200";case"low":return"bg-green-100 text-green-800 border-green-200";default:return"bg-gray-100 text-gray-800 border-gray-200"}},o1=t=>{const s=new Date,a=new Date(t),r=mj(s,a);if(r<1)return"just now";if(r<60)return`${r} minute${r>1?"s":""} ago`;const n=uj(s,a);if(n<24)return`${n} hour${n>1?"s":""} ago`;const i=xj(s,a);return`${i} day${i>1?"s":""} ago`},c1=t=>{if(!t)return t;let s=t.replace(/\s*\([^)]*@[^)]*\)/g,"");return s=s.replace(/\s+by\s+.+$/i,""),s=s.replace(/has been added as approver/gi,"added as approver"),s=s.replace(/has been added as spectator/gi,"added as spectator"),s=s.replace(/has been/gi,""),s=s.replace(/with TAT of (\d+) hours?/gi,"(TAT: $1h)"),s=s.replace(/with TAT of (\d+) days?/gi,"(TAT: $1d)"),s=s.replace(/\s+/g," "),s.trim()},d1=t=>{const s=t.toLowerCase();return s.includes("approv")?e.jsx(Be,{className:"w-2.5 h-2.5 sm:w-3 sm:h-3 text-green-600"}):s.includes("reject")?e.jsx(ss,{className:"w-2.5 h-2.5 sm:w-3 sm:h-3 text-red-600"}):s.includes("comment")?e.jsx(Ys,{className:"w-2.5 h-2.5 sm:w-3 sm:h-3 text-blue-600"}):s.includes("escalat")?e.jsx(Ia,{className:"w-2.5 h-2.5 sm:w-3 sm:h-3 text-orange-600"}):s.includes("submit")?e.jsx(Fe,{className:"w-2.5 h-2.5 sm:w-3 sm:h-3 text-purple-600"}):s.includes("document")?e.jsx(xl,{className:"w-2.5 h-2.5 sm:w-3 sm:h-3 text-indigo-600"}):e.jsx(Bt,{className:"w-2.5 h-2.5 sm:w-3 sm:h-3 text-gray-600"})};function m1({activity:t,currentUserId:s,currentUserDisplayName:a,currentUserEmail:r,onNavigate:n,testId:i="activity-feed-item"}){const l=t.userId===s,o=l?"You":t.userName||"System",c=l?(a||r||"ME").split(" ").map(d=>d[0]).join("").toUpperCase().substring(0,2):t.userName?t.userName.split(" ").map(d=>d[0]).join("").toUpperCase().substring(0,2):"SY";return e.jsxs("div",{className:"flex items-start gap-2 sm:gap-3 p-3 sm:p-4 rounded-lg hover:bg-gray-50 transition-all duration-200 cursor-pointer border border-gray-100 hover:border-gray-300",onClick:()=>n==null?void 0:n(t.requestNumber),"data-testid":`${i}-${t.activityId}`,children:[e.jsxs("div",{className:"relative flex-shrink-0 mt-0.5",children:[e.jsxs(cs,{className:`h-8 w-8 sm:h-10 sm:w-10 ring-2 shadow-sm ${l?"ring-blue-200":"ring-white"}`,"data-testid":`${i}-avatar`,children:[e.jsx(Tl,{src:""}),e.jsx(ds,{className:`text-white font-semibold text-xs sm:text-sm ${l?"bg-blue-600":"bg-slate-700"}`,children:c})]}),e.jsx("div",{className:"absolute -bottom-0.5 -right-0.5 sm:-bottom-1 sm:-right-1 w-4 h-4 sm:w-5 sm:h-5 bg-white rounded-full flex items-center justify-center shadow-sm border border-gray-200",children:d1(t.action)})]}),e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx("div",{className:"flex items-center justify-between gap-2 mb-1",children:e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"font-semibold text-xs sm:text-sm text-gray-900","data-testid":`${i}-request-number`,children:t.requestNumber}),e.jsx(re,{variant:"outline",className:`text-xs ${l1(t.priority)} font-medium flex-shrink-0`,"data-testid":`${i}-priority`,children:t.priority})]})}),e.jsx("p",{className:"text-xs text-muted-foreground mb-1 line-clamp-2",children:e.jsx("span",{className:`font-medium ${t.action.toLowerCase().includes("approv")?"text-green-600":t.action.toLowerCase().includes("reject")?"text-red-600":t.action.toLowerCase().includes("submit")?"text-blue-600":t.action.toLowerCase().includes("add")?"text-indigo-600":"text-gray-700"}`,"data-testid":`${i}-action`,children:c1(t.action)})}),e.jsx("p",{className:"text-xs sm:text-sm text-gray-700 line-clamp-1 mb-1","data-testid":`${i}-request-title`,children:t.requestTitle}),e.jsxs("div",{className:"flex items-center gap-1.5 text-xs text-muted-foreground",children:[e.jsx("span",{className:`font-medium truncate max-w-[150px] sm:max-w-[200px] ${l?"text-blue-600":"text-gray-900"}`,"data-testid":`${i}-user-name`,children:o}),e.jsx("span",{children:"•"}),e.jsx("span",{className:"whitespace-nowrap","data-testid":`${i}-timestamp`,children:o1(t.timestamp)})]})]}),e.jsx(js,{className:"h-4 w-4 text-gray-400 hover:text-blue-600 transition-colors flex-shrink-0 hidden sm:block mt-1","data-testid":`${i}-arrow`})]})}function u1({isAdmin:t,recentActivity:s,pagination:a,refreshing:r,onPageChange:n,onRefresh:i,onNavigate:l,currentUserId:o,currentUserDisplayName:c,currentUserEmail:d}){return e.jsxs(Z,{className:"lg:col-span-1 shadow-md hover:shadow-lg transition-shadow flex flex-col overflow-hidden h-full","data-testid":"recent-activity-section",children:[e.jsx(ce,{className:"pb-3 sm:pb-4 flex-shrink-0",children:e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsxs("div",{className:"flex items-center gap-2 sm:gap-3 flex-1 min-w-0",children:[e.jsx("div",{className:"p-2 sm:p-3 bg-blue-100 rounded-lg",children:e.jsx(Bt,{className:"h-4 w-4 sm:h-5 sm:w-5 text-blue-600"})}),e.jsxs("div",{className:"min-w-0",children:[e.jsx(de,{className:"text-base sm:text-lg text-gray-900",children:"Recent Activity"}),e.jsx(we,{className:"text-xs sm:text-sm text-gray-600 truncate",children:t?"All workflow updates":"My workflow updates"})]})]}),e.jsxs(E,{variant:"ghost",size:"sm",className:"text-blue-600 hover:bg-blue-50 font-medium flex-shrink-0 h-8 sm:h-9 px-2 sm:px-3 sm:min-w-[100px]",onClick:i,disabled:r,"data-testid":"activity-refresh-button",children:[e.jsx(Et,{className:`w-3 h-3 sm:w-4 sm:h-4 ${r?"animate-spin":""}`}),e.jsx("span",{className:"hidden sm:inline ml-2 sm:w-[60px] sm:text-center",children:r?"Refreshing...":"Refresh"})]})]})}),e.jsx(J,{className:`overflow-y-auto flex-1 p-4 ${a.totalPages>1?"max-h-[calc(100%-140px)]":"max-h-[calc(100%-80px)]"}`,children:e.jsx("div",{className:"space-y-2 sm:space-y-3",children:s.length===0?e.jsxs("div",{className:"text-center py-6 sm:py-8 text-muted-foreground","data-testid":"no-recent-activity",children:[e.jsx(Bt,{className:"w-10 h-10 sm:w-12 sm:h-12 mx-auto mb-2 text-gray-400"}),e.jsx("p",{className:"text-sm",children:"No recent activity"}),e.jsx("p",{className:"text-xs",children:"Activity will appear here once requests are processed"})]}):s.map(m=>e.jsx(m1,{activity:m,currentUserId:o,currentUserDisplayName:c,currentUserEmail:d,onNavigate:p=>l==null?void 0:l(`request/${p}`),testId:"dashboard-activity"},m.activityId))})}),a.totalPages>1&&s.length>0&&e.jsx("div",{className:"border-t bg-gray-50 px-4 py-2 flex-shrink-0",children:e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsxs("div",{className:"text-xs text-muted-foreground text-center",children:["Page ",a.page," of ",a.totalPages," (",a.totalRecords," total)"]}),e.jsxs("div",{className:"flex items-center justify-center gap-1 flex-wrap",children:[e.jsx(E,{variant:"outline",size:"sm",onClick:()=>n(a.page-1),disabled:a.page===1,className:"h-7 w-7 p-0","data-testid":"activity-pagination-prev",children:e.jsx(js,{className:"h-3 w-3 rotate-180"})}),ng(a.page,a.totalPages,3).map(m=>e.jsx(E,{variant:m===a.page?"default":"outline",size:"sm",onClick:()=>n(m),className:`h-7 w-7 p-0 text-xs ${m===a.page?"bg-re-green text-white hover:bg-re-green/90":""}`,"data-testid":`activity-pagination-page-${m}`,children:m},m)),e.jsx(E,{variant:"outline",size:"sm",onClick:()=>n(a.page+1),disabled:a.page===a.totalPages,className:"h-7 w-7 p-0","data-testid":"activity-pagination-next",children:e.jsx(js,{className:"h-3 w-3"})})]})]})})]})}function x1({kpis:t,upcomingDeadlines:s,criticalRequests:a,departmentStats:r,dateRange:n,customStartDate:i,customEndDate:l,exportingDeptStats:o,onKPIClick:c,onExportDepartmentStats:d}){if(!t)return null;const m=()=>({dateRange:n,startDate:i,endDate:l});return e.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-3 gap-4 sm:gap-6","data-testid":"admin-analytics-section",children:[e.jsxs("div",{className:"flex flex-col gap-4 sm:gap-6 lg:col-span-1",children:[e.jsxs(Z,{className:"shadow-md hover:shadow-lg transition-shadow flex-1","data-testid":"active-levels-card",children:[e.jsx(ce,{className:"pb-3",children:e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("div",{className:"p-2 bg-blue-100 rounded-lg",children:e.jsx(Bt,{className:"h-4 w-4 text-blue-600"})}),e.jsx(de,{className:"text-sm",children:"Active Levels"})]})}),e.jsx(J,{children:e.jsxs("div",{className:"space-y-3",children:[e.jsxs("div",{className:"flex items-baseline gap-2",children:[e.jsx("span",{className:"text-2xl sm:text-3xl font-bold text-blue-600",children:s.length}),e.jsx("span",{className:"text-xs text-muted-foreground",children:"levels"})]}),e.jsx(Jt,{}),e.jsxs("div",{className:"space-y-1 text-xs",children:[e.jsxs("div",{className:"flex justify-between items-center",children:[e.jsx("span",{className:"text-muted-foreground",children:"Avg Time/Level"}),e.jsxs("span",{className:"font-semibold",children:[s.length>0?(t.tatEfficiency.avgCycleTimeHours/s.length).toFixed(1):"0","h"]})]}),e.jsxs("div",{className:"flex justify-between items-center",children:[e.jsx("span",{className:"text-muted-foreground",children:"At Risk"}),e.jsx("span",{className:"font-semibold text-red-600",children:a.filter(p=>p.breachCount>0).length})]})]})]})})]}),e.jsxs(Z,{className:"shadow-md hover:shadow-lg transition-shadow flex-1","data-testid":"collaboration-card",children:[e.jsx(ce,{className:"pb-3",children:e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("div",{className:"p-2 bg-indigo-100 rounded-lg",children:e.jsx(Ys,{className:"h-4 w-4 text-indigo-600"})}),e.jsx(de,{className:"text-sm",children:"Collaboration"})]})}),e.jsx(J,{children:e.jsxs("div",{className:"space-y-3",children:[e.jsxs("div",{className:"grid grid-cols-2 gap-2",children:[e.jsxs("div",{className:"text-center p-2 bg-indigo-50 rounded",children:[e.jsx("p",{className:"text-xs text-muted-foreground",children:"Notes"}),e.jsx("p",{className:"text-lg sm:text-xl font-bold text-indigo-600",children:t.engagement.workNotesAdded})]}),e.jsxs("div",{className:"text-center p-2 bg-purple-50 rounded",children:[e.jsx("p",{className:"text-xs text-muted-foreground",children:"Files"}),e.jsx("p",{className:"text-lg sm:text-xl font-bold text-purple-600",children:t.engagement.attachmentsUploaded})]})]}),e.jsx(Jt,{}),e.jsxs("div",{className:"text-xs text-muted-foreground text-center",children:[t.requestVolume.totalRequests>0?(t.engagement.workNotesAdded/t.requestVolume.totalRequests).toFixed(1):"0"," ","avg notes per request"]})]})})]})]}),r.length>0&&e.jsxs(Z,{className:"shadow-md hover:shadow-lg transition-shadow lg:col-span-2","data-testid":"department-stats-card",children:[e.jsx(ce,{children:e.jsxs("div",{className:"flex flex-col sm:flex-row sm:items-center justify-between gap-3",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("div",{className:"bg-blue-50 p-2 sm:p-3 rounded-lg",children:e.jsx(_o,{className:"h-5 w-5 sm:h-6 sm:w-6 text-blue-600"})}),e.jsxs("div",{children:[e.jsx(de,{className:"text-base sm:text-lg lg:text-xl",children:"Department-wise Workflow Summary"}),e.jsx(we,{className:"text-xs sm:text-sm",children:"Workflow distribution across departments"})]})]}),e.jsxs(E,{variant:"outline",size:"sm",className:"gap-2 self-start sm:self-auto",onClick:()=>d(n,i,l),disabled:o,"data-testid":"export-dept-stats-button",children:[e.jsx(Tt,{className:`w-3 h-3 sm:w-4 sm:h-4 ${o?"animate-pulse":""}`}),e.jsx("span",{className:"text-xs sm:text-sm",children:o?"Exporting...":"Export"})]})]})}),e.jsx(J,{children:e.jsx(dn,{width:"100%",height:400,children:e.jsxs(Io,{data:r,children:[e.jsx(Yi,{strokeDasharray:"3 3",stroke:"#f0f0f0"}),e.jsx(Qi,{dataKey:"department",stroke:"#999",tick:p=>{const{x:u,y:h,payload:g}=p;return e.jsx("g",{transform:`translate(${u},${h})`,children:e.jsx("text",{x:0,y:0,dy:16,textAnchor:"middle",fill:"#999",fontSize:11,className:"cursor-pointer hover:text-blue-600 hover:underline",onClick:()=>{c({...m(),department:g.value})},children:g.value})})}}),e.jsx(Ki,{stroke:"#999",tick:{fontSize:11}}),e.jsx(mn,{contentStyle:{backgroundColor:"#ffffff",border:"1px solid #e5e7eb",borderRadius:"6px",fontSize:"12px"}}),e.jsx(Xi,{verticalAlign:"bottom",height:36,iconType:"square",wrapperStyle:{fontSize:"12px",paddingTop:"10px"}}),e.jsx(Ea,{dataKey:"approved",fill:"#10b981",name:"Approved"}),e.jsx(Ea,{dataKey:"inProgress",fill:"#f59e0b",name:"Pending"}),e.jsx(Ea,{dataKey:"rejected",fill:"#ef4444",name:"Rejected"})]})})})]})]})}function h1({kpis:t}){return t?e.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6","data-testid":"user-metrics-section",children:[e.jsxs(Z,{className:"shadow-md hover:shadow-lg transition-shadow","data-testid":"user-activity-card",children:[e.jsx(ce,{className:"pb-3",children:e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("div",{className:"p-2 bg-indigo-100 rounded-lg",children:e.jsx(Ys,{className:"h-4 w-4 text-indigo-600"})}),e.jsx(de,{className:"text-sm",children:"My Activity"})]})}),e.jsx(J,{children:e.jsx("div",{className:"space-y-3",children:e.jsxs("div",{className:"grid grid-cols-2 gap-2",children:[e.jsxs("div",{className:"text-center p-3 bg-indigo-50 rounded",children:[e.jsx("p",{className:"text-xs text-muted-foreground mb-1",children:"Work Notes"}),e.jsx("p",{className:"text-xl sm:text-2xl font-bold text-indigo-600",children:t.engagement.workNotesAdded})]}),e.jsxs("div",{className:"text-center p-3 bg-purple-50 rounded",children:[e.jsx("p",{className:"text-xs text-muted-foreground mb-1",children:"Attachments"}),e.jsx("p",{className:"text-xl sm:text-2xl font-bold text-purple-600",children:t.engagement.attachmentsUploaded})]})]})})})]}),e.jsxs(Z,{className:"shadow-md hover:shadow-lg transition-shadow","data-testid":"user-response-time-card",children:[e.jsx(ce,{className:"pb-3",children:e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("div",{className:"p-2 bg-purple-100 rounded-lg",children:e.jsx(it,{className:"h-4 w-4 text-purple-600"})}),e.jsx(de,{className:"text-sm",children:"Avg Response Time"})]})}),e.jsx(J,{children:e.jsxs("div",{className:"space-y-3",children:[e.jsx("div",{className:"flex items-baseline gap-2",children:e.jsx("span",{className:"text-2xl sm:text-3xl font-bold text-purple-600",children:Xt(t.tatEfficiency.avgCycleTimeHours)})}),e.jsx(Jt,{}),e.jsxs("div",{className:"text-xs text-muted-foreground text-center",children:["≈ ",t.tatEfficiency.avgCycleTimeDays.toFixed(1)," working days"]})]})})]})]}):null}function p1({priorityDistribution:t,onNavigate:s}){return t.length===0?null:e.jsxs(Z,{className:"shadow-md hover:shadow-lg transition-shadow","data-testid":"priority-distribution-report",children:[e.jsx(ce,{className:"pb-3",children:e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("div",{className:"bg-green-50 p-2 sm:p-3 rounded-lg",children:e.jsx(ts,{className:"h-5 w-5 sm:h-6 sm:w-6 text-green-600"})}),e.jsxs("div",{children:[e.jsx(de,{className:"text-base sm:text-lg lg:text-xl",children:"Priority Distribution Report"}),e.jsx(we,{className:"text-xs sm:text-sm",children:"Express vs Standard workflow analysis"})]})]})}),e.jsxs(J,{children:[e.jsx("div",{className:"grid grid-cols-2 gap-4 mb-6",children:t.map((a,r)=>{const n=Number(a.avgCycleTimeHours)||0,i=a.priority==="express",l=i?"bg-red-50":"bg-blue-50",o=i?"bg-red-500":"bg-blue-500";return e.jsxs("div",{className:`${l} p-4 rounded-lg`,"data-testid":`priority-card-${a.priority}`,children:[e.jsxs("div",{className:"flex items-center gap-2 mb-2",children:[e.jsx("div",{className:`w-3 h-3 ${o} rounded-full`}),e.jsx("span",{className:"text-sm text-gray-600 capitalize",children:a.priority})]}),e.jsx("div",{className:"text-2xl sm:text-3xl font-bold text-gray-900 mb-1",children:a.totalCount}),e.jsxs("div",{className:"text-xs text-gray-500",children:["Avg: ",Xt(n)," cycle"]})]},r)})}),e.jsx("div",{className:"flex justify-center",children:e.jsx(dn,{width:"100%",height:300,children:e.jsxs(Hh,{children:[e.jsx(Gh,{data:t.map(a=>({name:a.priority.charAt(0).toUpperCase()+a.priority.slice(1),value:a.totalCount,priority:a.priority,percentage:Math.round(a.totalCount/t.reduce((r,n)=>r+n.totalCount,0)*100)})),cx:"50%",cy:"50%",labelLine:!0,label:({cx:a,cy:r,midAngle:n,outerRadius:i,name:l,percentage:o})=>{const c=Math.PI/180,d=i+35,m=a+d*Math.cos(-n*c),p=r+d*Math.sin(-n*c);return e.jsx("text",{x:m,y:p,fill:"#1f2937",textAnchor:m>a?"start":"end",dominantBaseline:"central",className:"text-sm font-semibold pointer-events-none",children:`${l}: ${o}%`})},outerRadius:90,fill:"#8884d8",dataKey:"value",onClick:a=>{a&&a.priority&&s&&s(`requests?priority=${a.priority}`)},className:"cursor-pointer",children:t.map((a,r)=>e.jsx(Wh,{fill:a.priority==="express"?"#ef4444":"#3b82f6",className:"cursor-pointer"},`cell-${r}`))}),e.jsx(mn,{contentStyle:{backgroundColor:"#ffffff",border:"1px solid #e5e7eb",borderRadius:"6px",fontSize:"12px"},formatter:(a,r,n)=>{var l;const i=((l=n.payload)==null?void 0:l.priority)||"";return[`${a} requests`,`Click for ${i}`]}})]})})})]})]})}function Wr({currentPage:t,totalPages:s,itemsPerPage:a,totalRecords:r,onPageChange:n,loading:i=!1,itemLabel:l="items",testIdPrefix:o="pagination"}){const c=()=>{const p=[];let h=Math.max(1,t-Math.floor(2.5)),g=Math.min(s,h+5-1);g-h<4&&(h=Math.max(1,g-5+1));for(let f=h;f<=g;f++)p.push(f);return p},d=r>0?(t-1)*a+1:0,m=Math.min(t*a,r);return s<=1?e.jsx(Z,{className:"shadow-md border-gray-200","data-testid":`${o}-container`,children:e.jsx(J,{className:"p-4",children:e.jsx("div",{className:"flex items-center justify-center",children:e.jsx("div",{className:"text-sm sm:text-base font-medium text-gray-700","data-testid":`${o}-info`,children:i?`Loading ${l}...`:r===0?`No ${l} found`:r===1?`Showing 1 ${l.slice(0,-1)}`:d===m?`Showing ${d} of ${r} ${l}`:`Showing ${d} to ${m} of ${r} ${l}`})})})}):e.jsx(Z,{className:"shadow-md border-gray-200","data-testid":`${o}-container`,children:e.jsx(J,{className:"p-4",children:e.jsxs("div",{className:"flex flex-col sm:flex-row items-center justify-between gap-3",children:[e.jsxs("div",{className:"text-sm sm:text-base font-medium text-gray-700","data-testid":`${o}-info`,children:["Showing ",d," to ",m," of ",r," ",l]}),e.jsxs("div",{className:"flex items-center gap-2","data-testid":`${o}-controls`,children:[e.jsx(E,{variant:"outline",size:"sm",onClick:()=>n(t-1),disabled:t===1,className:"h-8 w-8 p-0","data-testid":`${o}-prev-btn`,"aria-label":"Previous page",children:e.jsx(js,{className:"h-4 w-4 rotate-180"})}),t>3&&s>5&&e.jsxs(e.Fragment,{children:[e.jsx(E,{variant:"outline",size:"sm",onClick:()=>n(1),className:"h-8 w-8 p-0","data-testid":`${o}-page-1`,children:"1"}),e.jsx("span",{className:"text-muted-foreground","data-testid":`${o}-ellipsis-start`,children:"..."})]}),c().map(p=>e.jsx(E,{variant:p===t?"default":"outline",size:"sm",onClick:()=>n(p),className:`h-8 w-8 p-0 ${p===t?"bg-re-green text-white hover:bg-re-green/90":""}`,"data-testid":`${o}-page-${p}`,"aria-current":p===t?"page":void 0,children:p},p)),t5&&e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"text-muted-foreground","data-testid":`${o}-ellipsis-end`,children:"..."}),e.jsx(E,{variant:"outline",size:"sm",onClick:()=>n(s),className:"h-8 w-8 p-0","data-testid":`${o}-page-${s}`,children:s})]}),e.jsx(E,{variant:"outline",size:"sm",onClick:()=>n(t+1),disabled:t===s,className:"h-8 w-8 p-0","data-testid":`${o}-next-btn`,"aria-label":"Next page",children:e.jsx(js,{className:"h-4 w-4"})})]})]})})})}function g1(t,s,a,r="for"){return t==="custom"&&s&&a?`${r} ${st(s,"MMM d, yyyy")} - ${st(a,"MMM d, yyyy")}`:{all:"(All Time)",today:`${r} Today`,week:`${r} This Week`,month:`${r} This Month`,quarter:`${r} This Quarter`,year:`${r} This Year`,last30days:`${r} Last 30 Days`,custom:`${r} Custom Range`}[t]||`${r} Selected Period`}function f1(t,s,a){return t==="all"?"All historical data":t==="custom"&&s&&a?`Data from ${st(s,"MMM d, yyyy")} to ${st(a,"MMM d, yyyy")}`:{all:"All historical data",today:"Today's data",week:"This week data",month:"This month data",quarter:"This quarter data",year:"This year data",last30days:"Last 30 days data",custom:"Custom date range"}[t]||"Filtered data"}function b1({breachedRequests:t,pagination:s,dateRange:a,customStartDate:r,customEndDate:n,onPageChange:i,onKPIClick:l,onNavigate:o}){if(t.length===0)return null;const c=()=>({dateRange:a,startDate:r,endDate:n});return e.jsxs(Z,{className:"shadow-md hover:shadow-lg transition-shadow","data-testid":"tat-breach-report",children:[e.jsx(ce,{children:e.jsxs("div",{className:"flex flex-col sm:flex-row sm:items-center justify-between gap-3",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("div",{className:"bg-red-50 p-2 sm:p-3 rounded-lg",children:e.jsx(ss,{className:"h-5 w-5 sm:h-6 sm:w-6 text-red-600"})}),e.jsxs("div",{children:[e.jsx(de,{className:"text-base sm:text-lg lg:text-xl",children:"TAT Breach Report"}),e.jsxs(we,{className:"text-xs sm:text-sm",children:["Requests that breached TAT - ",f1(a,r,n)]})]})]}),e.jsxs(re,{variant:"destructive",className:"text-sm font-medium self-start sm:self-auto",children:[s.totalRecords," ",s.totalRecords===1?"Breach":"Breaches"]})]})}),e.jsxs(J,{children:[e.jsx("div",{className:"overflow-x-auto -mx-4 sm:mx-0",children:e.jsxs("table",{className:"w-full min-w-[1000px]",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"border-b border-gray-200 bg-gray-50",children:[e.jsx("th",{className:"text-left py-3 px-4 text-sm font-medium text-gray-700",children:"Request ID"}),e.jsx("th",{className:"text-left py-3 px-4 text-sm font-medium text-gray-700 w-[250px]",children:"Title"}),e.jsx("th",{className:"text-left py-3 px-4 text-sm font-medium text-gray-700",children:"Department"}),e.jsx("th",{className:"text-left py-3 px-4 text-sm font-medium text-gray-700",children:"Approver"}),e.jsx("th",{className:"text-left py-3 px-4 text-sm font-medium text-gray-700",children:"Level"}),e.jsx("th",{className:"text-left py-3 px-4 text-sm font-medium text-gray-700 w-[140px]",children:"Breach Time"}),e.jsx("th",{className:"text-left py-3 px-4 text-sm font-medium text-gray-700 w-[300px]",children:"Reason"}),e.jsx("th",{className:"text-left py-3 px-4 text-sm font-medium text-gray-700",children:"Priority"})]})}),e.jsx("tbody",{children:t.map((d,m)=>{const p=d.breachTime||0;return e.jsxs("tr",{className:"border-b border-gray-100 hover:bg-gray-50 transition-colors",children:[e.jsx("td",{className:"py-3 px-4 text-sm font-medium text-blue-600 cursor-pointer hover:underline",onClick:()=>o==null?void 0:o(`request/${d.requestNumber}`),"data-testid":`breach-request-${d.requestNumber}`,children:d.requestNumber}),e.jsx("td",{className:"py-3 px-4 text-sm text-gray-900 w-[250px]",children:e.jsx("p",{className:"break-words leading-relaxed",children:d.title})}),e.jsx("td",{className:"py-3 px-4 text-sm text-gray-700 cursor-pointer hover:text-blue-600 hover:underline",onClick:()=>{d.department&&d.department!=="Unknown"&&l({...c(),department:d.department})},children:d.department||"Unknown"}),e.jsx("td",{className:"py-3 px-4 text-sm text-gray-700",children:d.approverId?e.jsx("span",{className:"cursor-pointer hover:text-blue-600 hover:underline",onClick:()=>{if(o){const u=new URLSearchParams;u.set("approver",d.approverId),u.set("approverType","current"),u.set("slaCompliance","breached"),a&&u.set("dateRange",a),r&&u.set("startDate",r.toISOString()),n&&u.set("endDate",n.toISOString()),o(`requests?${u.toString()}`)}},title:"Click to view all requests for this approver",children:d.approver||"N/A"}):d.approver||"N/A"}),e.jsx("td",{className:"py-3 px-4 text-sm text-gray-700",children:d.currentLevel&&d.totalLevels?e.jsxs("span",{className:"font-medium",children:[d.currentLevel,"/",d.totalLevels]}):d.currentLevel?e.jsxs("span",{className:"font-medium",children:[d.currentLevel,"/—"]}):e.jsx("span",{className:"text-gray-400",children:"—"})}),e.jsx("td",{className:"py-3 px-4 w-[140px]",children:e.jsx("span",{className:"bg-red-500 text-white px-2 py-1 rounded text-xs font-medium whitespace-nowrap",children:r1(p)})}),e.jsx("td",{className:"py-3 px-4 text-sm text-gray-700 w-[300px]",children:e.jsx("div",{className:"max-h-32 overflow-y-auto",children:e.jsx("p",{className:"whitespace-pre-line break-words leading-relaxed",children:d.breachReason||"TAT Exceeded"})})}),e.jsx("td",{className:"py-3 px-4",children:e.jsx(re,{variant:"outline",className:`text-xs font-medium ${d.priority==="express"?"bg-orange-100 text-orange-800 border-orange-200":"bg-blue-100 text-blue-800 border-blue-200"}`,children:d.priority})})]},m)})})]})}),e.jsx("div",{className:"mt-4",children:e.jsx(Wr,{currentPage:s.page,totalPages:s.totalPages,totalRecords:s.totalRecords,itemsPerPage:10,onPageChange:i,itemLabel:"critical requests",testIdPrefix:"dashboard-critical-pagination"})})]})]})}function j1({isAdmin:t,upcomingDeadlines:s,pagination:a,onPageChange:r,onNavigate:n}){return s.length===0?null:e.jsxs(Z,{className:"shadow-md hover:shadow-lg transition-shadow","data-testid":"upcoming-deadlines-section",children:[e.jsx(ce,{children:e.jsx("div",{className:"flex items-center justify-between gap-2",children:e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(ft,{className:"h-4 w-4 sm:h-5 sm:w-5 text-orange-600"}),e.jsxs("div",{children:[e.jsx(de,{className:"text-base sm:text-lg",children:"Upcoming Deadlines"}),e.jsx(we,{className:"text-xs sm:text-sm",children:t?"Current active levels organization-wide":"Requests awaiting your approval"})]})]})})}),e.jsxs(J,{children:[e.jsx("div",{className:"space-y-2 sm:space-y-3",children:s.map((i,l)=>{const o=Number(i.tatPercentageUsed)||0,c=Number(i.elapsedHours)||0,d=Number(i.remainingHours)||0;return e.jsxs("div",{className:"p-3 sm:p-4 border rounded-lg hover:shadow-md transition-all cursor-pointer",onClick:()=>n==null?void 0:n(`request/${i.requestNumber}`),"data-testid":`deadline-item-${i.requestNumber}`,children:[e.jsxs("div",{className:"flex items-start justify-between gap-2 mb-2",children:[e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsxs("div",{className:"flex items-center gap-1 sm:gap-2 mb-1 flex-wrap",children:[e.jsx("span",{className:"font-semibold text-xs sm:text-sm",children:i.requestNumber}),e.jsx(re,{variant:"outline",className:`text-xs ${i.priority==="express"?"bg-orange-50 text-orange-700":"bg-blue-50 text-blue-700"}`,children:i.priority})]}),e.jsx("p",{className:"text-xs sm:text-sm text-muted-foreground truncate",children:i.requestTitle}),e.jsxs("div",{className:"flex items-center gap-2 mt-1 text-xs text-muted-foreground flex-wrap",children:[e.jsxs(re,{variant:"outline",className:"text-xs bg-blue-50 text-blue-700 border-blue-200",children:["Level ",i.levelNumber||"?","/",i.totalLevels||"?"]}),e.jsx("span",{className:"truncate",children:i.approverName})]})]}),e.jsxs("div",{className:"text-right flex-shrink-0",children:[e.jsx("p",{className:"text-xs text-muted-foreground",children:"TAT Used"}),e.jsxs("p",{className:`text-base sm:text-lg font-bold ${o>=80?"text-red-600":o>=50?"text-orange-600":"text-green-600"}`,children:[o.toFixed(0),"%"]})]})]}),e.jsxs("div",{className:"space-y-1",children:[e.jsx(ia,{value:o,className:`h-1.5 sm:h-2 ${o>=80?"bg-red-100":o>=50?"bg-orange-100":"bg-green-100"}`}),e.jsxs("div",{className:"flex justify-between text-xs text-muted-foreground",children:[e.jsxs("span",{children:[Xt(c)," elapsed"]}),e.jsxs("span",{children:[Xt(d)," left"]})]})]})]},l)})}),e.jsx("div",{className:"mt-4",children:e.jsx(Wr,{currentPage:a.page,totalPages:a.totalPages,totalRecords:a.totalRecords,itemsPerPage:10,onPageChange:r,itemLabel:"deadlines",testIdPrefix:"dashboard-deadlines-pagination"})})]})]})}function y1({aiRemarkUtilization:t}){return t?e.jsxs(Z,{className:"shadow-md hover:shadow-lg transition-shadow","data-testid":"ai-remark-utilization-report",children:[e.jsx(ce,{className:"pb-3",children:e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("div",{className:"bg-purple-50 p-2 sm:p-3 rounded-lg",children:e.jsx(hl,{className:"h-5 w-5 sm:h-6 sm:w-6 text-purple-600"})}),e.jsxs("div",{children:[e.jsx(de,{className:"text-base sm:text-lg lg:text-xl",children:"AI Remark Utilization Report"}),e.jsx(we,{className:"text-xs sm:text-sm",children:"AI-generated remarks usage and edits"})]})]})}),e.jsxs(J,{children:[e.jsxs("div",{className:"grid grid-cols-3 gap-4 mb-6",children:[e.jsxs("div",{className:"bg-purple-50 p-4 rounded-lg text-center","data-testid":"ai-total-usage",children:[e.jsx("div",{className:"text-sm text-gray-600 mb-1",children:"Total Usage"}),e.jsx("div",{className:"text-2xl font-bold text-gray-900",children:t.totalUsage})]}),e.jsxs("div",{className:"bg-pink-50 p-4 rounded-lg text-center","data-testid":"ai-total-edits",children:[e.jsx("div",{className:"text-sm text-gray-600 mb-1",children:"Total Edits"}),e.jsx("div",{className:"text-2xl font-bold text-gray-900",children:t.totalEdits})]}),e.jsxs("div",{className:"bg-purple-50 p-4 rounded-lg text-center","data-testid":"ai-edit-rate",children:[e.jsx("div",{className:"text-sm text-gray-600 mb-1",children:"Edit Rate"}),e.jsxs("div",{className:"text-2xl font-bold text-gray-900",children:[t.editRate,"%"]})]})]}),t.monthlyTrends&&t.monthlyTrends.length>0&&e.jsx(dn,{width:"100%",height:250,children:e.jsxs(qb,{data:t.monthlyTrends,children:[e.jsx(Yi,{strokeDasharray:"3 3",stroke:"#f0f0f0"}),e.jsx(Qi,{dataKey:"month",stroke:"#999",tick:{fontSize:11}}),e.jsx(Ki,{stroke:"#999",tick:{fontSize:11}}),e.jsx(mn,{contentStyle:{backgroundColor:"#ffffff",border:"1px solid #e5e7eb",borderRadius:"6px",fontSize:"12px"}}),e.jsx(Xi,{verticalAlign:"bottom",height:36,iconType:"circle",wrapperStyle:{fontSize:"12px",paddingTop:"10px"}}),e.jsx(du,{type:"monotone",dataKey:"aiUsage",stroke:"#8b5cf6",strokeWidth:2,name:"AI Usage",dot:{fill:"#8b5cf6",r:4}}),e.jsx(du,{type:"monotone",dataKey:"manualEdits",stroke:"#ec4899",strokeWidth:2,name:"Manual Edits",dot:{fill:"#ec4899",r:4}})]})})]})]}):null}function v1({approverPerformance:t,pagination:s,dateRange:a,customStartDate:r,customEndDate:n,loading:i,exportingApproverPerformance:l,onPageChange:o,onExport:c,onNavigate:d}){return t.length===0?null:e.jsxs(Z,{className:"shadow-md hover:shadow-lg transition-shadow","data-testid":"approver-performance-report",children:[e.jsx(ce,{className:"pb-3",children:e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("div",{className:"bg-yellow-50 p-2 sm:p-3 rounded-lg",children:e.jsx(qt,{className:"h-5 w-5 sm:h-6 sm:w-6 text-yellow-600"})}),e.jsxs("div",{children:[e.jsx(de,{className:"text-base sm:text-lg lg:text-xl",children:"Approver Performance Report"}),e.jsx(we,{className:"text-xs sm:text-sm",children:"Response time & TAT compliance tracking"})]})]}),e.jsx(E,{onClick:()=>c(a,r,n),disabled:l||i,className:"bg-re-green hover:bg-re-green/90 text-white shrink-0",size:"sm","data-testid":"export-approver-performance-button",children:l?e.jsxs(e.Fragment,{children:[e.jsx(Et,{className:"w-4 h-4 mr-2 animate-spin"}),"Exporting..."]}):e.jsxs(e.Fragment,{children:[e.jsx(Tt,{className:"w-4 h-4 mr-2"}),"Export"]})})]})}),e.jsxs(J,{children:[e.jsx("div",{className:"space-y-3",children:t.map((m,p)=>{const u=m.tatCompliancePercent;return e.jsxs("div",{className:"border border-gray-200 rounded-lg p-4 hover:shadow-md transition-shadow cursor-pointer",onClick:()=>{const h=new URLSearchParams;h.set("approverId",m.approverId),h.set("approverName",m.approverName),h.set("dateRange",a),a==="custom"&&r&&n&&(h.set("startDate",r.toISOString()),h.set("endDate",n.toISOString())),d==null||d(`/approver-performance?${h.toString()}`)},"data-testid":`approver-item-${m.approverId}`,children:[e.jsxs("div",{className:"flex items-center justify-between mb-2",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("div",{className:"w-8 h-8 bg-orange-500 rounded-full flex items-center justify-center text-white font-semibold text-sm flex-shrink-0",children:p+1}),e.jsxs("div",{className:"min-w-0",children:[e.jsx("div",{className:"font-medium text-gray-900 truncate",children:m.approverName}),e.jsxs("div",{className:"text-xs text-gray-500",children:[m.totalApproved," requests approved"]})]})]}),e.jsxs("span",{className:`px-2 py-1 rounded text-xs font-medium ${n1(u)} flex-shrink-0`,children:[u,"% TAT"]})]}),e.jsxs("div",{className:"grid grid-cols-2 gap-4 mt-3",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-xs text-gray-500",children:"Avg Response"}),e.jsx("div",{className:"text-sm font-medium text-gray-900",children:Xt(m.avgResponseHours)})]}),e.jsxs("div",{children:[e.jsx("div",{className:"text-xs text-gray-500",children:"Pending"}),e.jsx("div",{className:"text-sm font-medium text-gray-900",children:e.jsx("span",{className:"bg-gray-500 text-white px-2 py-1 rounded text-xs",children:m.pendingCount})})]})]})]},p)})}),e.jsx("div",{className:"mt-4",children:e.jsx(Wr,{currentPage:s.page,totalPages:s.totalPages,totalRecords:s.totalRecords,itemsPerPage:10,onPageChange:o,itemLabel:"approvers",testIdPrefix:"dashboard-approver-pagination"})})]})]})}function N1(t){const s=new URLSearchParams;t.status&&s.set("status",t.status),t.priority&&s.set("priority",t.priority),t.slaCompliance&&s.set("slaCompliance",t.slaCompliance),t.department&&s.set("department",t.department),t.dateRange&&s.set("dateRange",t.dateRange),t.startDate&&s.set("startDate",t.startDate.toISOString()),t.endDate&&s.set("endDate",t.endDate.toISOString());const a=s.toString(),r=t.targetPage||"requests",n=r==="open-requests"?"/open-requests":r==="my-requests"?"/my-requests":"/requests";return a?`${n}?${a}`:n}function w1(t,s,a,r){const n=[];return r||n.push({label:"New Request",icon:Fe,action:()=>s==null?void 0:s(),color:"bg-emerald-600 hover:bg-emerald-700"}),n.push({label:"View Pending",icon:it,action:()=>a==null?void 0:a("open-requests"),color:"bg-blue-600 hover:bg-blue-700"},{label:"Settings",icon:dr,action:()=>a==null?void 0:a("settings"),color:"bg-slate-600 hover:bg-slate-700"}),t&&n.splice(n.length-1,0,{label:"Reports",icon:Bt,action:()=>a==null?void 0:a("detailed-reports"),color:"bg-purple-600 hover:bg-purple-700"}),n}function C1({onNavigate:t,onNewRequest:s}){const{user:a}=us(),r=di(),n=Nn(Se=>Se.dashboard.viewAsUser),i=x.useMemo(()=>Sl(a),[a]),l=i&&!n,o=x.useCallback(Se=>{r(eN(Se))},[r]),c=tN(),{dateRange:d,customStartDate:m,customEndDate:p,showCustomDatePicker:u,handleDateRangeChange:h,handleApplyCustomDate:g,resetCustomDates:f,setCustomStartDate:b,setCustomEndDate:j,setShowCustomDatePicker:y}=c,N=sN(),{activity:S,critical:A,deadlines:w,approver:I,updateActivityPagination:k,updateCriticalPagination:T,updateDeadlinesPagination:v,updateApproverPagination:_,handleActivityPageChange:P,handleCriticalPageChange:B,handleDeadlinesPageChange:R,handleApproverPageChange:M}=N,L=rN({isAdmin:l,viewAsUser:i&&n,userId:a==null?void 0:a.userId,dateRange:d,customStartDate:m,customEndDate:p,onPaginationUpdate:{activity:k,critical:T,deadlines:v,approver:_}}),{kpis:D,recentActivity:C,criticalRequests:G,departmentStats:U,priorityDistribution:$,upcomingDeadlines:F,aiRemarkUtilization:H,approverPerformance:K,loading:z,refreshing:O,fetchDashboardData:V,fetchRecentActivities:Q,fetchCriticalRequests:q,fetchUpcomingDeadlines:ee,fetchApproverPerformance:te}=L,ue=hN(),{exportingDeptStats:_e,exportingApproverPerformance:Ze,handleExportDepartmentStats:Ue,handleExportApproverPerformance:Pe}=ue,De=x.useMemo(()=>s1(G),[G]),le=x.useMemo(()=>a1(F),[F]),ye=x.useCallback(()=>{V(!0)},[d,m,p,V]),ae=x.useCallback(()=>{g(()=>{m&&p&&V(!1)})},[m,p,g,V]),he=x.useCallback(Se=>{P(Se,Q)},[P,Q]),ne=x.useCallback(Se=>{B(Se,q)},[B,q]),ge=x.useCallback(Se=>{R(Se,ee)},[R,ee]),ke=x.useCallback(Se=>{M(Se,()=>te(Se))},[M,te]);x.useEffect(()=>{d==="custom"?m&&p&&V(!1):V(!1)},[d,m,p,n]);const Oe=x.useMemo(()=>{try{const Se=tt.getUserData();return(Se==null?void 0:Se.jobTitle)==="Dealer"}catch(Se){return console.error("[Dashboard] Error checking dealer status:",Se),!1}},[]),je=x.useMemo(()=>w1(i,s,t,Oe),[i,s,t,Oe]),ie=x.useCallback(Se=>{const qe=N1(Se);t==null||t(qe)},[t]);return z?e.jsx("div",{className:"flex items-center justify-center h-screen",children:e.jsxs("div",{className:"flex flex-col items-center gap-4",children:[e.jsx(Et,{className:"w-8 h-8 animate-spin text-blue-600"}),e.jsx("p",{className:"text-muted-foreground",children:"Loading dashboard..."})]})}):e.jsxs("div",{className:"space-y-4 sm:space-y-6 max-w-7xl mx-auto p-3 sm:p-4","data-testid":"dashboard",children:[e.jsx(pN,{isAdmin:i,effectiveIsAdmin:l,viewAsUser:n,onToggleView:o,quickActions:je,userDisplayName:a==null?void 0:a.displayName,userEmail:a==null?void 0:a.email}),e.jsx(Xw,{isAdmin:l,dateRange:d,customStartDate:m,customEndDate:p,showCustomDatePicker:u,refreshing:O,onDateRangeChange:h,onCustomStartDateChange:b,onCustomEndDateChange:j,onShowCustomDatePickerChange:y,onApplyCustomDate:ae,onResetCustomDates:f,onRefresh:ye}),l?e.jsx(Zw,{kpis:D,priorityDistribution:$,dateRange:d,customStartDate:m,customEndDate:p,onKPIClick:ie}):e.jsx(Jw,{kpis:D,criticalRequests:G,dateRange:d,customStartDate:m,customEndDate:p,onKPIClick:ie,onNavigate:t,userId:a==null?void 0:a.userId,userDisplayName:(a==null?void 0:a.displayName)||(a==null?void 0:a.email)}),e.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-3 gap-4 sm:gap-6 h-[90vh] min-h-[720px] lg:h-[60vh] lg:min-h-[480px]","data-testid":"dashboard-alerts-activity",children:[e.jsx(i1,{isAdmin:l,breachedRequests:De,pagination:A,onPageChange:ne,onNavigate:t}),e.jsx(u1,{isAdmin:l,recentActivity:C,pagination:S,refreshing:O,onPageChange:he,onRefresh:ye,onNavigate:t,currentUserId:a==null?void 0:a.userId,currentUserDisplayName:a==null?void 0:a.displayName,currentUserEmail:a==null?void 0:a.email})]}),l&&D&&e.jsx(x1,{kpis:D,upcomingDeadlines:F,criticalRequests:G,departmentStats:U,dateRange:d,customStartDate:m,customEndDate:p,exportingDeptStats:_e,onKPIClick:ie,onExportDepartmentStats:Ue}),!l&&D&&e.jsx(h1,{kpis:D}),l&&$.length>0&&e.jsx(p1,{priorityDistribution:$,onNavigate:t}),l&&De.length>0&&e.jsx(b1,{breachedRequests:De,pagination:A,dateRange:d,customStartDate:m,customEndDate:p,onPageChange:ne,onKPIClick:ie,onNavigate:t}),le.length>0&&e.jsx(j1,{isAdmin:l,upcomingDeadlines:le,pagination:w,onPageChange:ge,onNavigate:t}),l&&H&&e.jsx(y1,{aiRemarkUtilization:H}),l&&K.length>0&&e.jsx(v1,{approverPerformance:K,pagination:I,dateRange:d,customStartDate:m,customEndDate:p,loading:z,exportingApproverPerformance:Ze,onPageChange:ke,onExport:Pe,onNavigate:t})]})}const S1={searchTerm:"",statusFilter:"all",priorityFilter:"all",templateTypeFilter:"all",form16FinancialYear:"",form16Quarter:"",sortBy:"created",sortOrder:"desc",currentPage:1},ig=yn({name:"openRequests",initialState:S1,reducers:{setSearchTerm:(t,s)=>{t.searchTerm=s.payload},setStatusFilter:(t,s)=>{t.statusFilter=s.payload},setPriorityFilter:(t,s)=>{t.priorityFilter=s.payload},setTemplateTypeFilter:(t,s)=>{t.templateTypeFilter=s.payload},setForm16FinancialYear:(t,s)=>{t.form16FinancialYear=s.payload},setForm16Quarter:(t,s)=>{t.form16Quarter=s.payload},setSortBy:(t,s)=>{t.sortBy=s.payload},setSortOrder:(t,s)=>{t.sortOrder=s.payload},setCurrentPage:(t,s)=>{t.currentPage=s.payload},clearFilters:t=>{t.searchTerm="",t.statusFilter="all",t.priorityFilter="all",t.templateTypeFilter="all",t.form16FinancialYear="",t.form16Quarter="",t.currentPage=1}}}),{setSearchTerm:A1,setStatusFilter:T1,setPriorityFilter:k1,setTemplateTypeFilter:D1,setForm16FinancialYear:R1,setForm16Quarter:F1,setSortBy:I1,setSortOrder:P1,setCurrentPage:E1,clearFilters:_1}=ig.actions;function M1(){const t=di(),{searchTerm:s,statusFilter:a,priorityFilter:r,templateTypeFilter:n,form16FinancialYear:i,form16Quarter:l,sortBy:o,sortOrder:c,currentPage:d}=Nn(A=>A.openRequests),m=x.useCallback(A=>t(A1(A)),[t]),p=x.useCallback(A=>t(T1(A)),[t]),u=x.useCallback(A=>t(k1(A)),[t]),h=x.useCallback(A=>t(D1(A)),[t]),g=x.useCallback(A=>t(R1(A)),[t]),f=x.useCallback(A=>t(F1(A)),[t]),b=x.useCallback(A=>t(I1(A)),[t]),j=x.useCallback(A=>t(P1(A)),[t]),y=x.useCallback(A=>t(E1(A)),[t]),N=x.useCallback(()=>t(_1()),[t]),S=[s,r!=="all"?r:null,a!=="all"?a:null,n!=="all"?n:null,n==="FORM_16"&&i?"fy":null,n==="FORM_16"&&l?"q":null].filter(Boolean).length;return{searchTerm:s,statusFilter:a,priorityFilter:r,templateTypeFilter:n,form16FinancialYear:i,form16Quarter:l,sortBy:o,sortOrder:c,currentPage:d,setSearchTerm:m,setStatusFilter:p,setPriorityFilter:u,setTemplateTypeFilter:h,setForm16FinancialYear:g,setForm16Quarter:f,setSortBy:b,setSortOrder:j,setCurrentPage:y,clearFilters:N,activeFiltersCount:S}}function Jl(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,t=>{const s=Math.random()*16|0;return(t==="x"?s:s&3|8).toString(16)})}async function L1(t){var o;const s=t.priorityUi.toUpperCase()==="EXPRESS"?"EXPRESS":"STANDARD",a=Array.from({length:t.approverCount||1},(c,d)=>{const m=d,p=t.approvers[m]||{},u=m+1,h=p.tat??"";let g=0;typeof h=="number"&&(g=p.tatType==="days"?h*24:h);const f=p.email||"",b=p.name&&p.name.trim()||f.split("@")[0]||`Approver ${u}`;return{levelNumber:u,levelName:`Level ${u}`,approverId:Jl(),approverEmail:f,approverName:b,tatHours:g>0?g:24,isFinalApprover:u===(t.approverCount||1)}}),r=[...(t.spectators||[]).map(c=>({userId:Jl(),userEmail:c.email,userName:c.name||c.email.split("@")[0]||"Spectator",participantType:"SPECTATOR",canComment:!0,canViewDocuments:!0,canDownloadDocuments:!1,notificationEnabled:!0})),...(t.ccList||[]).map(c=>({userId:Jl(),userEmail:c.email,userName:c.name||c.email.split("@")[0]||"CC",participantType:"CONSULTATION",canComment:!1,canViewDocuments:!0,canDownloadDocuments:!1,notificationEnabled:!0}))],n={templateType:t.templateType,title:t.title,description:t.description,priority:s,approvalLevels:a,participants:r.length?r:void 0,isDraft:t.isDraft},i=await ve.post("/workflows",n),l=((o=i.data)==null?void 0:o.data)||i.data;return{id:l.id||l.workflowId||""}}async function Bc(t,s,a="SUPPORTING"){var o;const r={templateType:t.templateType,title:t.title,description:t.description,priority:t.priorityUi.toUpperCase()==="EXPRESS"?"EXPRESS":"STANDARD",isDraft:t.isDraft,approvers:Array.from({length:t.approverCount||1},(c,d)=>{const m=t.approvers[d]||{},p=typeof m.tat=="number"?m.tat:0;if(!m.email||!m.email.trim())throw new Error(`Email is required for approver at level ${d+1}.`);return{email:m.email,tat:p,tatType:m.tatType||"hours"}})};t.spectators&&t.spectators.length>0&&(r.spectators=t.spectators.filter(c=>c==null?void 0:c.email).map(c=>({email:c.email})));const n=new FormData;n.append("payload",JSON.stringify(r)),n.append("category",a),s.forEach(c=>n.append("files",c));const i=await ve.post("/workflows/multipart",n,{headers:{"Content-Type":"multipart/form-data"}}),l=((o=i.data)==null?void 0:o.data)||i.data;return{id:l==null?void 0:l.requestId}}async function O1(t={}){var j;const{page:s=1,limit:a=20,search:r,status:n,priority:i,templateType:l,financialYear:o,quarter:c,department:d,initiator:m,approver:p,slaCompliance:u,dateRange:h,startDate:g,endDate:f}=t,b=await ve.get("/workflows",{params:{page:s,limit:a,search:r,status:n,priority:i,templateType:l,financialYear:o,quarter:c,department:d,initiator:m,approver:p,slaCompliance:u,dateRange:h,startDate:g,endDate:f}});return((j=b.data)==null?void 0:j.data)||b.data}async function $1(t={}){var y,N,S,A,w;const{page:s=1,limit:a=20,search:r,status:n,priority:i,templateType:l,financialYear:o,quarter:c,department:d,initiator:m,approver:p,approverType:u,slaCompliance:h,dateRange:g,startDate:f,endDate:b}=t,j=await ve.get("/workflows/participant-requests",{params:{page:s,limit:a,search:r,status:n,priority:i,templateType:l,financialYear:o,quarter:c,department:d,initiator:m,approver:p,approverType:u,slaCompliance:h,dateRange:g,startDate:f,endDate:b}});return{data:((N=(y=j.data)==null?void 0:y.data)==null?void 0:N.data)||((S=j.data)==null?void 0:S.data)||[],pagination:((w=(A=j.data)==null?void 0:A.data)==null?void 0:w.pagination)||{page:s,limit:a,total:0,totalPages:1}}}async function U1(t={}){var g,f,b,j,y;const{page:s=1,limit:a=20,search:r,status:n,priority:i,department:l,initiator:o,approver:c,slaCompliance:d,dateRange:m,startDate:p,endDate:u}=t,h=await ve.get("/workflows/my",{params:{page:s,limit:a,search:r,status:n,priority:i,department:l,initiator:o,approver:c,slaCompliance:d,dateRange:m,startDate:p,endDate:u}});return{data:((f=(g=h.data)==null?void 0:g.data)==null?void 0:f.data)||((b=h.data)==null?void 0:b.data)||[],pagination:((y=(j=h.data)==null?void 0:j.data)==null?void 0:y.pagination)||{page:s,limit:a,total:0,totalPages:1}}}async function B1(t={}){var h,g,f,b,j;const{page:s=1,limit:a=20,search:r,status:n,priority:i,templateType:l,department:o,slaCompliance:c,dateRange:d,startDate:m,endDate:p}=t,u=await ve.get("/workflows/my-initiated",{params:{page:s,limit:a,search:r,status:n,priority:i,templateType:l,department:o,slaCompliance:c,dateRange:d,startDate:m,endDate:p}});return{data:((g=(h=u.data)==null?void 0:h.data)==null?void 0:g.data)||((f=u.data)==null?void 0:f.data)||[],pagination:((j=(b=u.data)==null?void 0:b.data)==null?void 0:j.pagination)||{page:s,limit:a,total:0,totalPages:1}}}async function V1(t={}){var u,h,g,f,b;const{page:s=1,limit:a=20,search:r,status:n,priority:i,templateType:l,financialYear:o,quarter:c,sortBy:d,sortOrder:m}=t,p=await ve.get("/workflows/open-for-me",{params:{page:s,limit:a,search:r,status:n,priority:i,templateType:l,financialYear:o,quarter:c,sortBy:d,sortOrder:m}});return{data:((h=(u=p.data)==null?void 0:u.data)==null?void 0:h.data)||((g=p.data)==null?void 0:g.data)||[],pagination:((b=(f=p.data)==null?void 0:f.data)==null?void 0:b.pagination)||{page:s,limit:a,total:0,totalPages:1}}}async function z1(t={}){var u,h,g,f,b;const{page:s=1,limit:a=20,search:r,status:n,priority:i,templateType:l,financialYear:o,quarter:c,sortBy:d,sortOrder:m}=t,p=await ve.get("/workflows/closed-by-me",{params:{page:s,limit:a,search:r,status:n,priority:i,templateType:l,financialYear:o,quarter:c,sortBy:d,sortOrder:m}});return{data:((h=(u=p.data)==null?void 0:u.data)==null?void 0:h.data)||((g=p.data)==null?void 0:g.data)||[],pagination:((b=(f=p.data)==null?void 0:f.data)==null?void 0:b.pagination)||{page:s,limit:a,total:0,totalPages:1}}}async function Ps(t,s){var r;const a=await ve.get(`/workflows/${t}/details`);return((r=a.data)==null?void 0:r.data)||a.data}async function xn(t){var a;const s=await ve.get(`/workflows/${t}/work-notes`);return((a=s.data)==null?void 0:a.data)||s.data}async function lg(t,s,a=[]){var i;const r=new FormData;r.append("payload",JSON.stringify(s||{})),a.forEach(l=>r.append("files",l));const n=await ve.post(`/workflows/${t}/work-notes`,r,{headers:{"Content-Type":"multipart/form-data"}});return((i=n.data)==null?void 0:i.data)||n.data}async function og(t,s,a,r){var i;const n=await ve.post(`/workflows/${t}/approvers/at-level`,{email:s,tatHours:a,level:r});return((i=n.data)==null?void 0:i.data)||n.data}async function H1(t,s,a){var n;const r=await ve.post(`/workflows/${t}/approvals/${s}/skip`,{reason:a});return((n=r.data)==null?void 0:n.data)||r.data}async function cg(t,s){var r;const a=await ve.post(`/workflows/${t}/participants/spectator`,{email:s});return((r=a.data)==null?void 0:r.data)||a.data}async function G1(t,s,a,r){var i;const n=await ve.post(`/workflows/${t}/pause`,{levelId:s,reason:a,resumeDate:r.toISOString()});return((i=n.data)==null?void 0:i.data)||n.data}async function W1(t,s){var r;const a=await ve.post(`/workflows/${t}/resume`,{notes:s});return((r=a.data)==null?void 0:r.data)||a.data}async function Y1(t){var a;const s=await ve.post(`/workflows/${t}/pause/retrigger`);return((a=s.data)==null?void 0:a.data)||s.data}async function Ou(t){var a;const s=await ve.get(`/workflows/${t}/pause`);return((a=s.data)==null?void 0:a.data)||s.data}function Q1(t){return`https://reflow-uat.royalenfield.com/api/v1/workflows/work-notes/attachments/${t}/preview`}function wn(t){return`https://reflow-uat.royalenfield.com/api/v1/workflows/documents/${t}/preview`}function dg(t){var r;if(!t)return"download";const s=t.match(/filename\*=UTF-8''([^;]+)/);if(s&&s[1])try{return decodeURIComponent(s[1])}catch{}const a=t.match(/filename="?([^";]+)"?/);return a&&a.length>1&&a[1]&&((r=a[1].replace(/^"|"$/g,"").split(";")[0])==null?void 0:r.trim())||"download"}async function Ms(t){const a=`https://reflow-uat.royalenfield.com/api/v1/workflows/documents/${t}/download`,r=!0;try{const i=await fetch(a,{credentials:"include"});if(!i.ok){const p=await i.text();throw new Error(`Download failed: ${i.status} - ${p}`)}const l=await i.blob(),o=window.URL.createObjectURL(l),c=i.headers.get("Content-Disposition"),d=dg(c),m=document.createElement("a");m.href=o,m.download=d,document.body.appendChild(m),m.click(),document.body.removeChild(m),window.URL.revokeObjectURL(o)}catch(n){throw console.error("[Download] Failed:",n),n}}async function $u(t){const a=`https://reflow-uat.royalenfield.com/api/v1/workflows/work-notes/attachments/${t}/download`,r=!0;try{const i=await fetch(a,{credentials:"include"});if(!i.ok){const p=await i.text();throw new Error(`Download failed: ${i.status} - ${p}`)}const l=await i.blob(),o=window.URL.createObjectURL(l),c=i.headers.get("Content-Disposition"),d=dg(c),m=document.createElement("a");m.href=o,m.download=d,document.body.appendChild(m),m.click(),document.body.removeChild(m),window.URL.revokeObjectURL(o)}catch(n){throw console.error("[Download] Failed:",n),n}}const Ks={createWorkflowFromForm:L1,createWorkflowMultipart:Bc,listWorkflows:O1,listParticipantRequests:$1,listMyWorkflows:U1,listMyInitiatedWorkflows:B1,listOpenForMe:V1,listClosedByMe:z1,submitWorkflow:mg,getWorkflowDetails:Ps,getWorkNotes:xn,createWorkNoteMultipart:lg,handleInitiatorAction:ug};async function mg(t){var a;const s=await ve.patch(`/workflows/${t}/submit`);return((a=s.data)==null?void 0:a.data)||s.data}async function K1(t,s){var r;const a=await ve.put(`/workflows/${t}`,s);return((r=a.data)==null?void 0:r.data)||a.data}async function X1(t,s,a,r){var o;const n={...s,deleteDocumentIds:r||[]},i=new FormData;i.append("payload",JSON.stringify(n)),i.append("category","SUPPORTING"),a&&a.length>0&&a.forEach(c=>i.append("files",c));const l=await ve.put(`/workflows/${t}/multipart`,i,{headers:{"Content-Type":"multipart/form-data"}});return((o=l.data)==null?void 0:o.data)||l.data}async function Vn(t,s,a){var n;const r=await ve.patch(`/workflows/${t}/approvals/${s}/approve`,{action:"APPROVE",comments:a||""});return((n=r.data)==null?void 0:n.data)||r.data}async function Rr(t,s,a,r){var i;const n=await ve.patch(`/workflows/${t}/approvals/${s}/reject`,{action:"REJECT",rejectionReason:a||"",comments:r||""});return((i=n.data)==null?void 0:i.data)||n.data}async function Z1(t,s){const a=await ve.put(`/tat/breach-reason/${t}`,{breachReason:s});if(!a.data.success)throw new Error(a.data.error||"Failed to update breach reason")}async function ug(t,s,a){var n;const r=await ve.post(`/workflows/${t}/initiator-action`,{action:s,...a});return((n=r.data)==null?void 0:n.data)||r.data}async function J1(t){var a;return((a=(await ve.get(`/workflows/${t}/history`)).data)==null?void 0:a.data)||[]}function bs(t,s){if(!t)return"N/A";try{const a=typeof t=="string"?new Date(t):t;if(isNaN(a.getTime()))return"Invalid Date";const{includeTime:r=!0,includeSeconds:n=!1,format:i="medium"}=s||{};return r?a.toLocaleString("en-US",{year:"numeric",month:"short",day:"numeric",hour:"numeric",minute:"2-digit",...n&&{second:"2-digit"},hour12:!0}):a.toLocaleDateString("en-US",{year:"numeric",month:i==="short"?"short":"long",day:"numeric"})}catch(a){return console.error("Error formatting date:",a),String(t)}}function xg(t){return bs(t,{includeTime:!1,format:"short"})}function Fa(t,s=!1){if(!t)return"N/A";try{const a=typeof t=="string"?new Date(t):t;if(isNaN(a.getTime()))return"Invalid Date";const r=String(a.getDate()).padStart(2,"0"),i=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][a.getMonth()],l=a.getFullYear();if(s){const o=a.getHours(),c=o%12||12,d=String(a.getMinutes()).padStart(2,"0"),m=String(a.getSeconds()).padStart(2,"0"),p=o>=12?"PM":"AM";return`${r} ${i} ${l}, ${c}:${d}:${m} ${p}`}return`${r} ${i} ${l}`}catch(a){return console.error("Error formatting date:",a),String(t)}}function q1(t){return t?t.jobTitle==="Dealer"||t.jobTitle==="DEALER":!1}function Vc(t){return q1(t)?"DEALER":"STANDARD"}function _a(t){if(!t)return"";let s=t.replace(/
+
-
-
+
+
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..5430169
--- /dev/null
+++ b/src/controllers/CpcCdcController.ts
@@ -0,0 +1,1157 @@
+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,
+ canonicalizeMoneyFieldKeysInRecord,
+ canonicalizeRuleFieldKey,
+ cpcWhereFromAndParts,
+ isMoneyFieldKey,
+ sanitizeMoneyValuesInRecord,
+ sanitizePersonNameFieldsInRecord
+} 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
+ }];
+ }
+
+ queue = queue.map((entry) => {
+ const rawMsd = (entry.msd_payload || {}) as Record;
+ const msd_payload = sanitizeMoneyValuesInRecord(canonicalizeMoneyFieldKeysInRecord(rawMsd));
+ const rawKeys = (entry as { expected_field_keys?: unknown }).expected_field_keys;
+ const out: Record = { ...entry, msd_payload };
+ if (Array.isArray(rawKeys)) {
+ out.expected_field_keys = [
+ ...new Set(
+ (rawKeys as unknown[])
+ .map((k) => {
+ const s = String(k ?? '').trim();
+ if (!s) return '';
+ return isMoneyFieldKey(s) ? canonicalizeRuleFieldKey(s) : s;
+ })
+ .filter(Boolean)
+ )
+ ];
+ }
+ return out;
+ });
+
+ 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;
+ }
+ }
+ }
+
+ Object.assign(
+ extracted,
+ sanitizePersonNameFieldsInRecord({ ...extracted } as Record)
+ );
+ Object.assign(
+ extracted,
+ canonicalizeMoneyFieldKeysInRecord({ ...extracted } as Record)
+ );
+ Object.assign(
+ extracted,
+ sanitizeMoneyValuesInRecord({ ...extracted } as Record)
+ );
+
+ // 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, page, sortBy, order } = req.query;
+ const qFirst = (v: unknown): string => {
+ if (v == null) return '';
+ if (Array.isArray(v)) return v[0] != null ? String(v[0]) : '';
+ return String(v);
+ };
+ const takeRaw = parseInt(qFirst(limit) || '50', 10);
+ const take = Number.isFinite(takeRaw) && takeRaw > 0 ? Math.min(200, takeRaw) : 50;
+ const pageRaw = parseInt(qFirst(page) || '1', 10);
+ const pageNum = Number.isFinite(pageRaw) && pageRaw > 0 ? pageRaw : 1;
+ const skip = (pageNum - 1) * take;
+
+ const andParts: Record[] = [];
+ appendCpcDocumentFilters(andParts, {
+ type: qFirst(type) || (type as string),
+ status: qFirst(status) || (status as string),
+ search: qFirst(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)
+ };
+ });
+
+
+ const pages = count === 0 ? 1 : Math.ceil(count / take);
+
+ return res.json({
+ items: enriched,
+ meta: {
+ total: count,
+ page: pageNum,
+ limit: take,
+ pages
+ }
+ });
+ } 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
+ });
+ }
+ }
+
+ /**
+ * Manual validation override (edit / approve / reject) — disabled; status comes from pipeline only.
+ */
+ async updateDocumentStatus(_req: Request, res: Response) {
+ return res.status(403).json({
+ error_code: 'MANUAL_DOCUMENT_ACTIONS_DISABLED',
+ error_message:
+ 'Manual document status updates and corrected-field edits are not available for CPC/CSD documents.',
+ retryable: false
+ });
+ }
+
+ /**
+ * 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..042a339
--- /dev/null
+++ b/src/services/cpc-cdc/CpcRuleExtractService.ts
@@ -0,0 +1,375 @@
+import { calculateMatch, normalizePersonNameExtract } 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;
+ }
+ if (displayName) {
+ const n = normalizePersonNameExtract(displayName);
+ if (n) displayName = n;
+ }
+
+ // 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..de77193
--- /dev/null
+++ b/src/services/cpc-cdc/CpcValidationService.ts
@@ -0,0 +1,820 @@
+import fs from 'fs';
+import path from 'path';
+import { VertexAI } from '@google-cloud/vertexai';
+import {
+ calculateMatch,
+ canonicalizeRuleFieldKey,
+ digitsOnly,
+ isPersonalHolderNameField,
+ normalizeMoney,
+ normalizePersonNameExtract
+} 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 = canonicalizeRuleFieldKey(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;
+ }
+
+ let expected = rawExpected;
+ let found = findNormalizedValue(extractedFields, key);
+ const confidence = fieldConfidence[key] || 0;
+
+ if (isPersonalHolderNameField(key)) {
+ const en = normalizePersonNameExtract(String(expected ?? ''));
+ if (en) expected = en as typeof rawExpected;
+ if (found !== undefined && found !== null) {
+ const fn = normalizePersonNameExtract(String(found));
+ if (fn) found = fn as typeof found;
+ }
+ }
+
+ 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.
+NAME_LINE_VS_MSD: When the printed name includes a relation suffix (S/O, D/O, W/O, C/O, Son of, …) after the holder's name, if REFERENCE_VALUES show the same person's name without that suffix, return only that shorter holder name for customer_name / name / authorized_person_name (do not append the S/O clause).
+HOLDER_NAME_NO_TITLES: For customer_name, name, and authorized_person_name only — return the person's given name tokens as printed (Latin or Devanagari per script rules). Do NOT include salutations or ranks (Mr, Mrs, Ms, Dr, Prof, Sir, Shri, Smt, Kumari, Lt, Captain, Major, Colonel, General, Admiral, Wing Commander, Group Captain, etc.). Do NOT include relation lines (S/O, D/O, W/O, C/O, Son of, …) or father's name after the holder name; only the holder's own name span.
+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..59f1e72
--- /dev/null
+++ b/src/services/cpc-cdc/utils.ts
@@ -0,0 +1,401 @@
+import stringSimilarity from 'string-similarity';
+import { Op, cast, col, where as sqlWhere } 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 });
+ }
+ }
+
+ const q = String(search ?? '').trim();
+ if (q) {
+ const pattern = `%${q}%`;
+ const orClause: object[] = [
+ { bookingId: { [Op.iLike]: pattern } },
+ { claimId: { [Op.iLike]: pattern } },
+ { documentType: { [Op.iLike]: pattern } }
+ ];
+ if (searchIncludeId) {
+ // Postgres: `uuid ILIKE '…'` is invalid — cast so id substring search works and does not break the whole OR.
+ orClause.unshift(sqlWhere(cast(col('id'), 'TEXT'), { [Op.iLike]: pattern }));
+ }
+ 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));
+}
+
+/** Compact key for rule lookup / money detection (spaces, hyphens, underscores removed). */
+export function compactFieldKey(rawKey: string): string {
+ return String(rawKey || '')
+ .trim()
+ .toLowerCase()
+ .replace(/[\s_-]+/g, '');
+}
+
+/**
+ * True for MSD/extraction keys that represent rupee amounts (commas / Indian grouping should be ignored).
+ */
+export function isMoneyFieldKey(rawKey: string): boolean {
+ const k = compactFieldKey(rawKey);
+ if (!k) return false;
+ if (k.includes('amount')) return true;
+ if (k.includes('invoicevalue')) return true;
+ if (k.includes('totalvalue')) return true;
+ if (k.includes('taxamount')) return true;
+ return false;
+}
+
+/**
+ * Lowercase + spaces/hyphens → underscores for all keys; compact camelCase aliases **only for money keys**
+ * (e.g. poAmount / Po Amount → po_amount). Non-money keys are unchanged except whitespace normalization.
+ */
+export function canonicalizeRuleFieldKey(rawKey: string): string {
+ const k = String(rawKey || '')
+ .trim()
+ .toLowerCase()
+ .replace(/[\s-]+/g, '_');
+ if (!isMoneyFieldKey(k) && !isMoneyFieldKey(rawKey)) {
+ return k;
+ }
+ const compact = k.replace(/_/g, '');
+ const amountAliases: Record = {
+ poamount: 'po_amount',
+ letteramount: 'letter_amount',
+ invoicevalue: 'invoice_value',
+ taxamount: 'tax_amount',
+ totalamount: 'total_amount'
+ };
+ if (amountAliases[compact]) return amountAliases[compact];
+ return k;
+}
+
+/** Rename payload keys so money fields use canonical snake_case (e.g. poAmount → po_amount). Non-money keys untouched. */
+export function canonicalizeMoneyFieldKeysInRecord(obj: Record | null | undefined): Record {
+ if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return (obj || {}) as Record;
+ const out = { ...obj };
+ for (const key of [...Object.keys(out)]) {
+ if (!isMoneyFieldKey(key)) continue;
+ const nk = canonicalizeRuleFieldKey(key);
+ if (nk === key) continue;
+ const v = out[key];
+ delete out[key];
+ if (out[nk] === undefined) out[nk] = v;
+ }
+ return out;
+}
+
+/** Normalize money-type values to plain digit strings (no commas) for MSD / extracted payloads. */
+export function sanitizeMoneyValuesInRecord(obj: Record | null | undefined): Record {
+ if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return (obj || {}) as Record;
+ const out: Record = { ...obj };
+ for (const key of Object.keys(out)) {
+ if (!isMoneyFieldKey(key)) continue;
+ const v = out[key];
+ if (v === null || v === undefined) continue;
+ const s = String(v).trim();
+ if (!s) continue;
+ const nm = normalizeMoney(s);
+ if (nm !== '') out[key] = nm;
+ }
+ return out;
+}
+
+/**
+ * Strip trailing relation / father-name suffix (S/O, W/O, …) so "Arjun Mehar S/O Radheshyam Mehar" → "Arjun Mehar".
+ */
+export function trimPatronymicSuffixFromName(s: string | null | undefined): string {
+ let t = cleanText(s);
+ if (!t) return '';
+ const re = /\b(?:s\/o|w\/o|d\/o|c\/o|son\s+of|daughter\s+of|wife\s+of|husband\s+of|care\s+of)\b/i;
+ const parts = t.split(re);
+ t = (parts[0] ?? t).trim();
+ t = t.split(/[,;]/)[0]?.trim() ?? t;
+ return cleanText(t);
+}
+
+/** Multi-word military / rank prefixes at the start of a name line (longest first). */
+const MULTI_TITLE_PREFIX_RES: RegExp[] = [
+ /^air\s+vice\s+marshal\s+/i,
+ /^air\s+commodore\s+/i,
+ /^vice\s+admiral\s+/i,
+ /^rear\s+admiral\s+/i,
+ /^group\s+captain\s+/i,
+ /^wing\s+commander\s+/i,
+ /^sqn\s+ldr\.?\s+/i,
+ /^flying\s+officer\s+/i,
+ /^fg\s+offr\.?\s+/i
+];
+
+/** Single-token salutations / ranks at the start (repeat until none). */
+const SINGLE_TITLE_PREFIX_RE =
+ /^(?:mr|mrs|ms|miss|dr\.?|doctor|prof\.?|sir|madam|shri|smt\.?|smti\.?|kumari|kum\.?|lt\.?|lieut\.?|lieutenant|leftenant|capt\.?|captain|maj\.?|major|col\.?|colonel|brig\.?|brigadier|gen\.?|general|cmdr|commander|cmde|commodore|adm\.?|admiral|hon\.?|honorable|honourable|retd\.?|svc)\s+/i;
+
+function stripLeadingSalutationsAndTitles(s: string): string {
+ let t = cleanText(s);
+ for (let guard = 0; guard < 24; guard++) {
+ let removed = false;
+ for (const re of MULTI_TITLE_PREFIX_RES) {
+ if (re.test(t)) {
+ t = t.replace(re, '').trim();
+ removed = true;
+ break;
+ }
+ }
+ if (removed) continue;
+ if (SINGLE_TITLE_PREFIX_RE.test(t)) {
+ t = t.replace(SINGLE_TITLE_PREFIX_RE, '').trim();
+ continue;
+ }
+ break;
+ }
+ return t;
+}
+
+/**
+ * Holder-style person name for extraction / compare: no leading Dr./military rank tokens, no S/O-style suffixes.
+ */
+export function normalizePersonNameExtract(s: string | null | undefined): string {
+ if (s == null || !String(s).trim()) return '';
+ let t = stripLeadingSalutationsAndTitles(String(s));
+ t = trimPatronymicSuffixFromName(t);
+ return cleanText(t);
+}
+
+/** Strip salutations / relation clutter from holder name fields on an extracted / payload object. */
+export function sanitizePersonNameFieldsInRecord(obj: Record | null | undefined): Record {
+ if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return (obj || {}) as Record;
+ const out = { ...obj };
+ for (const key of Object.keys(out)) {
+ if (!isPersonalHolderNameField(key)) continue;
+ const v = out[key];
+ if (v === null || v === undefined) continue;
+ const n = normalizePersonNameExtract(String(v));
+ if (n) out[key] = n;
+ }
+ return out;
+}
+
+/** Customer / holder person name fields (not supplier, grantor, or company). */
+export function isPersonalHolderNameField(rawKey: string): boolean {
+ const k = compactFieldKey(rawKey);
+ if (!k) return false;
+ if (/(vendor|grantor|supplier|dealer|company|business)/.test(k)) return false;
+ return (
+ k === 'name' ||
+ k === 'customername' ||
+ k === 'authorizedpersonname' ||
+ k === 'accountholdername'
+ );
+}
+
+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();
+ }
+
+ // 2a. Personal name (MSD): document may print "Arjun Mehar S/O Radheshyam Mehar" while MSD is "Arjun Mehar".
+ // Strip S/O-style suffixes from the document side, then pass if the full MSD phrase appears as a whole phrase.
+ if (isPersonalHolderNameField(lowerKey)) {
+ const expTrim = trimPatronymicSuffixFromName(expStr).toLowerCase().replace(/\s+/g, ' ').trim();
+ const fndTrim = trimPatronymicSuffixFromName(fndStr).toLowerCase().replace(/\s+/g, ' ').trim();
+ if (expTrim.length >= 2 && fndTrim.length >= 2) {
+ const phraseOk = (hay: string, needle: string) => {
+ if (hay === needle) return true;
+ if (hay.startsWith(needle)) {
+ if (hay.length === needle.length) return true;
+ const next = hay.charAt(needle.length);
+ return /\s|[,;/]/.test(next);
+ }
+ const esc = needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ return new RegExp(`(^|\\s)${esc}(\\s|$)`).test(hay);
+ };
+ if (expTrim.length >= 3 && phraseOk(fndTrim, expTrim)) {
+ return 100;
+ }
+ expStr = expTrim;
+ fndStr = fndTrim;
+ }
+ }
+
+ // 2b. Money: ignore commas, ₹, spaces — compare numeric rupees (aligns browser vs API + Gemini "1,93,533")
+ if (isMoneyFieldKey(lowerKey)) {
+ const expM = normalizeMoney(expStr);
+ const fndM = normalizeMoney(fndStr);
+ if (expM && fndM && expM === fndM) return 100;
+ const a = expM ? Number(expM) : NaN;
+ const b = fndM ? Number(fndM) : NaN;
+ if (!Number.isNaN(a) && !Number.isNaN(b)) {
+ if (Math.abs(a - b) <= 5) return 100;
+ const maxv = Math.max(Math.abs(a), Math.abs(b), 1);
+ const pct = Math.round(100 - Math.min(100, (Math.abs(a - b) / maxv) * 100));
+ return Math.max(0, pct);
+ }
+ }
+
+ // 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 480ecd8..d1febd3 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
@@ -344,6 +356,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(),
+});