From 5102006d9dc49f011b9f4f8db78991fde37f785e Mon Sep 17 00:00:00 2001 From: Shawn Jackson Date: Wed, 13 May 2026 18:36:45 -0700 Subject: [PATCH] Updating docs --- docs/api/custom-maps.md | 578 ++++++++++++++++++++++ docs/api/user-defined-fields.md | 442 +++++++++++++++++ docs/configuration/mapping-layers.md | 10 +- docs/configuration/user-defined-fields.md | 214 ++++++++ docs/self-hosted/installation.md | 13 + docs/self-hosted/laptop.md | 14 +- docs/self-hosted/quick-start.md | 12 + docs/self-hosted/rick.md | 14 +- docs/web-app/call-checkin-timers.md | 195 ++++++++ docs/web-app/communication-tests.md | 225 +++++++++ docs/web-app/custom-maps.md | 406 +++++++++++++++ docs/web-app/department-settings.md | 12 + docs/web-app/dispatch-calls.md | 9 + docs/web-app/mapping.md | 15 +- docs/web-app/routes.md | 228 +++++++++ docs/web-app/templates.md | 2 + docs/web-app/user-defined-fields.md | 196 ++++++++ docs/web-app/weather-alerts.md | 278 +++++++++++ 18 files changed, 2857 insertions(+), 6 deletions(-) create mode 100644 docs/api/custom-maps.md create mode 100644 docs/api/user-defined-fields.md create mode 100644 docs/configuration/user-defined-fields.md create mode 100644 docs/web-app/call-checkin-timers.md create mode 100644 docs/web-app/communication-tests.md create mode 100644 docs/web-app/custom-maps.md create mode 100644 docs/web-app/routes.md create mode 100644 docs/web-app/user-defined-fields.md create mode 100644 docs/web-app/weather-alerts.md diff --git a/docs/api/custom-maps.md b/docs/api/custom-maps.md new file mode 100644 index 0000000..6aaed6a --- /dev/null +++ b/docs/api/custom-maps.md @@ -0,0 +1,578 @@ +--- +sidebar_position: 8 +title: Custom Maps API +--- + +# Custom Maps API + +The Custom Maps API provides programmatic access to department-owned custom maps, floors, zones, and coordinate resolution. All endpoints are scoped to the authenticated user's department. + +**Base URL:** `/api/v4/CustomMaps` + +## Authentication + +All Custom Maps API endpoints require a valid JWT bearer token. See [API Authentication](authentication) for details. + +--- + +## Enumerations + +### Map Type + +| Value | Name | Description | +|-------|------|-------------| +| `0` | Indoor | Building floor plans, arenas, hospitals | +| `1` | Satellite | Custom or classified satellite imagery | +| `2` | Schematic | Engineered drawings, utility diagrams | +| `3` | Event | Temporary event maps; auto-archived after end date | + +### Zone Type + +| Value | Name | Description | +|-------|------|-------------| +| `0` | Room | General room or enclosed space | +| `1` | Hallway | Corridor or passage | +| `2` | StairWell | Stairwell | +| `3` | Elevator | Elevator shaft or lobby | +| `4` | Hazard | Hazmat storage, chemical area, electrical panel | +| `5` | StagingArea | ICS resource or equipment staging | +| `6` | MusterPoint | Evacuation assembly / muster point | +| `7` | Exit | Emergency egress point | +| `8` | Parking | Parking area | +| `9` | Gate | Entry/exit gate (events, secured facilities) | +| `10` | Checkpoint | Security or access checkpoint | +| `11` | Custom | User-defined zone | + +--- + +## Custom Maps + +### Get All Custom Maps + +Returns all active custom maps for the department. + +``` +GET /api/v4/CustomMaps/GetCustomMaps +``` + +**Response:** Array of `CustomMapResult` + +```json +[ + { + "customMapId": "cm_abc123", + "departmentId": 42, + "name": "Station 4 — Main Building", + "description": "Three-story main station building", + "type": 0, + "boundsTopLeftLat": 40.7128, + "boundsTopLeftLng": -74.0060, + "boundsBottomRightLat": 40.7120, + "boundsBottomRightLng": -74.0050, + "imageUrl": "https://storage.resgrid.com/maps/cm_abc123/base.jpg", + "tileUrlTemplate": "https://storage.resgrid.com/tiles/cm_abc123/{z}/{x}/{y}.png", + "defaultZoom": 18, + "minZoom": 15, + "maxZoom": 22, + "isActive": true, + "floorCount": 3, + "addedOn": "2026-02-01T09:00:00Z", + "updatedOn": "2026-03-10T14:30:00Z" + } +] +``` + +--- + +### Get Custom Map + +Returns full detail for a specific custom map including all floors and zone summaries. + +``` +GET /api/v4/CustomMaps/GetCustomMap/{id} +``` + +| Parameter | Type | Location | Description | +|-----------|------|----------|-------------| +| `id` | string | Path | Custom map ID | + +**Response:** `CustomMapDetailResult` + +```json +{ + "customMapId": "cm_abc123", + "departmentId": 42, + "name": "Station 4 — Main Building", + "description": "Three-story main station building", + "type": 0, + "boundsTopLeftLat": 40.7128, + "boundsTopLeftLng": -74.0060, + "boundsBottomRightLat": 40.7120, + "boundsBottomRightLng": -74.0050, + "imageUrl": "https://storage.resgrid.com/maps/cm_abc123/base.jpg", + "tileUrlTemplate": "https://storage.resgrid.com/tiles/cm_abc123/{z}/{x}/{y}.png", + "defaultZoom": 18, + "minZoom": 15, + "maxZoom": 22, + "isActive": true, + "addedOn": "2026-02-01T09:00:00Z", + "updatedOn": "2026-03-10T14:30:00Z", + "floors": [ + { + "customMapFloorId": "cmf_001", + "floorNumber": 1, + "name": "1st Floor", + "imageUrl": "https://storage.resgrid.com/maps/cm_abc123/floor1.jpg", + "tileUrlTemplate": "https://storage.resgrid.com/tiles/cm_abc123/f1/{z}/{x}/{y}.png", + "sortOrder": 1, + "isDefault": true, + "elevationFt": 0.0, + "zoneCount": 12 + } + ] +} +``` + +--- + +### Save Custom Map + +Creates a new custom map. Use multipart/form-data to upload the map image. + +``` +POST /api/v4/CustomMaps/SaveCustomMap +``` + +**Content-Type:** `multipart/form-data` + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `name` | string | Yes | Display name | +| `description` | string | No | Description | +| `type` | int | Yes | Map type enum (0–3) | +| `boundsTopLeftLat` | decimal | Yes | Geo-bounds top-left latitude | +| `boundsTopLeftLng` | decimal | Yes | Geo-bounds top-left longitude | +| `boundsBottomRightLat` | decimal | Yes | Geo-bounds bottom-right latitude | +| `boundsBottomRightLng` | decimal | Yes | Geo-bounds bottom-right longitude | +| `defaultZoom` | int | No | Default zoom level (1–22) | +| `minZoom` | int | No | Minimum zoom level | +| `maxZoom` | int | No | Maximum zoom level | +| `eventStartDate` | string | No | ISO 8601 date; Event type only | +| `eventEndDate` | string | No | ISO 8601 date; Event type only | +| `isActive` | bool | No | Defaults to `true` | +| `imageFile` | file | Yes | PNG, JPG, or SVG — max 50 MB | + +**Response:** `CustomMapResult` (the newly created map, including assigned ID and tile URL once processing completes) + +--- + +### Update Custom Map + +Updates an existing custom map. To replace the image, use the `imageFile` field; omit it to keep the existing image. + +``` +PUT /api/v4/CustomMaps/UpdateCustomMap/{id} +``` + +**Content-Type:** `multipart/form-data` + +Same fields as `SaveCustomMap`. `imageFile` is optional on update. + +**Response:** `CustomMapResult` + +--- + +### Delete Custom Map + +Permanently deletes a custom map and all associated floors, zones, tile files, and share links. + +``` +DELETE /api/v4/CustomMaps/DeleteCustomMap/{id} +``` + +| Parameter | Type | Location | Description | +|-----------|------|----------|-------------| +| `id` | string | Path | Custom map ID | + +**Response:** `200 OK` on success. + +--- + +## Floors + +### Get Floors + +Returns all floors for a custom map. + +``` +GET /api/v4/CustomMaps/GetFloors/{customMapId} +``` + +| Parameter | Type | Location | Description | +|-----------|------|----------|-------------| +| `customMapId` | string | Path | Custom map ID | + +**Response:** Array of `CustomMapFloorResult` + +```json +[ + { + "customMapFloorId": "cmf_001", + "customMapId": "cm_abc123", + "floorNumber": 1, + "name": "1st Floor", + "imageUrl": "https://storage.resgrid.com/maps/cm_abc123/floor1.jpg", + "tileUrlTemplate": "https://storage.resgrid.com/tiles/cm_abc123/f1/{z}/{x}/{y}.png", + "sortOrder": 1, + "isDefault": true, + "elevationFt": 0.0 + }, + { + "customMapFloorId": "cmf_002", + "customMapId": "cm_abc123", + "floorNumber": 2, + "name": "2nd Floor", + "imageUrl": "https://storage.resgrid.com/maps/cm_abc123/floor2.jpg", + "tileUrlTemplate": "https://storage.resgrid.com/tiles/cm_abc123/f2/{z}/{x}/{y}.png", + "sortOrder": 2, + "isDefault": false, + "elevationFt": 12.0 + } +] +``` + +--- + +### Save Floor + +Adds a new floor to a custom map. Use multipart/form-data. + +``` +POST /api/v4/CustomMaps/SaveFloor/{customMapId} +``` + +**Content-Type:** `multipart/form-data` + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `floorNumber` | int | Yes | Numeric floor level (negative for sub-levels) | +| `name` | string | Yes | Floor display name | +| `sortOrder` | int | No | Display sort order in tab bar | +| `isDefault` | bool | No | Set as the default floor | +| `elevationFt` | decimal | No | Physical elevation above sea level in feet | +| `imageFile` | file | Yes | Floor plan image (PNG, JPG, SVG — max 50 MB) | + +**Response:** `CustomMapFloorResult` + +--- + +### Update Floor + +Updates floor metadata. Omit `imageFile` to keep the existing floor image. + +``` +PUT /api/v4/CustomMaps/UpdateFloor/{floorId} +``` + +**Content-Type:** `multipart/form-data` + +Same fields as `SaveFloor`. `imageFile` is optional on update. + +**Response:** `CustomMapFloorResult` + +--- + +### Delete Floor + +Permanently deletes a floor and all of its zones. + +``` +DELETE /api/v4/CustomMaps/DeleteFloor/{floorId} +``` + +| Parameter | Type | Location | Description | +|-----------|------|----------|-------------| +| `floorId` | string | Path | Floor ID | + +**Response:** `200 OK` on success. + +--- + +## Zones + +### Get Zones + +Returns all zones for a floor including full polygon GeoJSON. + +``` +GET /api/v4/CustomMaps/GetZones/{floorId} +``` + +| Parameter | Type | Location | Description | +|-----------|------|----------|-------------| +| `floorId` | string | Path | Floor ID | + +**Response:** Array of `CustomMapZoneResult` + +```json +[ + { + "customMapZoneId": "cmz_001", + "customMapFloorId": "cmf_001", + "name": "Building 1, Room 405a", + "zoneType": 0, + "polygonGeoJson": "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-74.0058,40.7126],[-74.0056,40.7126],[-74.0056,40.7124],[-74.0058,40.7124],[-74.0058,40.7126]]]},\"properties\":{}}", + "color": "#3388ff", + "metadata": "{\"capacity\": \"20\", \"wing\": \"North\"}", + "elevationFt": 45.0, + "isSearchable": true, + "isActive": true + } +] +``` + +--- + +### Get Zone + +Returns a single zone by ID. + +``` +GET /api/v4/CustomMaps/GetZone/{zoneId} +``` + +| Parameter | Type | Location | Description | +|-----------|------|----------|-------------| +| `zoneId` | string | Path | Zone ID | + +**Response:** `CustomMapZoneResult` + +--- + +### Save Zone + +Creates a new zone on a floor. + +``` +POST /api/v4/CustomMaps/SaveZone/{floorId} +``` + +**Content-Type:** `application/json` + +```json +{ + "name": "Building 1, Room 405a", + "zoneType": 0, + "polygonGeoJson": "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-74.0058,40.7126],[-74.0056,40.7126],[-74.0056,40.7124],[-74.0058,40.7124],[-74.0058,40.7126]]]},\"properties\":{}}}", + "color": "#3388ff", + "metadata": "{\"capacity\": \"20\"}", + "elevationFt": 45.0, + "isSearchable": true, + "isActive": true +} +``` + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `name` | string | Yes | Human-readable zone name; used as call location | +| `zoneType` | int | Yes | Zone type enum (0–11) | +| `polygonGeoJson` | string | Yes | GeoJSON Feature with a Polygon geometry; coordinates must be real-world lat/lng | +| `color` | string | No | Hex color code for polygon fill | +| `metadata` | string | No | JSON string of key/value metadata | +| `elevationFt` | decimal | No | Zone elevation in feet | +| `isSearchable` | bool | No | Include zone in map search | +| `isActive` | bool | No | Defaults to `true` | + +**Response:** `CustomMapZoneResult` + +--- + +### Update Zone + +Updates an existing zone. All fields may be updated including the polygon geometry. + +``` +PUT /api/v4/CustomMaps/UpdateZone/{zoneId} +``` + +**Content-Type:** `application/json` + +Same body shape as `SaveZone`. + +**Response:** `CustomMapZoneResult` + +--- + +### Delete Zone + +Permanently deletes a zone. + +``` +DELETE /api/v4/CustomMaps/DeleteZone/{zoneId} +``` + +| Parameter | Type | Location | Description | +|-----------|------|----------|-------------| +| `zoneId` | string | Path | Zone ID | + +**Response:** `200 OK` on success. + +--- + +## Coordinate Resolution + +### Resolve Coordinate to Zone + +Given a custom map, floor, and real-world latitude/longitude, performs a point-in-polygon test against all active zones on that floor and returns the matching zone name. + +This endpoint is used by mobile apps and CAD integrations to automatically resolve a GPS position to a room or area name. + +``` +POST /api/v4/CustomMaps/ResolveCoordinate +``` + +**Content-Type:** `application/json` + +```json +{ + "customMapId": "cm_abc123", + "floorId": "cmf_001", + "latitude": 40.7125, + "longitude": -74.0057 +} +``` + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `customMapId` | string | Yes | Custom map ID | +| `floorId` | string | Yes | Floor ID to test against | +| `latitude` | decimal | Yes | Point latitude | +| `longitude` | decimal | Yes | Point longitude | + +**Response:** `CoordinateResolveResult` + +```json +{ + "matched": true, + "customMapZoneId": "cmz_001", + "zoneName": "Building 1, Room 405a", + "zoneType": 0, + "floorName": "1st Floor", + "mapName": "Station 4 — Main Building" +} +``` + +If no zone matches: + +```json +{ + "matched": false, + "customMapZoneId": null, + "zoneName": null, + "zoneType": null, + "floorName": "1st Floor", + "mapName": "Station 4 — Main Building" +} +``` + +--- + +## Map Metadata in GetMapDataAndMarkers + +The existing map data endpoint includes a `customMaps` collection in its response when the department has active custom maps. This allows map clients to discover available custom maps without a separate API call. + +``` +GET /api/v4/Mapping/GetMapDataAndMarkers +``` + +The response includes: + +```json +{ + "customMaps": [ + { + "customMapId": "cm_abc123", + "name": "Station 4 — Main Building", + "type": 0, + "boundsTopLeftLat": 40.7128, + "boundsTopLeftLng": -74.0060, + "boundsBottomRightLat": 40.7120, + "boundsBottomRightLng": -74.0050, + "defaultZoom": 18, + "tileUrlTemplate": "https://storage.resgrid.com/tiles/cm_abc123/{z}/{x}/{y}.png", + "floorCount": 3, + "defaultFloorId": "cmf_001" + } + ] +} +``` + +--- + +## Offline Sync (Mobile) + +Mobile clients use a version-check endpoint before deciding whether to download map data. + +### Get Map Version Manifest + +Returns a lightweight manifest of all custom maps and their current versions. Mobile apps compare this against their local cache and download only what has changed. + +``` +GET /api/v4/CustomMaps/GetVersionManifest +``` + +**Response:** `CustomMapVersionManifest` + +```json +{ + "generatedAt": "2026-03-14T08:00:00Z", + "maps": [ + { + "customMapId": "cm_abc123", + "mapVersion": 7, + "floors": [ + { + "customMapFloorId": "cmf_001", + "floorVersion": 3, + "zoneVersion": 12 + }, + { + "customMapFloorId": "cmf_002", + "floorVersion": 1, + "zoneVersion": 5 + } + ] + } + ] +} +``` + +The mobile app: +1. Calls `GetVersionManifest` on launch or foreground. +2. Compares each `mapVersion`, `floorVersion`, and `zoneVersion` to the locally cached values. +3. For any entry with a higher server version, downloads the relevant floor image and/or zone GeoJSON. +4. Stores updated data in local storage for offline use. + +--- + +## Error Responses + +All endpoints return standard HTTP status codes with a JSON error body: + +```json +{ + "error": "CustomMapNotFound", + "message": "No custom map with ID 'cm_xyz' was found for this department.", + "statusCode": 404 +} +``` + +| Status Code | Meaning | +|-------------|---------| +| `400` | Invalid request body or missing required fields | +| `401` | Missing or expired authentication token | +| `403` | Caller does not have permission for this operation | +| `404` | Requested map, floor, or zone not found for this department | +| `413` | Uploaded image exceeds the 50 MB limit | +| `422` | Invalid GeoJSON polygon; polygon must be closed and have ≥ 3 vertices | +| `500` | Internal server error | diff --git a/docs/api/user-defined-fields.md b/docs/api/user-defined-fields.md new file mode 100644 index 0000000..09511a6 --- /dev/null +++ b/docs/api/user-defined-fields.md @@ -0,0 +1,442 @@ +--- +sidebar_position: 5 +--- + +# User Defined Fields API + +The User Defined Fields API provides full programmatic access to UDF definitions, field configurations, field values, and the mobile rendering schema. All endpoints are scoped to the authenticated user's department. + +**Base URL:** `/api/v4/UserDefinedFields` + +## Authentication + +All User Defined Fields API endpoints require a valid JWT token. See [API Authentication](authentication) for details. + +## Entity Types + +The `entityType` parameter is an integer enum used across all endpoints: + +| Value | Entity | +|-------|--------| +| `0` | Call | +| `1` | Personnel | +| `2` | Unit | +| `3` | Contact | + +## Field Data Types + +The `fieldDataType` value on field objects is an integer enum: + +| Value | Type | Description | +|-------|------|-------------| +| `0` | Text | Single-line text | +| `1` | Number | Integer | +| `2` | Decimal | Floating-point number | +| `3` | Boolean | True/false checkbox | +| `4` | Date | Date only (ISO 8601: `YYYY-MM-DD`) | +| `5` | DateTime | Date and time (ISO 8601: `YYYY-MM-DDTHH:MM:SS`) | +| `6` | Dropdown | Single-select; requires `options` in validation rules | +| `7` | MultiSelect | Multi-select; requires `options` in validation rules | +| `8` | Email | Email address | +| `9` | Phone | Phone number | +| `10` | URL | URL | + +--- + +## Definitions + +### Get Active Definition + +Returns the currently active UDF definition and its fields for the specified entity type. + +``` +GET /api/v4/UserDefinedFields/{entityType} +``` + +| Parameter | Type | Location | Description | +|-----------|------|----------|-------------| +| `entityType` | int | Path | Entity type enum value | + +**Response:** `UdfDefinitionResult` + +```json +{ + "udfDefinitionId": "def_abc123", + "departmentId": 42, + "entityType": 0, + "version": 3, + "isActive": true, + "createdOn": "2026-03-01T10:00:00Z", + "createdBy": "user_xyz", + "updatedOn": "2026-03-05T14:30:00Z", + "updatedBy": "user_xyz", + "fields": [ + { + "udfFieldId": "fld_001", + "udfDefinitionId": "def_abc123", + "name": "chemical_class", + "label": "Chemical Class", + "description": "Select the UN hazard class for the primary chemical involved.", + "placeholder": "Select a class...", + "fieldDataType": 6, + "isRequired": true, + "isReadOnly": false, + "defaultValue": null, + "validationRules": { + "options": [ + { "key": "class1", "label": "Class 1 – Explosives" }, + { "key": "class2", "label": "Class 2 – Gases" }, + { "key": "class3", "label": "Class 3 – Flammable Liquids" } + ], + "customErrorMessage": null + }, + "sortOrder": 1, + "groupName": "Hazmat Details", + "isVisibleOnMobile": true, + "isVisibleOnReports": true, + "isEnabled": true + } + ] +} +``` + +Returns `404 Not Found` if no active definition exists for the entity type. + +--- + +### Create or Update Definition + +Creates a new definition (or a new version of an existing definition) for the specified entity type. The previous active version is automatically marked inactive. + +``` +POST /api/v4/UserDefinedFields +``` + +**Authorization:** Department Admin + +**Request Body:** `UdfDefinitionInput` + +```json +{ + "entityType": 0, + "fields": [ + { + "name": "chemical_class", + "label": "Chemical Class", + "description": "Select the UN hazard class for the primary chemical involved.", + "placeholder": "Select a class...", + "fieldDataType": 6, + "isRequired": true, + "isReadOnly": false, + "defaultValue": null, + "validationRules": { + "options": [ + { "key": "class1", "label": "Class 1 – Explosives" }, + { "key": "class2", "label": "Class 2 – Gases" } + ] + }, + "sortOrder": 1, + "groupName": "Hazmat Details", + "isVisibleOnMobile": true, + "isVisibleOnReports": true, + "isEnabled": true + } + ] +} +``` + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `entityType` | int | Yes | Entity type enum value | +| `fields` | array | Yes | Array of field definitions (see [Field Input](#field-input)) | + +**Response:** `UdfDefinitionResult` — the newly created definition with the incremented version number. + +**Audit:** Creates a `UdfDefinitionCreated` or `UdfDefinitionUpdated` audit log entry. + +--- + +### Delete Field from Active Definition + +Removes a single field from the active definition by creating a new definition version without that field. + +``` +DELETE /api/v4/UserDefinedFields/Fields/{fieldId} +``` + +**Authorization:** Department Admin + +| Parameter | Type | Location | Description | +|-----------|------|----------|-------------| +| `fieldId` | string | Path | The `udfFieldId` of the field to remove | + +**Response:** `UdfDefinitionResult` — the new definition version without the removed field. + +**Audit:** Creates a `UdfFieldRemoved` audit log entry. + +:::note Historical Preservation +Removing a field creates a new definition version. Existing `UdfFieldValue` records for the removed field in prior entity records are preserved in the database and remain accessible via historical queries. +::: + +--- + +## Field Values + +### Get Values for an Entity + +Returns all UDF field values saved for a specific entity record, keyed to the definition version active at the time the values were saved. + +``` +GET /api/v4/UserDefinedFields/Values/{entityType}/{entityId} +``` + +| Parameter | Type | Location | Description | +|-----------|------|----------|-------------| +| `entityType` | int | Path | Entity type enum value | +| `entityId` | string | Path | The ID of the entity record (e.g., call ID, user ID, unit ID, contact ID) | + +**Response:** `UdfFieldValuesResult` + +```json +{ + "entityId": "call_789", + "entityType": 0, + "udfDefinitionId": "def_abc123", + "definitionVersion": 3, + "values": [ + { + "udfFieldValueId": "val_001", + "udfFieldId": "fld_001", + "udfDefinitionId": "def_abc123", + "entityId": "call_789", + "entityType": 0, + "value": "class3", + "createdOn": "2026-03-06T09:15:00Z", + "createdBy": "user_xyz", + "updatedOn": "2026-03-06T09:15:00Z", + "updatedBy": "user_xyz" + } + ] +} +``` + +Returns `404 Not Found` if the entity has no saved UDF values. + +--- + +### Save Values for an Entity + +Saves (creates or updates) UDF field values for a specific entity record. Server-side validation is applied against the field's `validationRules` before saving. + +``` +POST /api/v4/UserDefinedFields/Values +``` + +**Request Body:** `UdfFieldValuesInput` + +```json +{ + "entityType": 0, + "entityId": "call_789", + "values": [ + { + "udfFieldId": "fld_001", + "value": "class3" + }, + { + "udfFieldId": "fld_002", + "value": "150" + } + ] +} +``` + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `entityType` | int | Yes | Entity type enum value | +| `entityId` | string | Yes | The ID of the entity record | +| `values` | array | Yes | Array of field value pairs | +| `values[].udfFieldId` | string | Yes | The field's `udfFieldId` | +| `values[].value` | string | Yes | The value as a string (see type-specific formatting below) | + +#### Value Formatting by Data Type + +| Data Type | Expected Format | +|-----------|----------------| +| Text, Email, Phone, URL | Plain string | +| Number | Integer string (e.g., `"42"`) | +| Decimal | Decimal string (e.g., `"3.14"`) | +| Boolean | `"true"` or `"false"` | +| Date | ISO 8601 date string (`"2026-03-01"`) | +| DateTime | ISO 8601 datetime string (`"2026-03-01T14:30:00"`) | +| Dropdown | The option **key** string | +| MultiSelect | Comma-separated option **key** strings (e.g., `"key1,key2"`) | + +**Response:** `UdfFieldValuesResult` — the saved values. + +**Validation Errors:** Returns `400 Bad Request` with a validation error object if any field values fail server-side validation: + +```json +{ + "errors": { + "fld_001": ["Chemical Class is required."], + "fld_002": ["Quantity must be between 0 and 10000."] + } +} +``` + +**Audit:** Creates a `UdfFieldValueSaved` audit log entry capturing the entity type, entity ID, and changed field values (old → new). + +--- + +## Mobile Schema + +The schema endpoints return a structured JSON representation of the UDF definition and fields that mobile applications (React Native) use to dynamically render form controls with native validation. + +### Get Schema (Empty Form) + +Returns the field schema for an entity type without any pre-populated values. Use this when rendering a **new record** form on mobile. + +``` +GET /api/v4/UserDefinedFields/Schema/{entityType} +``` + +| Parameter | Type | Location | Description | +|-----------|------|----------|-------------| +| `entityType` | int | Path | Entity type enum value | + +**Response:** `UdfSchemaResult` + +```json +{ + "definitionId": "def_abc123", + "entityType": 0, + "version": 3, + "groups": [ + { + "groupName": "Hazmat Details", + "fields": [ + { + "fieldId": "fld_001", + "name": "chemical_class", + "label": "Chemical Class", + "description": "Select the UN hazard class for the primary chemical involved.", + "placeholder": "Select a class...", + "fieldDataType": 6, + "isRequired": true, + "isReadOnly": false, + "isVisibleOnMobile": true, + "currentValue": null, + "defaultValue": null, + "validationRules": { + "options": [ + { "key": "class1", "label": "Class 1 – Explosives" }, + { "key": "class2", "label": "Class 2 – Gases" }, + { "key": "class3", "label": "Class 3 – Flammable Liquids" } + ] + } + } + ] + } + ] +} +``` + +Returns `404 Not Found` if no active definition exists for the entity type. + +--- + +### Get Schema (Pre-Populated) + +Returns the field schema pre-populated with an existing entity record's saved values. Use this when rendering an **edit record** form on mobile. + +``` +GET /api/v4/UserDefinedFields/Schema/{entityType}/{entityId} +``` + +| Parameter | Type | Location | Description | +|-----------|------|----------|-------------| +| `entityType` | int | Path | Entity type enum value | +| `entityId` | string | Path | The ID of the entity record | + +**Response:** `UdfSchemaResult` — same structure as the empty schema, but all `currentValue` fields are populated with the entity's saved values. + +--- + +## Validation Rules Object + +The `validationRules` property is a JSON object on each field. Not all properties apply to every field data type. + +```json +{ + "minLength": 2, + "maxLength": 100, + "regex": "^[A-Z]{2,5}$", + "regexErrorMessage": "Must be 2–5 uppercase letters.", + "minValue": 0, + "maxValue": 10000, + "options": [ + { "key": "opt1", "label": "Option One" }, + { "key": "opt2", "label": "Option Two" } + ], + "customErrorMessage": "This field is required for hazmat incidents." +} +``` + +| Property | Applies To | Description | +|----------|-----------|-------------| +| `minLength` | Text, Email, Phone, URL | Minimum character count | +| `maxLength` | Text, Email, Phone, URL | Maximum character count | +| `regex` | Text | Regex pattern the value must match | +| `regexErrorMessage` | Text | Error message for regex mismatch | +| `minValue` | Number, Decimal | Minimum numeric value | +| `maxValue` | Number, Decimal | Maximum numeric value | +| `options` | Dropdown, MultiSelect | Array of `{ key, label }` pairs representing valid choices | +| `customErrorMessage` | All | Overrides the default validation error message | + +--- + +## Integration with Entity Endpoints + +UDF field values are also accessible via the standard entity API endpoints. The response models for Calls, Personnel, Units, and Contacts include a `udfValues` array when UDF values exist for that record. + +### Call Responses + +`CallResult` objects include a `udfValues` field: + +```json +{ + "callId": "call_789", + "name": "Structure Fire", + ... + "udfValues": [ + { "udfFieldId": "fld_001", "name": "chemical_class", "label": "Chemical Class", "value": "class3" } + ] +} +``` + +### Submitting UDF Values via Entity Endpoints + +When creating or updating a Call, Personnel record, Unit, or Contact via their respective API endpoints, include a `udfValues` array in the request body: + +```json +{ + "name": "Structure Fire", + "nature": "Working fire", + ... + "udfValues": [ + { "udfFieldId": "fld_001", "value": "class3" }, + { "udfFieldId": "fld_002", "value": "150" } + ] +} +``` + +Values are validated server-side and saved atomically with the parent record. Validation errors for UDF fields are included in the `400 Bad Request` response alongside standard field errors. + +--- + +## Related + +- [User Defined Fields Configuration](../configuration/user-defined-fields.md) — Admin guide for configuring field definitions, data types, and validation rules +- [User Defined Fields (Web App)](../web-app/user-defined-fields.md) — Web application usage guide +- [API Authentication](authentication) — How to authenticate API requests diff --git a/docs/configuration/mapping-layers.md b/docs/configuration/mapping-layers.md index 2778ebe..66228d6 100644 --- a/docs/configuration/mapping-layers.md +++ b/docs/configuration/mapping-layers.md @@ -6,6 +6,10 @@ sidebar_position: 18 Mapping Layers in Resgrid allow you to add custom geographic data overlays to your department's maps. Layers are defined using GeoJSON data and can represent district boundaries, response zones, hazard areas, water sources, or any other geographic information relevant to your operations. In addition to layers, you can also configure **Points of Interest (POIs)** to mark specific locations on the map. +:::tip Custom Maps for Building Floor Plans +If you need to upload a building floor plan, indoor map, or venue schematic — and draw named polygon zones (e.g., "Room 405a", "Staging Area B") on top of it — use **Custom Maps** instead of a standard layer. Custom Maps support multi-floor images geo-projected onto real-world coordinates, zone-based call locations, pre-plan attachments, and offline mobile caching. See the [Custom Maps](../web-app/custom-maps) documentation. +::: + ## Why Mapping Layers Matter Standard maps show streets and terrain but lack the operational context your department needs. Mapping layers let you overlay your own geographic data — such as first-due response districts, hydrant locations, pre-plan locations, mutual aid boundaries, or flood zones — directly onto the maps your personnel use for dispatch and response. POIs mark important fixed locations that responders need to quickly find during operations. @@ -148,4 +152,8 @@ The mapping module also supports routing: | "File is too large" | POI import files must be smaller than 10 MB | | GeoJSON parse error | Validate your GeoJSON at geojson.io before pasting into Resgrid | | POIs not showing on map | Ensure the POI type is not deleted and POIs have valid coordinates | -| Map centered on wrong location | Update department address or Map Center settings in Department Settings | \ No newline at end of file +| Map centered on wrong location | Update department address or Map Center settings in Department Settings | + +## See Also + +- [Custom Maps](../web-app/custom-maps) — Upload floor plans, schematics, and event maps; draw named polygon zones; use zones as call locations with pre-plan attachments \ No newline at end of file diff --git a/docs/configuration/user-defined-fields.md b/docs/configuration/user-defined-fields.md new file mode 100644 index 0000000..515017c --- /dev/null +++ b/docs/configuration/user-defined-fields.md @@ -0,0 +1,214 @@ +--- +sidebar_position: 23 +--- + +# User Defined Fields + +User Defined Fields (UDFs) allow departments to extend the built-in data model with custom fields tailored to their specific operational, compliance, or reporting requirements. UDFs can be added to **Calls**, **Personnel**, **Units**, and **Contacts**, and are rendered automatically in web forms, mobile apps, and exports. + +## Why User Defined Fields Matter + +Every department captures information that the standard system fields do not cover. A hazmat team may need a "Chemical Class" field on calls. A government agency may require a "Certification Level" field on personnel records. A fire department may track a "Service Date" on each unit. UDFs let department admins define these fields without any custom development, while preserving full audit trails and historical data integrity. + +## Entity Types + +UDFs are organized by entity type. Each entity type has its own independent set of field definitions: + +| Entity Type | Description | +|-------------|-------------| +| **Call** (0) | Fields added to new and existing call records | +| **Personnel** (1) | Fields added to personnel/member profiles | +| **Unit** (2) | Fields added to unit (apparatus) records | +| **Contact** (3) | Fields added to external contact records | + +## Definitions and Versioning + +A **UDF Definition** is the versioned container that holds a set of fields for a given entity type. Every time you modify the field configuration for an entity type, Resgrid automatically creates a **new version** of the definition rather than overwriting the existing one. + +### Why Versioning Matters + +- Existing records (call values, personnel values, etc.) remain linked to the version of the definition that was active when they were saved. Historical data is never altered. +- New and edited records always use the currently active version. +- Reports and exports query across all versions, so historical UDF values remain accessible regardless of subsequent definition changes. + +:::info Immutable Versions +You cannot edit a previous version. Each save of a definition creates a new version and marks the old version inactive. The version number is displayed on the management page so you can track definition history. +::: + +## Managing User Defined Fields + +Navigate to **Department → User Defined Fields** to access the UDF management page. + +### Selecting an Entity Type + +Use the **Entity Type** selector at the top of the page to switch between Call, Personnel, Unit, and Contact field configurations. Each entity type is managed independently. + +### Adding a Field + +Click **Add Field** to open the field editor. Configure the field settings described in [Field Settings](#field-settings) below, then click **Save**. The system will create a new definition version with the added field. + +### Editing a Field + +Click the **Edit** button next to any existing field. Update the field settings and click **Save**. A new definition version is created. Existing saved values for the affected entity records are preserved and remain linked to the previous version. + +### Removing a Field + +Click **Remove** next to a field to remove it from the active definition. A new version is created without that field. Existing saved values for that field in historical records are preserved. + +### Reordering Fields + +Drag fields up or down in the field list to adjust their display order. Click **Save Order** to apply. A new definition version is created reflecting the new sort order. + +--- + +## Field Settings + +Each field in a UDF definition has the following configurable settings: + +### Basic Settings + +| Setting | Required | Description | +|---------|----------|-------------| +| **Name** | Yes | Internal name used as the data key (no spaces; e.g., `chemical_class`). Cannot be changed after the field is created in a version. | +| **Label** | Yes | Human-readable label displayed on forms and reports (e.g., "Chemical Class"). | +| **Description** | No | Tooltip or help text displayed next to the field on forms. Useful for compliance-heavy workflows where field meaning must be unambiguous. | +| **Placeholder** | No | Placeholder text shown inside the input before the user types. | +| **Group Name** | No | Section heading for grouping related fields together. Fields with the same Group Name are rendered inside a shared section/fieldset. | +| **Sort Order** | Yes | Numeric position of the field within its group. Lower numbers appear first. | + +### Data Type + +The **Field Data Type** determines the input control rendered and the validation rules available: + +| Data Type | Value | Description | +|-----------|-------|-------------| +| **Text** | 0 | Single-line text input | +| **Number** | 1 | Integer numeric input | +| **Decimal** | 2 | Floating-point numeric input | +| **Boolean** | 3 | Checkbox (true/false) | +| **Date** | 4 | Date picker (date only) | +| **DateTime** | 5 | Date and time picker | +| **Dropdown** | 6 | Single-select dropdown; options defined in Validation Rules | +| **MultiSelect** | 7 | Multi-select list; options defined in Validation Rules | +| **Email** | 8 | Text input with email format validation | +| **Phone** | 9 | Text input with phone format validation | +| **URL** | 10 | Text input with URL format validation | + +### Behavior Flags + +| Setting | Default | Description | +|---------|---------|-------------| +| **Is Required** | Off | When enabled, the form cannot be submitted without a value for this field. | +| **Is Read-Only** | Off | When enabled, the field is displayed on forms but cannot be edited by users. Useful for values populated by integrations or workflows (e.g., CAD system sync). | +| **Is Visible on Mobile** | On | When disabled, the field is hidden in mobile apps (Responder, Dispatch app). | +| **Is Visible on Reports** | On | When disabled, the field is excluded from exports and reports. | +| **Is Enabled** | On | When disabled, the field is temporarily hidden from forms without creating a new version or losing the field definition. Useful during phased rollouts or for seasonal fields. | + +:::tip Is Enabled vs. Remove +Use **Is Enabled = Off** to temporarily hide a field without losing its configuration. Use **Remove** only when you are certain you no longer need the field in future definitions. Historical values are preserved in both cases. +::: + +--- + +## Validation Rules + +Validation rules are configured per field and control both server-side and client-side validation. The available rules depend on the field's data type. + +### Text, Email, Phone, URL Fields + +| Rule | Description | +|------|-------------| +| **Min Length** | Minimum character count required | +| **Max Length** | Maximum character count allowed | +| **Regex** | Regular expression pattern the value must match | +| **Regex Error Message** | Custom message displayed when the regex pattern fails | +| **Custom Error Message** | Override for the default required/format error message | + +### Number and Decimal Fields + +| Rule | Description | +|------|-------------| +| **Min Value** | Minimum allowed numeric value | +| **Max Value** | Maximum allowed numeric value | +| **Custom Error Message** | Override for the default range error message | + +### Dropdown and MultiSelect Fields + +| Rule | Description | +|------|-------------| +| **Options** | List of key/label pairs defining the valid choices. Each option has a **Key** (stored value) and a **Label** (displayed text). | +| **Custom Error Message** | Override for the default required error message | + +:::info Dual-Layer Validation +Validation is enforced on both the client and the server. Web forms use HTML5 validation attributes and data-attributes for in-browser validation before submission. The server independently validates all submitted values against the stored rules, preventing circumvention. Mobile apps receive the same rules via the [Schema API endpoint](../api/user-defined-fields.md#get-schema) and implement equivalent native validation. +::: + +--- + +## Field Grouping + +Fields with the same **Group Name** are visually grouped together in a labeled section on the form. Use grouping to organize related fields into logical categories, such as: + +- "Hazmat Details" (for hazmat call fields) +- "Patient Information" (for EMS calls) +- "Vehicle Specifications" (for apparatus/unit records) +- "Compliance Fields" (for government-required data) + +Fields without a Group Name are rendered in an ungrouped section at the top of the UDF area. + +--- + +## Read-Only Fields + +Fields marked **Is Read-Only** are displayed with a non-editable input on forms. These fields are typically populated by: + +- **Workflow actions** — A workflow step can write a value to a UDF field after a trigger event. +- **External integrations** — A CAD system or records management system can populate fields via the API. +- **Computed defaults** — A default value set in the field definition. + +Read-only fields are still included in exports and reports if **Is Visible on Reports** is enabled. + +--- + +## Default Values + +The **Default Value** setting pre-populates a field when a new record is created. For Dropdown and MultiSelect fields, the default value must match one of the configured option keys. For Date and DateTime fields, the value must be in ISO 8601 format (`YYYY-MM-DD` or `YYYY-MM-DDTHH:MM:SS`). + +--- + +## Audit Trail + +All UDF configuration changes are recorded in the department audit log: + +| Event | Description | +|-------|-------------| +| UDF Definition Created | A new definition (first version) was created for an entity type | +| UDF Definition Updated | A new version was created due to a field add, edit, remove, or reorder | +| UDF Field Added | A new field was added to the active definition | +| UDF Field Updated | A field's settings were changed | +| UDF Field Removed | A field was removed from the definition | +| UDF Field Value Saved | UDF values were saved for a specific entity record | + +Navigate to **Department → Audit Log** and filter by the UDF event types to review configuration history. + +--- + +## Permissions + +UDF management is restricted to **Department Admins** by default. There are no per-user UDF management permissions; if a user has Department Admin access, they can manage UDF definitions for all entity types. + +UDF field values on records (calls, personnel, units, contacts) are subject to the same create/edit permissions as the parent record. Users who can edit a call can also edit that call's UDF field values. + +--- + +## Integration with Reports and Exports + +UDF fields are automatically included in reports and data exports as additional dynamic columns. All versions of a definition are queried so that historical records display the UDF values that were active at the time of the record's creation. No additional configuration is required to include UDF columns in exports. + +--- + +## Related + +- [Forms](forms.md) — Visual form builder for extending the New Call workflow with custom form layouts +- [Workflows](workflows.md) — Event-driven automations that can read and write UDF field values +- [User Defined Fields API](../api/user-defined-fields.md) — REST API for managing definitions and values programmatically diff --git a/docs/self-hosted/installation.md b/docs/self-hosted/installation.md index 72e7e3f..c12e69a 100644 --- a/docs/self-hosted/installation.md +++ b/docs/self-hosted/installation.md @@ -107,6 +107,19 @@ You should now have a folder called resgrid in your current directory. ```bash cd resgrid ``` + +3. Docker Hub Authentication + +Resgrid container images are hosted on Docker Hub under the `dhi.io` repository. You will need a Docker Hub account (free) to pull the images. If you don't have one, create a free account at [https://hub.docker.com](https://hub.docker.com). + +Once you have an account, log in from your terminal before running the containers: + +```bash +docker login dhi.io +``` + +Enter your Docker Hub username and password when prompted. + You will need to clone this repo into all the servers running the Resgrid containers. But we are going to edit the .env file here and it'll need to be copied to every server running the Resgrid containers, this keeps the Resgrid settings consistent for every server. diff --git a/docs/self-hosted/laptop.md b/docs/self-hosted/laptop.md index da2a4ea..4fb3c4a 100644 --- a/docs/self-hosted/laptop.md +++ b/docs/self-hosted/laptop.md @@ -71,7 +71,19 @@ You should now have a folder called resgrid in your current directory. cd resgrid ``` -8. Import the osm.pbf you downloaded and placed in your home directory into the tile server. Change /home/yourname/yourregion.osm.pbf in the command below to the correct home directory name (yourname) and the name of the region file you downloaded (yourregion). +8. Docker Hub Authentication + +Resgrid container images are hosted on Docker Hub under the `dhi.io` repository. You will need a Docker Hub account (free) to pull the images. If you don't have one, create a free account at [https://hub.docker.com](https://hub.docker.com). + +Once you have an account, log in from your terminal before running the containers: + +```bash +docker login dhi.io +``` + +Enter your Docker Hub username and password when prompted. + +9. Import the osm.pbf you downloaded and placed in your home directory into the tile server. Change /home/yourname/yourregion.osm.pbf in the command below to the correct home directory name (yourname) and the name of the region file you downloaded (yourregion). ```bash docker run \ diff --git a/docs/self-hosted/quick-start.md b/docs/self-hosted/quick-start.md index e9a3729..00181a9 100644 --- a/docs/self-hosted/quick-start.md +++ b/docs/self-hosted/quick-start.md @@ -93,6 +93,18 @@ Open the resgrid directory: cd resgrid ``` +## Docker Hub Authentication + +Resgrid container images are hosted on Docker Hub under the `dhi.io` repository. You will need a Docker Hub account (free) to pull the images. If you don't have one, create a free account at [https://hub.docker.com](https://hub.docker.com). + +Once you have an account, log in from your terminal before running the containers: + +```bash +docker login dhi.io +``` + +Enter your Docker Hub username and password when prompted. + ## Setting Environment Variables Edit the environment file: diff --git a/docs/self-hosted/rick.md b/docs/self-hosted/rick.md index bda826c..bb370cc 100644 --- a/docs/self-hosted/rick.md +++ b/docs/self-hosted/rick.md @@ -71,7 +71,19 @@ You should now have a folder called resgrid in your current directory. cd resgrid ``` -8. Import the osm.pbf you downloaded and placed in your home directory into the tile server. Change /home/yourname/yourregion.osm.pbf in the command below to the correct home directory name (yourname) and the name of the region file you downloaded (yourregion). +8. Docker Hub Authentication + +Resgrid container images are hosted on Docker Hub under the `dhi.io` repository. You will need a Docker Hub account (free) to pull the images. If you don't have one, create a free account at [https://hub.docker.com](https://hub.docker.com). + +Once you have an account, log in from your terminal before running the containers: + +```bash +docker login dhi.io +``` + +Enter your Docker Hub username and password when prompted. + +9. Import the osm.pbf you downloaded and placed in your home directory into the tile server. Change /home/yourname/yourregion.osm.pbf in the command below to the correct home directory name (yourname) and the name of the region file you downloaded (yourregion). ```bash docker run \ diff --git a/docs/web-app/call-checkin-timers.md b/docs/web-app/call-checkin-timers.md new file mode 100644 index 0000000..b8f0585 --- /dev/null +++ b/docs/web-app/call-checkin-timers.md @@ -0,0 +1,195 @@ +--- +sidebar_position: 40 +title: Call Check-In Timers +--- + +# Call Check-In Timers + +The Call Check-In Timer system is an accountability and safety feature that tracks whether personnel, units, or operational roles have checked in within required time intervals while a call (incident) is active. Timers visually escalate through **Green / Warning / Critical** states, helping incident commanders maintain situational awareness and comply with safety protocols such as NFPA 1561 PAR checks. + +**Navigation:** Dispatch → View Call → Check-In Timers panel + +--- + +## Overview + +When check-in timers are enabled on a call, a **Check-In Timers** panel appears on the View Call page. The panel shows a table with: + +- **Target** — the timer type (e.g., PAR, Rehab, IC) +- **Time Remaining** — a live countdown updated every second in the browser +- **Status** — a color-coded badge (Green, Warning, or Critical) +- **Check In** — a button to record a check-in and reset the timer + +A collapsible **Check-In History** section below the timers panel shows all past check-in records for the call, including timestamp, who performed it, check-in type, GPS location, and any notes. + +--- + +## Timer Target Types + +Timers can be configured for seven target types: + +| Target Type | Description | +|-------------|-------------| +| **Personnel** | General check-in for all dispatched personnel | +| **UnitType** | Check-in scoped to a specific unit type (e.g., Engine, Ladder) | +| **IC** | Incident Commander accountability check | +| **PAR** | Personnel Accountability Report | +| **HazmatExposure** | Hazmat exposure time tracking | +| **SectorRotation** | Sector or division rotation timer | +| **Rehab** | Rehabilitation rotation timer | + +--- + +## Timer Status and Visual Indicators + +Each timer transitions through three states based on how much time has elapsed since the last check-in (or the call's start time if no check-in has been recorded): + +| Status | Condition | +|--------|-----------| +| **Green** | Elapsed time is less than the configured duration | +| **Warning** | Elapsed time has exceeded the duration but is within the warning threshold | +| **Critical** | Elapsed time has exceeded both the duration and the warning threshold | + +The browser updates countdown timers every second for a responsive live display. Status badges recalculate in real-time as the countdown progresses. + +--- + +## Configuration + +### Enabling Check-In Timers for a Call + +Check-in timers are enabled per-call using the **Enable Check-In Timers** checkbox on the New Call and Update Call forms. If the department has **Auto-Enable Check-In Timers for New Calls** turned on in Dispatch Settings, this checkbox defaults to checked for every new call. Dispatchers can toggle it manually at call creation or any time the call is updated. + +Call Quick Templates can also store a check-in timer preference. When a template is applied to a new call, the template's setting pre-fills the checkbox. See [Templates](./templates) for details. + +### Default Timer Configurations + +**Authorization:** Department Admin — Dispatch Settings + +Department admins configure default timers that apply to all calls (unless overridden). Each default timer config specifies: + +| Field | Description | +|-------|-------------| +| **Timer Target Type** | Which target type this timer applies to (see table above) | +| **Unit Type** | Optional; restricts the timer to a specific unit type (only used when target type is UnitType) | +| **Duration (minutes)** | How often a check-in is required (e.g., 30 minutes) | +| **Warning Threshold (minutes)** | Additional minutes after the duration before the timer goes Critical | +| **Enabled** | Whether this config is active | +| **Active For States** | Optional; comma-separated list of personnel/unit state IDs — if set, the timer only activates when a dispatched entity is in one of these states | + +Admins can add and delete default timer configs from the **Dispatch Settings** section of **Department Settings**. + +### Call-Type and Priority Overrides + +Overrides replace default configs for specific call types and/or priorities. An override has all the same fields as a default config plus: + +| Field | Description | +|-------|-------------| +| **Call Type** | Optional; match against the call's type | +| **Call Priority** | Optional; match against the call's priority | + +When resolving timers for a call, overrides are scored for specificity: + +| Score | Condition | +|-------|-----------| +| **3** | Both Call Type and Call Priority match | +| **2** | Only Call Type matches | +| **1** | Only Call Priority matches | +| **0** | Neither specified (catch-all override) | + +Higher-scoring overrides take precedence. An override replaces the default config for the same target type and unit type combination. + +### Active-For-States Filtering + +The **Active For States** setting restricts when a timer is active based on the current state of dispatched entities: + +- **Personnel-based timers** (Personnel, IC, PAR, HazmatExposure, SectorRotation, Rehab): The timer activates if any dispatched person's current action type matches one of the allowed state IDs. +- **UnitType timers**: The timer activates if any dispatched unit of the specified type is currently in one of the allowed states. +- If **Active For States** is empty, the timer is always active regardless of entity states. + +--- + +## Performing a Check-In + +When a user clicks the **Check In** button on an active timer: + +1. A modal dialog opens with an optional **Note** field. +2. The browser requests GPS coordinates via the Geolocation API (if available and permitted). +3. The check-in is submitted with the call ID, timer type, optional unit ID, optional GPS coordinates, and optional note. +4. The server validates the call belongs to the department, is currently active, and has timers enabled. +5. A check-in record is created with a UTC timestamp. +6. The timer resets — the countdown restarts from the configured duration. +7. The timer statuses and check-in history panels refresh automatically. + +Check-ins can also be recorded via the [API](#api-reference) from mobile apps or other integrations. + +--- + +## Call Export + +When a call with check-in timers enabled is exported (PDF or HTML), the export includes: + +- **Timer Configuration** — Lists the resolved timers for the call, including type, duration, warning threshold, and whether each timer came from a default config or an override. +- **Check-In Log** — A full log of all check-in records with timestamps, who performed them, check-in type, GPS coordinates, and notes. + +This export provides a complete post-incident record for review and compliance documentation. + +--- + +## API Reference + +The Check-In Timer API is available at `api/v4/CheckInTimers/`. All endpoints require authentication. + +| Endpoint | Method | Authorization | Description | +|----------|--------|---------------|-------------| +| `GetTimerConfigs` | GET | Department_View | List all default timer configs for the department | +| `SaveTimerConfig` | POST | Department_Update | Create or update a default timer config | +| `DeleteTimerConfig` | DELETE | Department_Update | Delete a default timer config | +| `GetTimerOverrides` | GET | Department_View | List all call-type/priority overrides | +| `SaveTimerOverride` | POST | Department_Update | Create or update an override | +| `DeleteTimerOverride` | DELETE | Department_Update | Delete an override | +| `GetTimersForCall` | GET | Call_View | Get the resolved set of timers for a specific call | +| `GetTimerStatuses` | GET | Call_View | Get real-time timer statuses with color-coded state | +| `PerformCheckIn` | POST | Call_Update | Record a check-in with optional GPS coordinates | +| `GetCheckInHistory` | GET | Call_View | Get all check-in records for a call | +| `ToggleCallTimers` | PUT | Call_Update | Enable or disable check-in timers on a call | + +--- + +## Real-Time Notifications + +The system broadcasts SignalR events when check-ins occur or timer configurations change, allowing connected clients (browser tabs, mobile apps) to receive live updates without polling. + +--- + +## Audit Logging + +All check-in timer configuration changes are recorded in the department's audit log, providing a full trail of who added, modified, or deleted timer configs and overrides. + +--- + +## Typical Workflow + +1. A department admin configures default timers in **Department Settings → Dispatch Settings** (e.g., PAR every 20 minutes with a 5-minute warning, Rehab every 45 minutes). +2. Optionally, the admin adds overrides for specific call types or priorities (e.g., for High Priority calls, PAR every 10 minutes). +3. Optionally, the admin enables **Auto-Enable Check-In Timers for New Calls** so all new calls have timers on by default. +4. A dispatcher creates a new call — timers are enabled automatically or toggled manually. +5. The incident commander and dispatchers view the call and see timers counting down in real-time. +6. As timers approach expiration, they transition **Green → Warning → Critical**. +7. Personnel perform check-ins via the web interface or mobile API, resetting the timer for that type. +8. All check-ins are logged with timestamps, GPS coordinates, and optional notes. +9. After the call, the Call Export includes the full timer configuration and check-in log for post-incident review. + +--- + +## Interactions with Other Modules + +| Module | Interaction | +|--------|-------------| +| **Dispatch & Calls** | Check-in timers are enabled on calls at creation or update; timer statuses display on the View Call page | +| **Templates** | Call Quick Templates can pre-configure the check-in timers enabled state | +| **Department Settings** | Default timer configs and overrides are managed in Dispatch Settings | +| **Units** | UnitType timers track dispatched units by type | +| **Personnel** | Personnel-based timers track dispatched personnel action states | +| **API** | Full REST API for timer management and check-in recording | +| **Reports / Export** | Call Export includes timer configuration and full check-in log | diff --git a/docs/web-app/communication-tests.md b/docs/web-app/communication-tests.md new file mode 100644 index 0000000..b333eaa --- /dev/null +++ b/docs/web-app/communication-tests.md @@ -0,0 +1,225 @@ +--- +sidebar_position: 39 +title: Communication Tests +--- + +# Communication Tests + +The Communication Tests module allows department administrators to verify that communication channels (SMS, Email, Voice, and Push Notifications) are working correctly for all members of their department. It answers the question: *"If we needed to reach everyone right now, could we?"* + +Admins define tests, run them on-demand or on a schedule, and then review per-user, per-channel reports showing who was reachable and who responded. + +**Authorization:** Department Admins only. Access is controlled via `ClaimsAuthorizationHelper.IsUserDepartmentAdmin()`. + +**Navigation:** Department Menu → Communication Tests + +--- + +## Overview + +A Communication Test consists of three components: + +- **Test Definition** — A reusable configuration specifying which channels to test, a schedule, and a response window. +- **Test Run** — A single execution of a test definition, identified by a unique run code (e.g., `CT-A3K7`). +- **Test Results** — Per-user, per-channel records showing whether each message was sent and whether the user responded. + +--- + +## Communication Tests List + +The main page displays a table of all test definitions configured for your department, showing: + +- Name and description +- Schedule type (On-Demand, Weekly, or Monthly) +- Channels being tested (SMS, Email, Voice, Push) +- Active status +- Response window (in minutes) +- **Edit**, **Delete**, and **Run Now** action buttons + +Below the test definitions, the 20 most recent test runs are listed with: + +- Run code (e.g., `CT-A3K7`) +- Status (Pending, Running, Awaiting Responses, Completed, Failed) +- Number of users tested +- Number of responses received +- A **View Report** link for completed runs + +--- + +## Creating a Communication Test + +Navigate to **Department → Communication Tests** and click **New Test**. + +### Test Fields + +| Field | Required | Description | +|-------|----------|-------------| +| Name | Yes | A human-readable label for the test (max 500 characters) | +| Description | No | Optional notes about the test's purpose (max 4000 characters) | +| Schedule Type | Yes | How the test is triggered: **On-Demand**, **Weekly**, or **Monthly** | +| Day(s) of Week | Conditional | Required for Weekly schedule — select one or more days | +| Day of Month | Conditional | Required for Monthly schedule — a value from 1 to 28 | +| Time | Conditional | Time of day for scheduled runs (e.g., `09:00 AM`) | +| Test SMS | No | Include SMS as a tested channel | +| Test Email | No | Include Email as a tested channel | +| Test Voice | No | Include Voice calls as a tested channel | +| Test Push | No | Include Push Notifications as a tested channel | +| Response Window (minutes) | Yes | How long users have to respond before the run auto-closes (default: 60) | +| Active | Yes | Whether this test is enabled for scheduled execution (default: enabled) | + +### Schedule Type Constraints + +| Schedule Type | Limit per Department | +|---------------|---------------------| +| On-Demand | Unlimited | +| Weekly | One | +| Monthly | One | + +:::info Scheduled Test Guard +A newly created scheduled test will not run until at least one full period after its creation date — 7 days for Weekly tests and 28 days for Monthly tests. This prevents a test from triggering immediately upon creation. +::: + +--- + +## Running a Test On-Demand + +From the Communication Tests list, click **Run Now** next to any test definition. + +:::caution Rate Limit +On-demand tests can only be started once every **48 hours** per test definition. If a run was started less than 48 hours ago, the **Run Now** button will be unavailable. +::: + +When a run is started, Resgrid will: + +1. Generate a unique **Run Code** in the format `CT-XXXX` (e.g., `CT-A3K7`). +2. Iterate over all department members and create per-user, per-channel result records. +3. Attempt to send a test message on each enabled channel to each member. +4. Set the run status to **Awaiting Responses** and wait for replies within the response window. + +--- + +## How Members Respond + +Each channel uses a different response mechanism: + +### SMS +- The test SMS message includes the run code (e.g., `CT-A3K7`). +- The member replies by texting back a message starting with that code. +- The incoming reply is matched to the run and the member's result is marked as responded. + +### Email +- The test email contains a **confirmation link**. +- The member clicks the link to confirm receipt. +- The link is valid for the duration of the response window; clicking it after the window has closed will show a "not found" message. + +### Voice +- A test voice call plays an automated message and prompts the member to **press 1** to confirm receipt. +- Pressing 1 records the response automatically. + +### Push Notification +- A push notification is sent to the member's mobile device. +- The notification payload includes a token that the Resgrid mobile app uses to record the response automatically when the notification is opened and confirmed. + +:::note +All response recording is **idempotent** — responding more than once on the same channel for the same run will not create duplicate records. +::: + +--- + +## Viewing a Run Report + +Click **View Report** next to any completed run to open the detailed report page. + +### Report Summary + +The report header shows: + +- Test name +- Run code +- Current status +- Start time and completion time +- Total users tested +- Total responses received + +Four summary cards display the **responded / sent ratio and percentage** for each channel (SMS, Email, Voice, Push). + +### Per-User Results Table + +The table lists every department member included in the run with a column for each enabled channel: + +| Column | Description | +|--------|-------------| +| Member | The department member's name | +| SMS | Send result and response status for SMS | +| Email | Send result and response status for Email | +| Voice | Send result and response status for Voice | +| Push | Send result and response status for Push | + +Each channel cell is **color-coded**: + +| Color | Meaning | +|-------|---------| +| Green | Message was sent and the member **responded** | +| Red | Message was sent but the member **did not respond** | +| Gray | Message was **not sent** (contact unverified, missing, or channel not enabled for this member) | + +The cell also shows: +- The contact value used (phone number or email address) +- Mobile carrier name (SMS only) +- Verification status of the contact method + +--- + +## Scheduled Test Execution + +Weekly and Monthly tests are processed automatically by a background worker that runs periodically. The worker: + +1. Checks all active Weekly tests to see if today matches one of their configured days. +2. Checks all active Monthly tests to see if today matches their configured day of the month. +3. Starts a run for any eligible test. +4. Scans all runs in **Pending**, **Running**, or **Awaiting Responses** status whose response window has elapsed and marks them **Completed**. + +--- + +## Audit Logging + +All administrative actions are captured in the department audit log: + +| Event | Description | +|-------|-------------| +| `CommunicationTestCreated` | A new test definition was created | +| `CommunicationTestUpdated` | An existing test definition was modified | +| `CommunicationTestDeleted` | A test definition was deleted | +| `CommunicationTestRunStarted` | A test run was initiated | + +Each audit event records the before/after state of the record, the acting user, IP address, user agent, and server name. + +--- + +## API Access + +The Communication Tests feature is also available via the Resgrid API v4. + +### CommunicationTests Endpoints (`/api/v4/CommunicationTests/`) + +| Method | Endpoint | Required Permission | Description | +|--------|----------|---------------------|-------------| +| GET | `GetAll` | `CommunicationTest_View` | List all test definitions for the department | +| GET | `Get?id={id}` | `CommunicationTest_View` | Get a single test definition by ID | +| POST | `Save` | `CommunicationTest_Create` (+ `CommunicationTest_Update` for edits) | Create or update a test definition | +| DELETE | `Delete?id={id}` | `CommunicationTest_Delete` | Delete a test definition | +| POST | `StartRun?testId={id}` | `CommunicationTest_Create` | Start an on-demand run (subject to 48-hour rate limit) | +| GET | `GetRuns?testId={id}` | `CommunicationTest_View` | Get all runs for a test definition | +| GET | `GetReport?runId={id}` | `CommunicationTest_View` | Get per-user results for a specific run | +| POST | `RecordPushResponse` | Authenticated | Record a push notification response from a mobile device | + +### CommunicationTestResponse Endpoints (`/api/v4/CommunicationTestResponse/`) + +| Method | Endpoint | Auth | Description | +|--------|----------|------|-------------| +| GET | `EmailConfirm?token={token}` | Anonymous | Email confirmation link handler | +| POST | `VoiceWebhook?token={token}&Digits={d}` | Anonymous | Voice DTMF callback from telephony provider | + +:::note +The `EmailConfirm` and `VoiceWebhook` endpoints are publicly accessible (no authentication required) since they are called by end-users clicking email links or by telephony providers delivering DTMF responses. +::: diff --git a/docs/web-app/custom-maps.md b/docs/web-app/custom-maps.md new file mode 100644 index 0000000..cd44968 --- /dev/null +++ b/docs/web-app/custom-maps.md @@ -0,0 +1,406 @@ +--- +sidebar_position: 11 +title: Custom Maps +--- + +# Custom Maps + +Custom Maps allow departments to upload their own map images — floor plans, venue layouts, campus schematics, satellite imagery, or tactical overlays — and overlay them on the standard Resgrid map. Polygon zones drawn on those images resolve to human-readable locations (e.g., "Building 1, Room 405a") that can be used as call locations, pre-plan attachments, and real-time personnel overlays. + +Custom Maps are designed for: +- **Fire departments** — building pre-plans, high-rise floor plans, industrial facility schematics +- **Law enforcement / security** — venue layouts, campus maps, event perimeters, gate/checkpoint configurations +- **EMS / SAR** — wilderness sectors, staging areas, muster points, triage zones +- **Emergency management** — multi-agency event maps, evacuation zones, resource staging areas + +**Navigation:** Mapping → Custom Maps + +--- + +## Overview + +A Custom Map consists of three nested layers: + +``` +Custom Map (the image + geo-bounds) + └── Floors / Levels (one image per floor or sub-level) + └── Zones (named polygon regions drawn on the floor image) +``` + +When a caller or dispatcher selects a zone, Resgrid resolves it to its human-readable name (e.g., "Sector B, Grid 7" or "South Tower, 14th Floor, Server Room") and stores that as the call location. + +--- + +## Map Types + +| Type | Use Case | +|------|----------| +| **Indoor** | Building floor plans, arenas, hospitals | +| **Satellite** | Custom or classified satellite images overlaid on real-world coordinates | +| **Schematic** | Engineered drawings, utility diagrams | +| **Event** | Temporary maps for concerts, marathons, sporting events — auto-archived after the event date | + +--- + +## Managing Custom Maps + +### Viewing All Custom Maps + +Navigate to **Mapping → Custom Maps** to see a table listing all maps for your department. Each row shows the map name, type, active status, and date created. + +### Creating a Map + +Click **New Custom Map** to open the creation form. + +| Field | Required | Description | +|-------|----------|-------------| +| Name | Yes | Display name (e.g., "Station 4 — Main Building") | +| Description | No | Additional detail about the map | +| Type | Yes | Indoor, Satellite, Schematic, or Event | +| Default Zoom | No | Initial zoom level when the map loads (1–22) | +| Min / Max Zoom | No | Zoom constraints for the map | +| Event Start / End | Event type only | Date range for automatic archival; these fields appear automatically when **Type = Event** is selected | +| Is Active | Yes | Toggle to show/hide the map across all views | +| Geo-Bounds | Yes | Draw a rectangle on the embedded Leaflet map (see below) | + +#### Setting Geo-Bounds + +The form includes a Leaflet map with the Leaflet-Geoman rectangle draw tool. To register where your map image sits in the real world: + +1. Pan and zoom the Leaflet map to the location your map covers. +2. Click the **rectangle icon** in the Geoman toolbar. +3. Click and drag to draw the bounding rectangle over the real-world footprint of your building or area. +4. The four hidden form fields (`BoundsTopLeftLat`, `BoundsTopLeftLng`, `BoundsBottomRightLat`, `BoundsBottomRightLng`) are populated automatically from the drawn rectangle. + +This geo-projects your floor images onto real-world GPS coordinates so that zone names and call locations align with GPS data from mobile devices. + +:::info Image Processing +Floor plan images are uploaded per-floor (not at the map level). Large images are automatically sliced into a tile pyramid by the Resgrid tile server using S3-compatible storage. You upload one file per floor; the system handles tile generation in the background. +::: + +### Editing a Map + +Click the **Edit** button next to a map in the list. The form is identical to **New Custom Map** but pre-populated with the saved values. The existing geo-bounds are rendered as an editable rectangle on the Leaflet map — drag the rectangle handles to adjust the bounds, or delete and redraw it using the Geoman toolbar. Save when done. + +### Deleting a Map + +Click the **Delete** button next to a map in the list. Deletion cascades immediately — the map, all of its floors, and all zones on those floors are permanently removed. Any calls that previously referenced a zone on this map retain the zone name as plain text but lose the live zone link. + +:::note Admin Required +The **New**, **Edit**, and **Delete** buttons are only visible to Department Admins. Users without admin access see the map list and the **Floors** button in read-only context. +::: + +--- + +## Managing Floors + +Every Custom Map has at least one floor. For single-story buildings or outdoor maps, use a single default floor. Add additional floors for multi-story buildings. + +### Opening Floor Management + +From the Custom Maps list, click the **Floors** button next to any map to open the **Manage Floors** page. + +### Page Layout + +The Manage Floors page uses a split layout: + +- **Left panel — floor list:** Each existing floor is shown as a row with its floor number, name, sort order, and two action buttons: **Zones** (opens the zone editor for that floor) and **Delete**. +- **Right panel — Add Floor form:** Always visible alongside the floor list for quickly adding new floors. + +### Adding a Floor + +Fill in the **Add Floor** form on the right and click **Save Floor**. + +| Field | Required | Description | +|-------|----------|-------------| +| Floor Number | Yes | Numeric level (e.g., `-1` for basement, `0` for ground, `1` for first floor) | +| Name | Yes | Human-readable level name (e.g., "3rd Floor", "Basement — Parking") | +| Image File | Yes | Floor plan image for this level (PNG, JPG, SVG); stored via the Resgrid file service and served through the internal `GetFloorImage` endpoint | +| Sort Order | No | Display order in the floor list (lower numbers appear first) | +| Elevation (ft) | No | Physical elevation above sea level in feet; used in SAR and high-rise operations | +| Is Default | No | When checked, this floor is shown first when the map is opened | + +:::info Floor Image Storage +Floor images are stored in the Resgrid file store and served by the `GetFloorImage` controller endpoint rather than as direct file URLs. This ensures access control and consistent URL stability regardless of the underlying storage backend. +::: + +### Editing a Floor + +Floor metadata edits (name, sort order, elevation, default flag) are not available through a separate edit form — delete the floor and re-add it with the corrected values. Zone data is lost when a floor is deleted, so export zone GeoJSON before deleting if you need to preserve it. + +### Deleting a Floor + +Click the **Delete** button in the floor row. The floor and all zones on it are permanently deleted immediately. + +--- + +## Managing Zones + +Zones are named polygon regions drawn on a floor image. Each zone resolves to a human-readable location used in dispatch, pre-plans, and search. + +### Opening the Zone Editor + +From the **Manage Floors** page, click the **Zones** button next to any floor row. This opens the full-screen **Manage Zones** editor for that floor. + +### Zone Editor Layout + +The zone editor is a full-screen Leaflet-based interface with three areas: + +- **Map canvas (centre):** OpenStreetMap tiles rendered at reduced opacity as a position reference, with the floor plan image overlaid at the map's saved geo-bounds. Existing zones appear as colored polygon fills with name labels. +- **Left sidebar:** A scrollable list of all zones on this floor. Each row displays a color swatch, the zone type badge, and the zone name. Clicking a row selects that zone on the map and opens the editor panel. A **Delete** button on each row permanently removes the zone. +- **Slide-in editor panel (right):** Appears when a zone is selected or a new polygon is drawn. Contains all editable zone fields. +- **Zone type legend (bottom of map):** A color-coded key showing all zone types and their default colors for quick reference. + +### Drawing a New Zone + +1. Click the **polygon icon** in the Leaflet-Geoman toolbar at the top of the map. +2. Click points on the map to trace the zone boundary. +3. Close the polygon by clicking the first point again (or double-clicking the last point). +4. The slide-in editor panel opens automatically. Fill in the zone details (see fields below). +5. Click **Save Zones** to commit all pending changes. + +### Zone Fields (Slide-In Editor Panel) + +| Field | Required | Description | +|-------|----------|-------------| +| Name | Yes | Human-readable location name (e.g., "Building 1, Room 405a"); becomes the call location when the zone is selected in dispatch | +| Zone Type | Yes | Select from the list; selecting a type automatically applies the default color for that type | +| Color | No | Hex color picker; overrides the zone-type default color for this individual zone | +| Is Searchable | No | Include this zone in map search results | +| Is Dispatchable | No | Allow this zone to be selected as a call location in the dispatch form | +| Is Active | No | Uncheck to hide the zone from all map overlays without deleting it | +| Metadata | No | Free-form JSON (e.g., `{"capacity": "20", "wing": "North"}`); stored with the zone for API consumers and pre-plan display | + +### Zone Types and Default Colors + +| Type | Default Color | Description | +|------|--------------|-------------| +| Room | Blue | General room or enclosed space | +| Hallway | Gray | Corridor or passage | +| StairWell | Purple | Stairwell | +| Elevator | Teal | Elevator shaft or lobby | +| **Hazard** | Red | Hazmat storage, chemical areas, electrical panels | +| **StagingArea** | Orange | ICS resource or equipment staging | +| **MusterPoint** | Green | Evacuation assembly / muster point | +| Exit | Yellow | Emergency egress point | +| Parking | Light Gray | Parking area | +| Gate | Dark Blue | Entry/exit gate (events, secured facilities) | +| Checkpoint | Brown | Security or access checkpoint | +| Custom | Configurable | User-defined; no auto-color applied | + +The zone type legend at the bottom of the map canvas shows all types and their current colors at a glance. + +:::note Dispatchable vs. Searchable +**Is Searchable** controls whether the zone appears in Resgrid's map/location search. **Is Dispatchable** controls whether the zone can be selected as a call location in the dispatch form. A zone can be one, both, or neither depending on your operational needs. +::: + +### Saving Zones + +The zone editor uses a **bulk save** model. All polygon additions, edits, and deletions are held in memory until you click **Save Zones**. At that point, the editor serializes every drawn polygon into a GeoJSON `FeatureCollection` (with zone fields stored as feature properties) and submits it to the `SaveZones` endpoint. The endpoint upserts changed zones and removes any zones that were deleted in the editor session. + +:::warning Unsaved Changes +Navigating away from the zone editor before clicking **Save Zones** discards all pending changes. The editor does not auto-save. +::: + +### Editing an Existing Zone + +Click the zone's row in the left sidebar (or click the polygon directly on the map). The slide-in editor panel opens with the current values. Edit any field. To reshape the polygon, use Leaflet-Geoman's **Edit** tool (pencil icon) to drag vertices. Click **Save Zones** when finished. + +### Deleting a Zone + +Click the **Delete** button in the zone's row in the left sidebar. The zone is removed from the map canvas immediately. Click **Save Zones** to commit the deletion to the server. + +--- + +## Pre-Plan Attachments on Zones + +Each zone can have documents, photos, hazmat data sheets, and tactical notes attached to it. These attachments appear in the zone detail panel and in the dispatch view when the zone is selected as a call location. + +### Adding an Attachment + +1. Select a zone on the **Manage Zones** map view. +2. Click **Attachments** in the Zone Details panel. +3. Click **Add Attachment** and choose a file or an existing document from the department's Document library. + +| Attachment Type | Supported Formats | +|-----------------|-------------------| +| Photos | JPG, PNG, GIF | +| Documents | PDF, DOCX, XLSX | +| Hazmat Sheets | PDF | +| Tactical Notes | Inline text or Markdown | + +Attachments appear in the dispatch interface and in the mobile apps whenever the zone is referenced as a call location, enabling first responders to view pre-plans without leaving the app. + +--- + +## Hazard and Safety Markers + +Zones with type **Hazard** support additional safety fields: + +| Field | Description | +|-------|-------------| +| Hazard Class | UN/DOT hazard class (1–9) | +| Chemical Name | Primary hazardous material | +| NFPA 704 Rating | Health / Flammability / Instability / Special | +| Suppression Type | Recommended suppression agent | +| Knox Box Location | Description of lock-box or key location | +| AED Location | Notes if AED is within zone | +| Utility Shutoff | Location of gas, electrical, or water shutoff | + +These fields populate the pre-plan display and are surfaced in the dispatch view when a call references the zone. + +--- + +## Using Custom Maps in Dispatch + +Custom map zones can be used as call locations in two ways: + +### Zone Selection in the Dispatch Form + +When creating or editing a call: +1. Click the **Location** field. +2. Toggle to **Custom Map**. +3. Select the Custom Map from the dropdown. +4. Select the Floor. +5. Click a zone polygon on the mini-map to select it. +6. The zone's Name populates the call location, and the map/floor/zone reference is saved with the call. + +Dispatched personnel and units see the zone name as the incident address and can view the pre-plan attachments directly from the call detail in the mobile apps. + +### GPS Auto-Resolution + +If a call is created via GPS coordinates (from a mobile device or CAD integration) and those coordinates fall within a zone's geo-projected polygon boundary, Resgrid automatically resolves and appends the zone name to the location. + +--- + +## Event Maps + +Event-type Custom Maps include a date range. When the event ends: +- The map is automatically set to **Is Active = false** and moved to the archived map list. +- No zones or calls linked to the map are deleted. +- The map can be re-activated manually if needed. + +### Event Zone Types + +Event maps support the full set of zone types plus: + +| Type | Use Case | +|------|----------| +| Gate | Main entry/exit gates | +| Checkpoint | Security screening points | +| StagingArea | Command posts, medical tents, resource staging | +| MusterPoint | Evacuation assembly points | +| Parking | Parking lots and zones | + +--- + +## Real-Time Personnel Overlay + +When indoor positioning data is available (via BLE beacons or other indoor location sources providing floor-level coordinates), personnel locations are plotted on the correct floor image. + +- The floor selector automatically highlights floors that have personnel present. +- Each personnel marker shows the same name and status information as the outdoor map. +- Personnel location visibility follows the same **CanSeePersonnelLocations** permission as the standard map. + +--- + +## Map Sharing (Temporary Access Links) + +For mutual-aid incidents or coordinating with partner agencies who do not have Resgrid accounts, you can generate a time-limited public link to a Custom Map. + +### Generating a Share Link + +1. Open the Custom Map detail page. +2. Click **Share Map**. +3. Set the expiration time (1 hour to 30 days). +4. Optionally restrict to specific floors. +5. Click **Generate Link**. + +The generated URL provides read-only access to the map and its zones with no login required. The link automatically expires at the configured time. Active share links are listed on the Share Map panel and can be revoked at any time. + +:::warning +Share links grant unauthenticated read access to the map, floor images, and zone names. Do not include sensitive pre-plan data on maps that will be shared, or ensure share links are limited to the duration of the incident. +::: + +--- + +## Multi-Map Composite View + +For campus, industrial, or large-site facilities requiring multiple buildings or areas, you can view several Custom Maps simultaneously in the main Leaflet map. + +1. Open the main **Mapping** view. +2. Click the **Custom Maps** layer control (alongside Layers, POIs, and Geofences). +3. Check the maps you want to display. +4. A **Building Selector** sidebar appears listing all loaded maps. Click a building/map to bring it to the foreground and switch floors. + +Zone polygons and labels from all loaded maps are rendered simultaneously, allowing incident commanders to see an entire campus in a single view. + +--- + +## Import and Export + +### Importing Zone Data + +| Format | Notes | +|--------|-------| +| GeoJSON | Import zones from any GeoJSON FeatureCollection; zone names are read from feature properties | +| KML / KMZ | Polygons imported as zones; point features are ignored (use POI import for points) | +| IFC (BIM) | Building spaces extracted as zones; requires IFC 2×3 or IFC4 | +| DXF / DWG | Converted to GeoJSON polygons before import; requires AutoCAD layer selection | + +### Exporting Zone Data + +Click **Export Zones** on any floor to download the zone polygons as a GeoJSON FeatureCollection. This can be imported into QGIS, ArcGIS, or other GIS tools. + +--- + +## Mobile App Behavior + +The Resgrid mobile apps (iOS, Android, and Web/Electron) display Custom Maps in the **Map** tab and in the call detail view. + +- **Online mode:** Maps and zones are fetched from the API as needed. +- **Offline mode:** The app checks whether the local cache is stale and downloads any changed map images and zone data automatically. Only maps that have been viewed or explicitly queued for download are cached. Cached maps are accessible without a network connection. +- **Sync strategy:** On app launch (or returning to foreground), the app queries the API for a lightweight version manifest. If the server version differs from the cached version, the app downloads only the changed floor images and zone GeoJSON. + +--- + +## Geofence / Zone-Triggered Notifications + +Zones on Custom Maps can trigger automatic notifications when a unit or personnel member enters the zone boundary. + +To configure: +1. Open the zone detail on the **Manage Zones** page. +2. Click **Zone Notifications**. +3. Configure the trigger type (**Unit Enters**, **Personnel Enters**, or both). +4. Select the notification recipients (group, role, or specific personnel). +5. Compose the notification message template (supports zone name, unit name, and timestamp tokens). + +Zone notifications extend the standard Resgrid notification system and are delivered via the same channels as other department notifications (push, SMS, email). + +--- + +## Interactions with Other Features + +| Feature | Integration | +|---------|-------------| +| **Dispatch / Calls** | Zone selected as call location; zone name stored with call; pre-plan attachments shown in dispatch | +| **Mapping** | Custom Maps appear as toggleable overlays on the main map | +| **Notifications** | Zone-triggered notifications when units/personnel enter a zone | +| **Documents** | Pre-plan documents and hazmat sheets attached to zones | +| **Personnel Locations** | Personnel plotted on correct floor when indoor positioning data available | +| **Units** | Unit GPS can trigger zone-entry notifications | +| **Geofences** | Zones function as polygon geofences for notification triggers | + +--- + +## Common Errors and Resolutions + +| Error | Resolution | +|-------|------------| +| "Image processing failed" | Ensure the uploaded image is a valid PNG, JPG, or SVG under 50 MB and not corrupted | +| Map tiles show "Processing" for over 10 minutes | Contact support; tile generation may have failed in the background | +| Zone polygon not saving | Ensure the polygon is closed (double-click to finish) and has at least 3 vertices | +| GPS auto-resolution not matching zone | Verify the map's geo-bounds rectangle closely aligns to the real-world building footprint | +| Share link expired | Regenerate from the Share Map panel; expired links cannot be extended | +| Import failed — "invalid GeoJSON" | Validate the GeoJSON at [geojson.io](https://geojson.io) before importing | +| Indoor personnel not appearing on floor | Confirm the indoor positioning source is sending floor-level data to the Resgrid location API | diff --git a/docs/web-app/department-settings.md b/docs/web-app/department-settings.md index b036597..00d067a 100644 --- a/docs/web-app/department-settings.md +++ b/docs/web-app/department-settings.md @@ -112,6 +112,17 @@ Number provisioning is limited by the department's subscription plan. | Dispatch Shift Instead of Group | When dispatching a group, dispatch personnel signed up for the current shift instead | | Auto-Set Status for Shift Dispatch | Automatically change dispatched shift personnel to a configurable status | | Unit Dispatch Behaviors | Configure how units respond to dispatch | +| Auto-Enable Check-In Timers for New Calls | Automatically enable check-in timers on every new call created | + +### Default Check-In Timer Configs + +Admins can define department-wide default check-in timers that apply to all calls. Each config specifies the timer target type, optional unit type, check-in interval (duration in minutes), warning threshold, enabled state, and an optional active-for-states filter. + +See [Call Check-In Timers](./call-checkin-timers) for the full list of target types and configuration details. + +### Check-In Timer Overrides + +Overrides replace default timer configs for specific call types and/or call priorities. They share the same fields as default configs and are evaluated by specificity — an override matching both call type and priority takes precedence over one matching only one of those values. ## Shift Settings @@ -213,3 +224,4 @@ The controller provides several JSON API endpoints used by the UI: | **Groups** | Station groups are managed here and used throughout the system | | **Calls** | Email import and text-to-call create calls automatically | | **Mapping** | Map center and zoom settings affect all map views | +| **Check-In Timers** | Default timer configs and call-type/priority overrides are managed in Dispatch Settings | diff --git a/docs/web-app/dispatch-calls.md b/docs/web-app/dispatch-calls.md index cfcf733..3f80b0a 100644 --- a/docs/web-app/dispatch-calls.md +++ b/docs/web-app/dispatch-calls.md @@ -41,6 +41,7 @@ The main dispatch dashboard displays: | Linked Calls | No | Reference related calls | | Contacts | No | Attach relevant contacts | | Form Data | No | Custom form data | +| Enable Check-In Timers | No | Activates the check-in timer system for this call (auto-checked if the department setting is enabled) | ### Dispatch Targets @@ -81,6 +82,12 @@ The system supports multiple location input methods: - **What3Words** — Three-word location codes resolved via the W3W API - **Reverse geocoding** — If only coordinates are provided, the system can look up the address +## Check-In Timers + +When a call has check-in timers enabled, a **Check-In Timers** panel appears on the View Call page showing real-time countdowns for each configured timer type (PAR, Rehab, IC, Hazmat, etc.). Timers visually escalate from **Green → Warning → Critical** as deadlines approach, and clicking **Check In** resets the timer and logs a record with an optional note and GPS coordinates. + +For full configuration, workflow, and API documentation see [Call Check-In Timers](./call-checkin-timers). + ## Updating a Call **Authorization:** `Call_Update` policy + `CanUserEditCall` runtime check @@ -90,6 +97,7 @@ Updating a call supports: - Adding or removing dispatch targets (diff-based — only changes are applied) - Updating linked calls and contacts - Updating protocols +- Toggling check-in timers on or off - **Rebroadcast option** — Optionally re-send notifications to all dispatched personnel Changes fire a `CallUpdatedEvent`. @@ -237,3 +245,4 @@ The dispatch area includes a chat view for real-time communication with departme | **Reports** | Call analytics and reports | | **Queue** | Async notification broadcast | | **Contact Verification** | Email, SMS, and voice call dispatches are gated by each user's contact verification status | +| **Check-In Timers** | Configurable accountability timers that track check-ins during active calls | diff --git a/docs/web-app/mapping.md b/docs/web-app/mapping.md index 989af84..bb42155 100644 --- a/docs/web-app/mapping.md +++ b/docs/web-app/mapping.md @@ -23,6 +23,7 @@ The `GetMapData` endpoint is the primary aggregation endpoint, supporting these | **Personnel** | `ShowPersonnel` | Personnel locations (permission-controlled) | | **POIs** | `ShowPOIs` | Points of interest | | **Districts** | `ShowDistricts` | Response district boundaries | +| **Custom Maps** | `ShowCustomMaps` | Uploaded floor plans, schematics, and event maps with named zones | :::note Permission Control Personnel location visibility is controlled by the `CanSeePersonnelLocations` permission. This is configurable per department in the Security settings. @@ -82,6 +83,12 @@ The `StationRouting` action shows routing from a station to a call: - Resolves start coordinates from station address or GPS coordinates - End coordinates from call location +## Custom Maps + +Custom Maps allow departments to upload building floor plans, venue layouts, schematics, and satellite imagery, draw named polygon zones on those images, and use zone names as call locations. See the dedicated [Custom Maps](custom-maps) documentation for full details. + +On the main map view, use the **Custom Maps** layer control (alongside Layers, POIs, and Geofences) to toggle custom map overlays. When one or more custom maps are enabled, a **Building Selector** sidebar appears for switching between maps and floors. + ## Data Endpoints | Endpoint | Purpose | @@ -89,14 +96,16 @@ The `StationRouting` action shows routing from a station to a call: | `GetMapData` | All map markers and geofences based on flag settings | | `GetTypesMapData` | Map data for a specific POI type | | `GetPoisForType` | POI list for a specific type | +| `GetCustomMaps` | Active custom maps with floor metadata for the overlay control | ## Interactions with Other Modules | Module | Interaction | |--------|-------------| -| **Calls** | Call locations displayed as markers | +| **Calls** | Call locations displayed as markers; zone names used as call locations from custom maps | | **Groups** | Station locations and geofences displayed | -| **Units** | Unit positions shown (from GPS tracking) | -| **Personnel** | Personnel locations shown (permission-controlled) | +| **Units** | Unit positions shown (from GPS tracking); zone-entry notifications triggered | +| **Personnel** | Personnel locations shown (permission-controlled); plotted on indoor floors when positioning data available | | **Department** | Map center, zoom, and refresh settings | | **Security** | Personnel location visibility permission | +| **Custom Maps** | Uploaded floor plans and zone overlays; see [Custom Maps](custom-maps) | diff --git a/docs/web-app/routes.md b/docs/web-app/routes.md new file mode 100644 index 0000000..e884060 --- /dev/null +++ b/docs/web-app/routes.md @@ -0,0 +1,228 @@ +--- +sidebar_position: 41 +title: Routes +--- + +# Routes + +The Routes module provides route planning, execution tracking, and deviation monitoring for units (vehicles/apparatus). It allows departments to define multi-stop routes, schedule them on a recurring basis, track real-time execution by units, and detect deviations from planned paths. The system integrates with Mapbox for route geometry and turn-by-turn navigation. + +**Navigation:** Department Menu → Routes + +## Overview + +The Routes system is built around six core concepts that work together to plan and execute routes: + +``` +Route Plan + Stops + Schedules → Start Route (Instance) → Execute Stops → End / Cancel +``` + +- A **Route Plan** is the reusable blueprint defining the path, stops, and settings +- **Route Stops** are the ordered waypoints within a plan +- **Route Schedules** define when a plan recurs automatically +- A **Route Instance** is the live execution record created when a unit starts a route +- **Route Instance Stops** track per-stop execution state (check-in, check-out, skipped) +- **Route Deviations** record when a unit strays from the planned path + +## Route Plans + +Route Plans are the core reusable definitions for a route. They belong to a department and can optionally be assigned to a specific unit. + +**Navigation:** Routes → Route Plans + +### Plan List + +The plan list displays all route plans for the department with: + +- Plan name and description +- Assigned unit (if any) +- Status badge (Draft, Active, Paused, Archived) +- Route color swatch +- Quick links to view, edit, and delete + +### Creating a Route Plan + +Click **New Route Plan** to open the creation form. + +| Field | Required | Description | +|-------|----------|-------------| +| Name | Yes | Display name for the route (max 250 characters) | +| Description | No | Optional description of the route's purpose | +| Status | Yes | Draft, Active, Paused, or Archived | +| Assigned Unit | No | Restricts the plan to a specific unit | +| Route Color | No | Hex color used for map rendering | +| Start Latitude / Longitude | Conditional | Explicit start coordinates (or use station) | +| End Latitude / Longitude | Conditional | Explicit end coordinates (or use station) | +| Use Station as Start | No | Use the unit's home station as the starting point | +| Use Station as End | No | Use the unit's home station as the ending point | +| Navigation Profile | Yes | Mapbox routing profile: Driving, Walking, Cycling, or Driving (Traffic) | +| Optimize Stop Order | No | Reorder stops automatically for efficiency | +| Geofence Radius (meters) | No | Default proximity radius for automatic stop check-in | +| Estimated Distance (meters) | No | Pre-computed route distance | +| Estimated Duration (seconds) | No | Pre-computed route duration | + +### Route Stops + +Each plan contains an ordered list of stops added from the plan editor. + +| Field | Required | Description | +|-------|----------|-------------| +| Stop Order | Yes | Integer position in the sequence (1, 2, 3, …) | +| Stop Type | Yes | Manual, Scheduled Call, Station, or Waypoint | +| Address | No | Human-readable address for the stop | +| Latitude / Longitude | Yes | Geographic coordinates of the stop | +| Geofence Radius (meters) | No | Per-stop override of the plan-level geofence radius | +| Priority | Yes | Normal, High, Critical, or Optional | +| Planned Arrival Time | No | Expected arrival time | +| Planned Departure Time | No | Expected departure time | +| Estimated Dwell (minutes) | No | How long the unit is expected to remain at the stop | +| Contact | No | Linked contact for service-delivery stops | +| Call | No | Linked Resgrid Call (for Scheduled Call stop types) | +| Notes | No | Free-form notes | + +:::tip Stop Types +- **Manual** — A general-purpose stop at any location +- **Scheduled Call** — Links the stop to an existing Resgrid Call +- **Station** — A department station or group +- **Waypoint** — A path waypoint without a service interaction +::: + +### Route Schedules + +A plan can have one or more schedules that automatically create route instances on a recurring basis. + +| Field | Required | Description | +|-------|----------|-------------| +| Recurrence Type | Yes | None, Daily, Weekly, Bi-Weekly, Monthly, or Custom | +| Recurrence Cron | Conditional | Cron expression (Custom recurrence only) | +| Days of Week | Conditional | Comma-separated days (Weekly / Bi-Weekly recurrence) | +| Day of Month | Conditional | Specific day number (Monthly recurrence) | +| Scheduled Start Time | No | Time-of-day for the route to begin | +| Effective From | No | Date the schedule becomes active | +| Effective To | No | Date the schedule expires | +| Active | Yes | Whether the schedule is currently enabled | + +### Editing and Deleting Plans + +- **Edit** — Modify plan metadata, add/remove/reorder stops, and manage schedules inline +- **Delete** — Soft-deletes the plan (`IsDeleted = true`); historical instances are preserved + +## Executing Routes + +### Starting a Route + +From the Route Plans list or the unit view, click **Start Route** and select the plan and unit. This creates a **Route Instance** with status **In Progress** and initializes a pending execution record for every stop in the plan. + +### Stop Execution + +As a unit progresses through the route, each stop can be acted on in three ways: + +| Action | Description | +|--------|-------------| +| **Check In** | Records arrival GPS coordinates and timestamp at the stop | +| **Check Out** | Records departure, computes dwell time, and advances the stop counter | +| **Skip** | Marks the stop as skipped and requires a reason | + +Check-in can occur in three ways: + +- **Manual** — Dispatcher or driver taps Check In +- **Geofence** — System automatically checks in when the unit's GPS position comes within the stop's geofence radius +- **QR Code** — Driver scans a QR code at the stop location + +:::note Geofence Auto-Check-In +When a unit's GPS position is reported, the system uses the Haversine formula to determine whether the unit is within a stop's geofence radius. If within range, the system automatically records a check-in. Each stop can define its own radius; otherwise the plan-level default is used. +::: + +### Pausing and Resuming + +An active route can be **Paused** (e.g., for an unexpected delay) and later **Resumed**. The route instance status moves between **In Progress** and **Paused** accordingly. + +### Ending a Route + +Click **End Route** to mark the instance as **Completed**. The system calculates total duration and records the actual route geometry. + +### Cancelling a Route + +Click **Cancel Route** to abort an in-progress or paused route. A cancellation reason is required. The instance status is set to **Cancelled**. + +## Monitoring Active Routes + +### Active Routes View + +Displays all route instances currently **In Progress** or **Paused** for the department, with: + +- Unit name and assigned plan +- Status badge +- Stops completed vs. total stops +- Scheduled and actual start times + +### Route Progress + +The detailed progress view for a specific route instance shows: + +- Overall instance status and timing +- Per-stop execution state (Pending, Checked In, Checked Out, Skipped) +- Check-in/check-out timestamps and GPS coordinates +- Dwell time per stop +- Arrival deviation (negative = early, positive = late, in seconds) +- Any notes added to individual stops + +## Route History + +From any Route Plan, click **View History** to see all past instances. The history list shows: + +- Executing unit +- Actual start and end times +- Completion status +- Stops completed vs. planned + +## Deviations + +Route Deviations are automatically recorded when a unit strays from the planned path. + +### Unacknowledged Deviations + +Supervisors can view all unreviewed deviations for the department, including: + +- Unit and route instance +- Detected timestamp and location +- Distance off-route (meters) +- Deviation type + +### Acknowledging a Deviation + +Select a deviation and click **Acknowledge** to mark it as reviewed. The acknowledgment records the supervisor's user ID and timestamp. + +## Navigation and Directions + +The Routes module integrates with Mapbox to provide turn-by-turn directions. + +| View | Description | +|------|-------------| +| **Plan Directions** | Shows the full planned route geometry and all waypoints for a route plan | +| **Live Directions** | Shows remaining stops only (excluding completed and skipped stops) for an active instance; optionally uses the unit's current GPS position as the real-time origin | + +## Contacts Integration + +Route stops can be linked to contacts from the [Contacts](contacts.md) module. This is useful for service delivery routes where each stop has an associated person or business. + +- View the contact record directly from a stop in the plan editor or the execution view +- Contact information includes name, company, phone, email, address, and GPS coordinates +- The **Route Contacts** view lists all unique contacts across all stops in a plan + +## Scheduled Routes + +View all active route schedules for the department under **Routes → Scheduled Routes**. Each entry shows the associated plan, recurrence type, next scheduled time, and whether the schedule is currently enabled. + +## Authorization + +Access to Routes features is controlled by four permission levels: + +| Permission | Allowed Actions | +|------------|----------------| +| Route View | View plans, instances, history, deviations, directions, contacts | +| Route Create | Create new route plans | +| Route Update | Edit plans, start/end/pause/resume/cancel routes, check-in/check-out, acknowledge deviations | +| Route Delete | Delete route plans | + +All data is scoped to the authenticated user's department — users can only see and manage their own department's routes. diff --git a/docs/web-app/templates.md b/docs/web-app/templates.md index f0ebe3f..74fad81 100644 --- a/docs/web-app/templates.md +++ b/docs/web-app/templates.md @@ -23,6 +23,7 @@ Displays all call quick templates for the department. | Call Nature | Either name or nature required | Pre-filled call nature/description | | Call Priority | No | Pre-selected priority | | Call Type | No | Pre-selected call type | +| Enable Check-In Timers | No | When set, pre-fills the "Enable Check-In Timers" checkbox on the new call form | Templates enable one-click creation of common call types by pre-filling the dispatch form. @@ -71,3 +72,4 @@ Validates department ownership. |--------|-------------| | **Dispatch** | Templates pre-fill call form; autofills insert into call notes | | **Types** | Call types and priorities used in template configuration | +| **Check-In Timers** | Templates can pre-configure the check-in timers enabled state for new calls | diff --git a/docs/web-app/user-defined-fields.md b/docs/web-app/user-defined-fields.md new file mode 100644 index 0000000..528c9e9 --- /dev/null +++ b/docs/web-app/user-defined-fields.md @@ -0,0 +1,196 @@ +--- +sidebar_position: 38 +title: User Defined Fields +--- + +# User Defined Fields + +The User Defined Fields module allows departments to define, manage, and view custom data fields on **Calls**, **Personnel**, **Units**, and **Contacts**. UDF field definitions are configured by Department Admins via the UDF management page. Field values are captured in the standard create/edit forms for each entity type and are displayed on detail/view pages. + +**Authorization:** UDF definition management requires Department Admin access. Viewing and editing field values on records follows the same permissions as the parent entity. + +**Navigation:** Department Menu → User Defined Fields + +--- + +## Overview + +UDF fields are defined once per entity type and automatically appear in the relevant create/edit/view forms throughout the web application. The system uses a **versioned definition** model — each time a department updates its field configuration, a new version of the definition is created. Existing records remain linked to the version that was active when they were saved, preserving historical data integrity. + +``` +Admin configures fields → Definition v1 created → Records save values against v1 +Admin adds a field → Definition v2 created → New records use v2 → v1 records unchanged +``` + +--- + +## UDF Management Page + +### Accessing the Management Page + +Navigate to **Department → User Defined Fields**. Use the **Entity Type** selector to switch between: +- **Calls** — Custom fields on dispatch calls +- **Personnel** — Custom fields on personnel/member profiles +- **Units** — Custom fields on apparatus/unit records +- **Contacts** — Custom fields on external contact records + +### Current Definition + +The management page displays the currently active definition version number and the list of configured fields. Each field row shows: + +| Column | Description | +|--------|-------------| +| Sort Order | The field's display position | +| Name | Internal field identifier | +| Label | Display label shown on forms | +| Data Type | The field input type | +| Group | Group/section name, if assigned | +| Required | Whether the field is mandatory | +| Enabled | Whether the field is currently visible on forms | +| Actions | Edit, Remove buttons | + +### Adding a Field + +1. Click **Add Field**. +2. Set the **Label**, **Name**, **Data Type**, and any validation rules. +3. Optionally configure the Group Name, Placeholder, Description (help text), sort order, and behavior flags. +4. Click **Save**. A new definition version is created with the field added. + +### Editing a Field + +1. Click **Edit** next to the field. +2. Update any settings. +3. Click **Save**. A new definition version is created preserving all existing field values on historical records. + +### Removing a Field + +Click **Remove** next to any field and confirm the prompt. A new definition version is created without that field. The field and its historical values remain in the database and continue to appear on records saved under previous versions. + +### Reordering Fields + +Drag and drop fields in the list to change their display order. Click **Save Order**. A new definition version is created with the updated sort order. + +:::tip Temporary vs. Permanent +Use the **Is Enabled** toggle on a field to temporarily hide it from forms without creating a new version. Use **Remove** only for permanent removal. +::: + +--- + +## UDF Fields in Call Records + +### New Call Form + +When an active UDF definition exists for the **Call** entity type, the UDF field section appears below the standard call fields on the **New Call** form. Fields are organized into groups (if configured) displayed as labeled sections. + +- Required fields are marked with an asterisk. +- Dropdowns and multi-select fields display the configured options. +- Read-only fields are displayed as non-editable. +- Help text (Description) appears as a tooltip icon next to the field label. + +### Edit Call Form + +The same UDF field section appears on the **Update Call** form. The form loads values saved under the call record's definition version. If the active definition has changed since the call was created, new fields from the latest version are displayed empty (no default is applied retroactively). + +### Call Detail View + +The call detail/view page displays all UDF field values in a read-only section below the standard call details. Fields with **Is Visible on Reports = Off** are excluded from this view. + +--- + +## UDF Fields in Personnel Records + +### Personnel Profile (Edit) + +When an active UDF definition exists for the **Personnel** entity type, the UDF section appears on the personnel profile edit page. Admins and the personnel member themselves (subject to permissions) can populate these fields. Fields are grouped by Group Name into labeled sections. + +### Personnel Profile (View) + +The personnel read-only profile page displays all UDF field values in a dedicated section. Fields with **Is Visible on Reports = Off** are excluded from the view. + +--- + +## UDF Fields in Unit Records + +### Edit Unit Form + +When an active UDF definition exists for the **Unit** entity type, the UDF section appears on the unit edit page. Unit admins can populate custom fields such as "Service Date", "Equipment Serial Number", or "Vehicle Class". + +### Unit Detail View + +The unit detail page renders UDF field values read-only below the standard unit fields. + +--- + +## UDF Fields in Contact Records + +### New / Edit Contact Form + +When an active UDF definition exists for the **Contact** entity type, the UDF section appears on the contact create and edit forms. This enables departments to capture compliance information, relationship metadata, or sector-specific data alongside the standard contact fields. + +### Contact Detail View + +The contact detail page renders UDF field values read-only below the standard contact information. + +--- + +## Field Rendering Details + +### Grouping Sections + +Fields that share a **Group Name** are rendered inside a labeled fieldset or collapsible panel. Example: + +``` +┌─────────────────────────────────────────┐ +│ Hazmat Details │ +│ Chemical Class: [dropdown ▼] │ +│ UN Number: [____________] │ +│ Quantity (L): [____________] │ +└─────────────────────────────────────────┘ +┌─────────────────────────────────────────┐ +│ Response Details │ +│ Entry Team: [____________] │ +│ Decon Required: [✓] │ +└─────────────────────────────────────────┘ +``` + +Fields without a Group Name appear in a default section above any named groups. + +### Client-Side Validation + +The web forms include HTML5 and data-attribute validation that runs in the browser before form submission: +- Required fields display an error if left empty. +- Text fields enforce min/max length. +- Number and Decimal fields enforce min/max value via `min`/`max` attributes. +- Regex rules emit a `pattern` attribute with the matching error message. +- Email, Phone, and URL fields use format-specific validation patterns. + +Server-side validation is always applied independently of client-side validation. + +### Read-Only Display + +Fields marked **Is Read-Only** render as static text (not an input control) on create and edit forms. They are displayed identically to editable fields on view/detail pages. + +--- + +## Reports and Exports + +UDF fields are automatically included as additional columns in call exports, personnel exports, unit exports, and contact exports. The columns are dynamically generated based on all definition versions, so historical records display the values that were captured under the definition version active at that time. + +No configuration is required to enable UDF columns in exports — they appear by default as long as the field's **Is Visible on Reports** flag is enabled. + +--- + +## Audit Log + +All UDF field value changes on records are written to the department audit log alongside the standard entity audit events. Navigate to **Department → Audit Log** and filter by **UDF Field Value Saved** to review a history of value changes, including the entity type, entity ID, and old/new values. + +For definition configuration changes (adding/editing/removing fields), filter by the UDF Definition and UDF Field event types. + +--- + +## Related + +- [User Defined Fields Configuration](../configuration/user-defined-fields.md) — How to configure field definitions, data types, validation rules, and grouping +- [User Defined Fields API](../api/user-defined-fields.md) — REST API reference for programmatic access to definitions, values, and mobile schema +- [Forms](forms.md) — Visual form builder for extending the New Call creation workflow +- [Workflows](workflows.md) — Automations that can interact with UDF field values diff --git a/docs/web-app/weather-alerts.md b/docs/web-app/weather-alerts.md new file mode 100644 index 0000000..05e3259 --- /dev/null +++ b/docs/web-app/weather-alerts.md @@ -0,0 +1,278 @@ +--- +sidebar_position: 39 +title: Weather Alerts +--- + +# Weather Alerts + +The Weather Alerts module automatically ingests severe weather alerts from government meteorological agencies, notifies department members, and optionally attaches relevant alerts to dispatch calls. It is designed for first responder organizations that need real-time situational awareness of severe weather in their jurisdiction. + +**Authorization:** Viewing alerts requires the `WeatherAlert_View` permission. Managing sources and zones requires `Department_Update` (department administrator). Creating, updating, and deleting sources requires `WeatherAlert_Create`, `WeatherAlert_Update`, and `WeatherAlert_Delete` permissions respectively. + +**Navigation:** Department Menu → Weather Alerts + +--- + +## Overview + +The Weather Alert system connects to external weather providers, polls for alerts on a configurable schedule, and surfaces them inside Resgrid. The end-to-end flow is: + +``` +Admin configures a source → Background worker polls every 5 min +→ New/updated alerts ingested → Notifications sent to members +→ Alerts optionally attached to dispatch calls → Real-time push to connected clients +``` + +Supported providers: + +| Provider | Coverage | Authentication | +|---|---|---| +| **National Weather Service (NWS)** | United States | None required | +| **Environment Canada (EC)** | Canada | None required | +| **MeteoAlarm** | Europe (EU countries) | Optional API key | + +--- + +## Views + +### Dashboard (Index) + +The main weather alerts page displays all **active** alerts for your department. Each alert card shows the event type, headline, severity, affected area, and expiration time. Clicking an alert opens the details page. + +### Alert Details + +The detail view for a single alert includes: + +| Section | Content | +|---|---| +| Header | Event type, sender, and current status | +| Alert Details | Severity, urgency, certainty, and category | +| Timing | Onset, effective, sent, and expiration times (displayed in your department's time zone) | +| Affected Area | Human-readable area description and geographic polygon on the map | +| Description | Full alert description text | +| Instructions | Action instructions from the issuing agency | +| Source | The configured alert source that produced this alert | + +### History + +The history view allows you to search past alerts within a date range. Alerts are listed in descending order by received time. + +### Settings (Admin) + +The Settings page is the administration interface for configuring alert **sources** and department-level **settings**. Requires department administrator access. + +### Zones (Admin) + +The Zones page manages geographic zones of interest for your department. Zones can be used to scope which alerts are relevant to your area of operations. + +--- + +## Alert Sources + +Alert sources define the upstream feeds that Resgrid polls for weather data. Each source targets a specific provider and geographic filter. + +### Configuring a Source + +Navigate to **Weather Alerts → Settings** and click **Add Source**. + +| Field | Required | Description | +|---|---|---| +| Name | Yes | A descriptive name (e.g., "NWS Nevada Alerts") | +| Source Type | Yes | The provider: National Weather Service, Environment Canada, or MeteoAlarm | +| Area Filter | Yes | Geographic scope — see provider-specific guidance below | +| Poll Interval (minutes) | Yes | How often to poll this source. Minimum: **15 minutes** | +| API Key | No | Required for MeteoAlarm if using authenticated access | +| Custom Endpoint | No | Override the default provider URL (HTTPS only; restricted to known provider domains) | +| Active | Yes | Toggle to enable or disable polling for this source | + +:::caution Poll Interval Minimum +The system enforces a minimum poll interval of 15 minutes regardless of the value entered. Setting a lower value will be automatically clamped to 15 minutes. +::: + +### Area Filter by Provider + +The Area Filter field accepts different values depending on the selected provider: + +**National Weather Service (NWS)** +- State abbreviations (comma-separated): `NV,CA,AZ` +- Specific NWS zone codes: `WAZ021,ORZ001` + +**Environment Canada (EC)** +- Province codes (comma-separated): `ON,BC,AB` + +**MeteoAlarm** +- ISO 3166-1 alpha-2 country codes (comma-separated): `DE,FR,IT` + +### Source Status + +After a source has been polled, the Settings page shows its current status: + +| Status | Description | +|---|---| +| Active | Source is enabled and will be polled on schedule | +| Inactive | Source is disabled; no polling occurs | +| Error | Last poll failed; the error message is displayed | +| Last Poll / Last Success | Timestamps of the most recent poll attempt and successful retrieval | + +### Deleting a Source + +Click **Delete** next to a source on the Settings page. Deleting a source removes its configuration; already-ingested alerts from that source are retained. + +--- + +## Geographic Zones + +Zones define geographic areas of interest within your jurisdiction. Each zone has a center point and a radius, and can be flagged as a primary zone. + +### Configuring a Zone + +Navigate to **Weather Alerts → Zones** and click **Add Zone**. + +| Field | Required | Description | +|---|---|---| +| Name | Yes | Zone name (e.g., "Station 1 Coverage Area") | +| Zone Code | No | NWS or Environment Canada zone code for this area | +| Center Location | Yes | Latitude and longitude of the zone center (`lat,lng`) | +| Radius (miles) | Yes | Radius in miles for proximity filtering | +| Active | Yes | Whether this zone is currently in use | +| Primary Zone | No | Marks this as the primary zone for the department | + +--- + +## Department Settings + +Weather alert behavior is controlled by department-level settings. Navigate to **Weather Alerts → Settings** and scroll to the **Settings** section. + +| Setting | Description | Default | +|---|---|---| +| **Weather Alerts Enabled** | Master toggle. When disabled, no alert processing, notifications, or call attachments occur | Off | +| **Minimum Severity** | Minimum severity level to display and import. Alerts below this threshold are ignored | All severities | +| **Auto Message Severity** | Severity threshold for automatic system messages to all department members | Severe | +| **Call Integration** | Automatically attach active alerts to new and updated dispatch calls | Off | +| **Cache Duration (minutes)** | How long provider responses are cached to reduce duplicate API calls | 10 minutes | + +### Severity Levels + +Severity values range from highest to lowest priority: + +| Level | Description | +|---|---| +| **Extreme** | Exceptional threat to life or property | +| **Severe** | Significant threat to life or property | +| **Moderate** | Possible threat to life or property | +| **Minor** | Minimal threat | +| **Unknown** | Severity not defined by the issuing agency | + +The **Auto Message Severity** threshold works inclusively — setting it to **Severe** means both **Extreme** and **Severe** alerts trigger system messages. + +--- + +## Notifications + +When a new alert meets the department's **Auto Message Severity** threshold, Resgrid automatically sends a broadcast system message to all department members. This message includes: + +- Alert event type and headline +- Severity, urgency, certainty, and category +- Onset, expiration, and effective times (in the department's configured time zone) +- Affected area description +- Full description and instructions from the issuing agency +- Source attribution + +Messages are delivered via push notification, email, and/or SMS based on each member's contact preferences. + +:::info One Message Per Alert +Each alert triggers at most one automatic system message. Once a notification has been sent for an alert, it will not be re-sent even if the alert is updated. +::: + +--- + +## Call Integration + +When **Call Integration** is enabled, Resgrid automatically attaches relevant active weather alerts to dispatch calls as system call notes when a call is created or updated. + +- If the call has a geolocation, only alerts within **50 miles** of the call location are attached. +- Alerts without a defined geographic polygon are treated as area-wide and are always attached. +- Each alert is attached at most once per call — duplicate attachments are suppressed automatically. + +Each attached alert note follows this format: + +``` +[WeatherAlert:{alertId}] {Event Type} +{Headline} +Area: {Affected Area Description} +Severity: {Severity Level} +Expires: {Expiration Time} +Instructions: {Instruction Text} +``` + +--- + +## Real-Time Updates + +Resgrid pushes real-time weather alert events to all connected web clients via SignalR. No manual refresh is needed to see new or updated alerts. + +| Event | Trigger | +|---|---| +| **WeatherAlertReceived** | A new alert has been ingested for the department | +| **WeatherAlertExpired** | An active alert has passed its expiration time | +| **WeatherAlertUpdated** | An existing alert has been updated by the upstream provider | + +--- + +## API Reference + +Weather alert data is available through the Resgrid REST API (v4). All endpoints require authentication and the appropriate permission claim. + +| Method | Endpoint | Permission | Description | +|---|---|---|---| +| GET | `/api/v4/WeatherAlerts/GetActiveAlerts` | View | All active alerts for the department | +| GET | `/api/v4/WeatherAlerts/GetWeatherAlert/{alertId}` | View | Single alert by ID | +| GET | `/api/v4/WeatherAlerts/GetAlertHistory?startDate=&endDate=` | View | Historical alerts in a date range | +| GET | `/api/v4/WeatherAlerts/GetAlertsNearLocation?lat=&lng=&radiusMiles=` | View | Active alerts near a coordinate (max 500 mi radius) | +| GET | `/api/v4/WeatherAlerts/GetSources` | View | All configured sources | +| POST | `/api/v4/WeatherAlerts/SaveSource` | Create / Update | Create or update a source | +| DELETE | `/api/v4/WeatherAlerts/DeleteSource/{sourceId}` | Delete | Delete a source | +| GET | `/api/v4/WeatherAlerts/GetZones` | View | All configured zones | +| POST | `/api/v4/WeatherAlerts/SaveZone` | Create / Update | Create or update a zone | +| DELETE | `/api/v4/WeatherAlerts/DeleteZone/{zoneId}` | Delete | Delete a zone | +| GET | `/api/v4/WeatherAlerts/GetSettings` | View | Get department weather alert settings | +| POST | `/api/v4/WeatherAlerts/SaveSettings` | Update | Save department weather alert settings | + +All timestamps returned by the API are converted to the department's configured time zone. + +--- + +## Alert Lifecycle + +The following describes the complete lifecycle of a weather alert within Resgrid: + +1. **Ingestion** — The background worker runs every 5 minutes, polling all active sources whose poll interval has elapsed. The appropriate provider (NWS, EC, or MeteoAlarm) fetches alerts from the upstream API. +2. **Deduplication** — Each fetched alert is matched against existing records by its external ID and source. New alerts are inserted; existing alerts are updated with the latest content. +3. **Update/Cancel chains** — If a new alert references a prior alert (e.g., an update or cancellation), the referenced alert is automatically marked as Cancelled. +4. **Expiration** — Each worker cycle marks alerts whose expiration time has passed as Expired. +5. **Notification** — Each worker cycle sends system messages for any alert that meets the severity threshold and has not yet had a notification sent. +6. **Call attachment** — When a call is created or updated via the API and Call Integration is enabled, active alerts near the call location are attached as call notes. +7. **Real-time push** — Connected clients receive SignalR events for new, updated, and expired alerts immediately. + +--- + +## Localization + +The Weather Alerts module is available in the following languages: + +English, Spanish, German, French, Italian, Polish, Swedish, Ukrainian, Arabic + +The display language is determined by each user's language preference. + +--- + +## Interactions with Other Modules + +| Module | Interaction | +|---|---| +| **Dispatch / Calls** | Active alerts can be automatically attached as system call notes when Call Integration is enabled | +| **Messages** | Automatic broadcast messages are sent via the Messages module for qualifying alerts | +| **Department Settings** | Time zone is used for all alert timestamp display and message formatting | +| **Mapping** | Alert polygon geometry is displayed on the map in the alert detail view | +| **Notifications** | A link to Weather Alerts is accessible from the Notifications page | +| **Permissions** | View, Create, Update, and Delete operations are governed by dedicated weather alert permission claims |