unit View.Map;

interface

uses
  System.SysUtils, System.Classes, WEBLib.Graphics, WEBLib.Forms, WEBLib.Dialogs,
  Vcl.Controls, Vcl.StdCtrls, WEBLib.StdCtrls, WEBLib.Controls, WEBLib.Grids,
  WEBLib.ExtCtrls, DB, WEBLib.WebCtrls, WEBLib.REST, VCL.TMSFNCTypes, VCL.TMSFNCUtils,
  VCL.TMSFNCGraphics, VCL.TMSFNCGraphicsTypes, VCL.TMSFNCCustomControl, VCL.TMSFNCWebBrowser,
  VCL.TMSFNCMaps, VCL.TMSFNCLeaflet, VCL.TMSFNCMapsCommonTypes, System.StrUtils, XData.Web.Client,
  XData.Web.Connection, ConnectionModule, Utils, uMapFilters;

type
  TFViewMap = class(TWebForm)
    pnlMap: TWebPanel;
    lfMap: TTMSFNCLeaflet;
    httpReqGeoJson: TWebHttpRequest;
    xdwcMap: TXDataWebClient;
    btnFindLocation: TWebButton;
    tmrLocate: TWebTimer;

    procedure lfMapMapInitialized(Sender: TObject);
    [async] procedure httpReqGeoJsonResponse(Sender: TObject; AResponse: string);
    procedure lfMapCustomizeMarker(Sender: TObject;
    var ACustomizeMarker: string);
    procedure lfMapCustomizeCSS(Sender: TObject; var ACustomizeCSS: string);
    procedure btnFindLocationClick(Sender: TObject);
    procedure tmrLocateTimer(Sender: TObject);
  private
    userLocationMarker: TTMSFNCMapsMarker;
    geoWatchId: Integer;
    FUnitsLoaded: Boolean;
    FComplaintsLoaded: Boolean;
    FLoadingPoints: Boolean;
    mapFilters: TMapFilters;
    FPendingUnitId: string;
    FPendingComplaintId: string;
    FPendingFocusCoord: TTMSFNCMapsCoordinateRec;
    FPendingFocusZoom: Integer;
    FDoFocusZoom: Boolean;

    [async] procedure LoadPointsAsync(showBusy: Boolean);
    function CarIconForDistrict(const DistrictCode: string): string;
    procedure UpdateDeviceLocation(lat, lng: Double);
    procedure StartDeviceLocation;
    procedure ApplyPendingUnitFocus;
    procedure ApplyPendingComplaintFocus;
  public
    procedure FocusUnit(const unitId: string);
    procedure FocusComplaint(const complaintId: string);
    procedure RefreshData;
  end;

var
  FViewMap: TFViewMap;

implementation

uses
  JS, Web;

{$R *.dfm}

procedure TFViewMap.lfMapMapInitialized(Sender: TObject);
begin
  ShowSpinner('spinner');
  FUnitsLoaded := False;
  FComplaintsLoaded := False;
  httpReqGeoJson.Execute;
  asm
    window.showComplaintDetails = function (id) {
      try {
        pas['View.Main'].FViewMain.ShowComplaintDetails(id);
      } catch (e) {
      }
    };

    window.showUnitDetails = function (id) {
      try {
        pas['View.Main'].FViewMain.ShowUnitDetails(id);
      } catch (e) {
      }
    };
  end;
  StartDeviceLocation;
end;


procedure TFViewMap.UpdateDeviceLocation(lat, lng: Double);
begin
  if userLocationMarker = nil then
  begin
    userLocationMarker := lfMap.Markers.Add;
    userLocationMarker.Title := '<div class="small fw-semibold">You are here</div>';
    userLocationMarker.DataString := 'device';
    userLocationMarker.IconURL := 'assets/markers/location_dot.png';
    userLocationMarker.Visible := False;
  end;

  userLocationMarker.Latitude := lat;
  userLocationMarker.Longitude := lng;
end;


procedure TFViewMap.StartDeviceLocation;
begin
  asm
    const self = this;

    if (!navigator.geolocation) {
      console.log('Geolocation not supported');
      return;
    }

    const onPos = function (pos) {
      const lat = pos.coords.latitude;
      const lng = pos.coords.longitude;

      try {
        self.UpdateDeviceLocation(lat, lng);
      } catch (e) {
        console.log('UpdateDeviceLocation failed', e);
      }
    };

    const onErr = function (err) {
      console.log('Geolocation error:', err && err.message ? err.message : err);
    };

    if (self.geoWatchId != null && self.geoWatchId !== 0) {
      try { navigator.geolocation.clearWatch(self.geoWatchId); } catch(e) {}
      self.geoWatchId = 0;
    }

    self.geoWatchId = navigator.geolocation.watchPosition(onPos, onErr, {
      enableHighAccuracy: true,
      maximumAge: 5000,
      timeout: 15000
    });
  end;
end;


[async] procedure TFViewMap.httpReqGeoJsonResponse(Sender: TObject; AResponse: string);
var
  i: Integer;
  P: TTMSFNCMapsPolygon;
begin
  lfMap.BeginUpdate;
  try
    lfMap.Polygons.Clear;

    lfMap.LoadGeoJSONFromText(AResponse, True, Trim(FPendingUnitId) = '');

    for i := 0 to lfMap.Polygons.Count - 1 do
    begin
      P := lfMap.Polygons[i];

      case i of
        0: begin
             P.DisplayName := 'District A';
             P.FillColor   := HTMLToColor('#d3ffbe'); // light green
             P.StrokeColor := HTMLToColor('#6ea85c'); // darker green
           end;
        1: begin
             P.DisplayName := 'District B';
             P.FillColor   := HTMLToColor('#ffbebe'); // light red
             P.StrokeColor := HTMLToColor('#b34a4a'); // darker red
           end;
        2: begin
             P.DisplayName := 'District C';
             P.FillColor   := HTMLToColor('#ffd37f'); // light orange
             P.StrokeColor := HTMLToColor('#b36e00'); // darker orange
           end;
        3: begin
             P.DisplayName := 'District D';
             P.FillColor   := HTMLToColor('#bed2ff'); // light blue
             P.StrokeColor := HTMLToColor('#3c5ca8'); // darker blue
           end;
        4: begin
             P.DisplayName := 'District E';
             P.FillColor   := HTMLToColor('#ffffbe'); // light yellow
             P.StrokeColor := HTMLToColor('#a8a85c'); // darker yellow/olive
           end;
      end;

      P.FillOpacity := 0.60;
      P.StrokeOpacity := 1.0;
      P.StrokeWidth := 2;

    end;
  finally
    lfMap.EndUpdate;
  end;

  await(LoadPointsAsync(True));

  if mapFilters = nil then
  begin
    mapFilters := TMapFilters.Create(lfMap);
    mapFilters.Init;
  end;
end;

function TFViewMap.CarIconForDistrict(const DistrictCode: string): string;
var
  U: string;
  L: Char;
begin
  U := UpperCase(Trim(DistrictCode));

  if U = '' then
    Exit('assets/markers/car_default.png');

  L := U[1];
  case L of
    'A','B','C','D','E','X':
      Result := 'assets/markers/car_' + L + '.png';
  else
    Result := 'assets/markers/car_default.png';
  end;
end;


[async] procedure TFViewMap.LoadPointsAsync(showBusy: Boolean);
var
  resp: TXDataClientResponse;
  root, item, uo: TJSObject;
  units: TJSArray;
  i, ui: Integer;
  m: TTMSFNCMapsMarker;
  lat, lng: Double;
  uName, dist: string;
  unitId, callType, priorityText, statusText: string;
  complaintId, codeDesc, dispatchDist, priority, business: string;
  pngName, iconUrl, rowsHtml: string;
  officer1Lname, officer1Fname, officer1Empnum: string;
  officer2Lname, officer2Fname, officer2Empnum: string;
  officer1Display, officer2Display: string;
  updateTimeText: string;
  canShowDetails: Boolean;
  canShowDetailsText: string;
  detailsBtnHtml: string;
  unitsData: TJSArray;
  complaintsData: TJSArray;
begin
  if FLoadingPoints then
    Exit;

  FLoadingPoints := True;

  if showBusy then
    ShowSpinner('spinner');
  try
    FUnitsLoaded := False;
    FComplaintsLoaded := False;

    unitsData := nil;
    complaintsData := nil;

    // --- Fetch Units ---------------------------------------------------------
    try
      resp := await(xdwcMap.RawInvokeAsync('IApiService.GetUnitMap', []));
      root := TJSObject(resp.Result);
      unitsData := TJSArray(root['data']);
      FUnitsLoaded := True;
    except
      on E: EXDataClientRequestException do
        Console.Log('Units XData error: ' + E.ErrorResult.ErrorMessage);
    end;

    // --- Fetch Complaints ----------------------------------------------------
    try
      resp := await(xdwcMap.RawInvokeAsync('IApiService.GetComplaintMap', []));
      root := TJSObject(resp.Result);
      complaintsData := TJSArray(root['data']);
      FComplaintsLoaded := True;
    except
      on E: EXDataClientRequestException do
        Console.Log('Complaints XData error: ' + E.ErrorResult.ErrorMessage);
    end;

    // --- Swap Markers (no blank map while loading) ---------------------------
    lfMap.BeginUpdate;
    try
      for i := lfMap.Markers.Count - 1 downto 0 do
      begin
        m := lfMap.Markers[i];

        if StartsText('unit|', m.DataString) or StartsText('complaint|', m.DataString) then
          lfMap.Markers.Delete(i);
      end;

      // Unit markers
      if unitsData <> nil then
      begin
        for i := 0 to unitsData.Length - 1 do
        begin
          item := TJSObject(unitsData[i]);

          lat := Double(item['Lat']);
          lng := Double(item['Lng']);
          uName := string(item['UnitName']);
          dist := string(item['District']);
          unitId := string(item['UnitId']);
          callType := string(item['CallType']);
          priorityText := string(item['Priority']);
          statusText := string(item['Status']);
          updateTimeText := string(item['UpdateTime']);

          officer1Lname := string(item['Officer1Lname']);
          officer1Fname := string(item['Officer1Fname']);
          officer1Empnum := string(item['Officer1Empnum']);

          officer2Lname := string(item['Officer2Lname']);
          officer2Fname := string(item['Officer2Fname']);
          officer2Empnum := string(item['Officer2Empnum']);

          canShowDetailsText := '';
          if item['CanShowDetails'] <> nil then
            canShowDetailsText := string(item['CanShowDetails']);
          canShowDetails := SameText(Trim(canShowDetailsText), 'true');

          if canShowDetails then
          begin
            detailsBtnHtml :=
              '<button type="button" class="btn btn-primary btn-sm px-2 py-1" ' +
                'onclick="window.showUnitDetails(''' + unitId + ''')">' +
                'Details' +
              '</button>';
          end
          else
            detailsBtnHtml := '';

          officer1Display := '';
          if Trim(officer1Lname + officer1Fname + officer1Empnum) <> '' then
          begin
            officer1Display := Trim(officer1Lname);
            if Trim(officer1Fname) <> '' then
              officer1Display := officer1Display + ', ' + Trim(officer1Fname);
            if Trim(officer1Empnum) <> '' then
              officer1Display := officer1Display + ' (' + Trim(officer1Empnum) + ')';
          end;

          officer2Display := '';
          if Trim(officer2Lname + officer2Fname + officer2Empnum) <> '' then
          begin
            officer2Display := Trim(officer2Lname);
            if Trim(officer2Fname) <> '' then
              officer2Display := officer2Display + ', ' + Trim(officer2Fname);
            if Trim(officer2Empnum) <> '' then
              officer2Display := officer2Display + ' (' + Trim(officer2Empnum) + ')';
          end;

          m := lfMap.Markers.Add;
          m.Latitude := lat;
          m.Longitude := lng;

          m.Title :=
            '<div class="d-flex flex-column gap-1 px-1 py-1" style="width:260px;">' +
              '<div class="fw-semibold small">' +
                '<span class="fw-bold">Unit:</span> ' + uName +
              '</div>' +
              IfThen(dist <> '',
                '<div class="small"><span class="fw-bold">District:</span> ' + dist + '</div>',
                ''
              ) +
              IfThen(Trim(callType) <> '',
                '<div class="small"><span class="fw-bold">Call Type:</span> ' + callType + '</div>',
                ''
              ) +
              IfThen(Trim(priorityText) <> '',
                '<div class="small"><span class="fw-bold">Priority:</span> ' + priorityText + '</div>',
                ''
              ) +
              IfThen(Trim(statusText) <> '',
                '<div class="small"><span class="fw-bold">Status:</span> ' + statusText + '</div>',
                ''
              ) +
              IfThen(Trim(officer1Display) <> '',
                '<div class="small"><span class="fw-bold">Officer 1:</span> ' + officer1Display + '</div>',
                ''
              ) +
              IfThen(Trim(officer2Display) <> '',
                '<div class="small"><span class="fw-bold">Officer 2:</span> ' + officer2Display + '</div>',
                ''
              ) +
              IfThen(Trim(updateTimeText) <> '',
                '<div class="small mb-1"><span class="fw-bold">Updated:</span> ' + updateTimeText + '</div>',
                '<div class="small mb-1"></div>'
              ) +
              IfThen(detailsBtnHtml <> '',
                '<div class="d-flex justify-content-end mt-0">' + detailsBtnHtml + '</div>',
                ''
              ) +
            '</div>';

          m.DataString := 'unit|' + unitId;
          m.IconURL := CarIconForDistrict(dist);
        end;
      end;

      // Complaint markers
      if complaintsData <> nil then
      begin
        for i := 0 to complaintsData.Length - 1 do
        begin
          item := TJSObject(complaintsData[i]);

          complaintId := string(item['ComplaintId']);
          codeDesc := string(item['DispatchCodeDesc']);
          dispatchDist := string(item['DispatchDistrict']);
          priority := string(item['Priority']);
          business := string(item['Business']);

          lat := Double(item['Lat']);
          lng := Double(item['Lng']);

          if ((lat = 0) and (lng = 0)) or (Abs(lat) > 90) or (Abs(lng) > 180) then
            Continue;

          pngName := string(item['pngName']);
          if Trim(pngName) <> '' then
            iconUrl := 'assets/markers/' + pngName
          else
            iconUrl := 'assets/markers/default_5-9.png';

          rowsHtml := '';
          units := TJSArray(item['Units']);

          if Assigned(units) and (units.Length > 0) then
          begin
            for ui := 0 to units.Length - 1 do
            begin
              uo := TJSObject(units[ui]);

              rowsHtml := rowsHtml +
                '<tr>' +
                  '<td>' + string(uo['Unit']) + '</td>' +
                  '<td>' + string(uo['Status']) + '</td>' +
                  '<td>' + string(uo['Updated']) + '</td>' +
                '</tr>';
            end;
          end
          else
            rowsHtml := '<tr><td colspan="3" class="text-muted">No units</td></tr>';

          m := lfMap.Markers.Add;
          m.Latitude := lat;
          m.Longitude := lng;

          m.DataString := 'complaint|' + complaintId;
          m.IconURL := iconUrl;

          m.Title :=
            '<div class="d-flex flex-column gap-1 px-1 py-1" style="width:260px;">' +
              '<div class="fw-semibold small">' +
                '<span class="fw-bold">Complaint:</span> ' + complaintId +
              '</div>' +
              '<div class="small">' +
                '<span class="fw-bold">Priority:</span> ' + priority +
              '</div>' +
              '<div class="small">' +
                '<span class="fw-bold">Dispatch Code:</span> ' + codeDesc +
              '</div>' +
              '<div class="small">' +
                '<span class="fw-bold">Dispatch District:</span> ' + dispatchDist +
              '</div>' +
              IfThen(Trim(business) <> '',
                '<div class="small">' +
                  '<span class="fw-bold">Business:</span> ' + business +
                '</div>',
                ''
              ) +
              '<div class="small mb-1">' +
                '<span class="fw-bold">Address:</span> ' + string(item['Address']) +
              '</div>' +
              '<table class="table table-sm table-bordered mb-1 emi-tip-table">' +
                '<colgroup>' +
                  '<col style="width:34%">' +
                  '<col style="width:33%">' +
                  '<col style="width:33%">' +
                '</colgroup>' +
                '<thead class="table-light">' +
                  '<tr>' +
                    '<th>Unit</th>' +
                    '<th>Status</th>' +
                    '<th>Updated</th>' +
                  '</tr>' +
                '</thead>' +
                '<tbody>' + rowsHtml + '</tbody>' +
              '</table>' +
              '<div class="d-flex justify-content-end mt-0">' +
                '<button type="button" class="btn btn-primary btn-sm px-2 py-1" ' +
                  'onclick="window.showComplaintDetails(''' + complaintId + ''')">' +
                  'Details' +
                '</button>' +
              '</div>' +
            '</div>';
        end;
      end;
    finally
      lfMap.EndUpdate;
    end;

    if mapFilters <> nil then
      mapFilters.Apply;

    ApplyPendingUnitFocus;
    ApplyPendingComplaintFocus;
  finally
    if showBusy then
      HideSpinner('spinner');
  FLoadingPoints := False;
  end;
end;


procedure TFViewMap.lfMapCustomizeMarker(Sender: TObject; var ACustomizeMarker: string);
begin
  ACustomizeMarker :=
    'var m=' + MARKERVAR + ', o=m.options||{};' + #13#10 +
    'var rawTitle = (o && o.title) ? o.title : "";' + #13#10 +
    'var ds = (o && o.datastring) ? o.datastring : "";' + #13#10 +
    'if (ds === "device") {' + #13#10 +
    '  try { if (m.unbindTooltip) m.unbindTooltip(); } catch(e) {}' + #13#10 +
    '  try { if (m.unbindPopup)   m.unbindPopup();   } catch(e) {}' + #13#10 +
    '  return;' + #13#10 +
    '}' + #13#10 +
    'o.tooltipHtml = rawTitle;' + #13#10 +
    'o.title = "";' + #13#10 +
    'var t = o.tooltipHtml || "";' + #13#10 +
    'var u = (o.icon && o.icon.options && o.icon.options.iconUrl) ? o.icon.options.iconUrl : null;' + #13#10 +

    'try { if (m.unbindTooltip) m.unbindTooltip(); } catch(e) {}' + #13#10 +
    'try { if (m.unbindPopup)   m.unbindPopup();   } catch(e) {}' + #13#10 +

    // Note: we derive badgeText from icon filename suffix: *_2.png, *_3.png, *_4.png, *_5-9.png, etc.
    'var badgeText = "";' + #13#10 +
    'if (u) {' + #13#10 +
    '  try {' + #13#10 +
    '    var file = u.split("/").pop();' + #13#10 +
    '    var base = file.replace(/\.[^/.]+$/, "");' + #13#10 +
    '    var idx  = base.lastIndexOf("_");' + #13#10 +
    '    if (idx >= 0 && idx < base.length - 1) {' + #13#10 +
    '      var suffix = base.substring(idx + 1);' + #13#10 +
    '      if (suffix && suffix.length > 0) {' + #13#10 +
    '        var ch = suffix.charAt(0);' + #13#10 +
    '        if (ch >= "1" && ch <= "9") badgeText = ch;' + #13#10 +
    '      }' + #13#10 +
    '    }' + #13#10 +
    '  } catch(e) {}' + #13#10 +
    '}' + #13#10 +

    'var badgeColor = "#4b5563";' + #13#10 +
    'if (badgeText === "1") badgeColor = "#bc28d9";' + #13#10 +
    'else if (badgeText === "2") badgeColor = "#b91c1c";' + #13#10 +
    'else if (badgeText === "3") badgeColor = "#b45309";' + #13#10 +
    'else if (badgeText === "4") badgeColor = "#047857";' + #13#10 +

    'if (u) {' + #13#10 +
    '  var html = ''<div class="emi-marker-wrap"><img src="'' + u + ''" class="emi-marker-img">'';' + #13#10 +
    '  if (badgeText) {' + #13#10 +
    '    html += ''<div class="emi-marker-badge" style="background:'' + badgeColor + '';">'' + badgeText + ''</div>'';' + #13#10 +
    '  }' + #13#10 +
    '  html += ''</div>'';' + #13#10 +
    '  m.setIcon(L.divIcon({' + #13#10 +
    '    html: html,' + #13#10 +
    '    iconSize: [32,32],' + #13#10 +
    '    iconAnchor: [16,32],' + #13#10 +
    '    popupAnchor: [0,-20],' + #13#10 +
    '    className: ""' + #13#10 +
    '  }));' + #13#10 +
    '  try { if (m._icon) m._icon.removeAttribute("title"); } catch(e) {}' + #13#10 +
    '}' + #13#10 +

    'm.bindPopup(t, {' + #13#10 +
    '  className: "emi-tip",' + #13#10 +
    '  maxWidth: 260,' + #13#10 +
    '  closeButton: false,' + #13#10 +
    '  autoPan: true' + #13#10 +
    '});';
end;


procedure TFViewMap.lfMapCustomizeCSS(Sender: TObject; var ACustomizeCSS: string);
begin
  ACustomizeCSS :=
  // --- Popup container----------------------------------------------------------
    '.emi-tip .leaflet-popup-content-wrapper{' +
      'border-radius:8px;' +
      'box-shadow:0 4px 14px rgba(0,0,0,.25);' +
    '}' + #13#10 +
  // --- Popup content------------------------------------------------------------
    '.emi-tip .leaflet-popup-content{' +
      'margin:0;' +
      'padding:0;' +
      'width:auto;' +
      'max-width:260px;' +
    '}' + #13#10 +
  // --- Media Query: on very small screens, lets it grow almost full width ------
    '@media (max-width:480px){' +
      '.emi-tip .leaflet-popup-content{' +
        'max-width:calc(100vw - 32px);' +
      '}' +
    '}' + #13#10 +
  // --- Table: compact, wraps text ----------------------------------------------
    '.emi-tip .emi-tip-table{display:table;border-collapse:collapse;table-layout:auto;width:auto;margin:.25rem 0;}'+#13#10+
    '.emi-tip .emi-tip-table thead{display:table-header-group;}'+#13#10+
    '.emi-tip .emi-tip-table tbody{display:table-row-group;}'+#13#10+
    '.emi-tip .emi-tip-table tr{display:table-row;}'+#13#10+
    '.emi-tip .emi-tip-table th,.emi-tip .emi-tip-table td{' +
      'display:table-cell;' +
      'white-space:normal;' +
      'word-break:break-word;' +
      'padding:.125rem .25rem;' +
      'font-size:11px;' +
      'vertical-align:middle;' +
    '}' + #13#10 +
  // --- Marker Badge ------------------------------------------------------------
    '.emi-marker-wrap{position:relative;display:inline-block;}'+#13#10+
    '.emi-marker-img{display:block;}'+#13#10+
    '.emi-marker-badge{position:absolute;top:-4px;right:-4px;min-width:16px;height:16px;padding:0 4px;border-radius:999px;background:var(--bs-danger);color:#fff;font:700 11px/16px system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;text-align:center;box-shadow:0 0 0 2px #fff;}' + #13#10;
end;

procedure TFViewMap.tmrLocateTimer(Sender: TObject);
begin
  tmrLocate.Enabled := False;

  if not FDoFocusZoom then
    Exit;

  lfMap.SetCenterCoordinate(FPendingFocusCoord);
  lfMap.SetZoomLevel(FPendingFocusZoom);

  FDoFocusZoom := False;
end;

procedure TFViewMap.btnFindLocationClick(Sender: TObject);
var
  coord: TTMSFNCMapsCoordinateRec;
begin
  if userLocationMarker = nil then
    Exit;

  coord := CreateCoordinate(userLocationMarker.Latitude, userLocationMarker.Longitude);
  lfMap.SetCenterCoordinate(coord);

  FPendingFocusCoord := coord;
  FPendingFocusZoom := 17;
  FDoFocusZoom := True;

  tmrLocate.Interval := 250;
  tmrLocate.Enabled := True;
end;


procedure TFViewMap.FocusUnit(const unitId: string);
begin
  FPendingUnitId := Trim(unitId);
  if mapFilters <> nil then
    LoadPointsAsync(True);
end;


procedure TFViewMap.FocusComplaint(const complaintId: string);
begin
  FPendingComplaintId := Trim(complaintId);
  if mapFilters <> nil then
    LoadPointsAsync(True);
end;


procedure TFViewMap.ApplyPendingUnitFocus;
var
  i: Integer;
  m: TTMSFNCMapsMarker;
  coord: TTMSFNCMapsCoordinateRec;
  targetDs: string;
  found: Boolean;
begin
  if Trim(FPendingUnitId) = '' then
    Exit;

  targetDs := 'unit|' + FPendingUnitId;
  found := False;

  for i := 0 to lfMap.Markers.Count - 1 do
  begin
    m := lfMap.Markers[i];
    if SameText(m.DataString, targetDs) then
    begin
      coord := CreateCoordinate(m.Latitude, m.Longitude);
      lfmap.SetCenterCoordinate(coord);

      FPendingFocusCoord := coord;
      FPendingFocusZoom := 17;
      FDoFocusZoom := True;

      tmrLocate.Interval := 250;
      tmrLocate.Enabled := True;

      found := True;
      Break;
    end;
  end;

  if found then
    FPendingUnitId := '';
end;

procedure TFViewMap.ApplyPendingComplaintFocus;
var
  i: Integer;
  m: TTMSFNCMapsMarker;
  coord: TTMSFNCMapsCoordinateRec;
  targetDs: string;
  found: Boolean;
begin
  if Trim(FPendingComplaintId) = '' then
    Exit;

  targetDs := 'complaint|' + FPendingComplaintId;
  found := False;

  for i := 0 to lfMap.Markers.Count - 1 do
  begin
    m := lfMap.Markers[i];
    if SameText(m.DataString, targetDs) then
    begin
      coord := CreateCoordinate(m.Latitude, m.Longitude);
      lfmap.SetCenterCoordinate(coord);

      FPendingFocusCoord := coord;
      FPendingFocusZoom := 17;
      FDoFocusZoom := True;

      tmrLocate.Interval := 250;
      tmrLocate.Enabled := True;

      found := True;
      Break;
    end;
  end;

  if found then
    FPendingComplaintId := '';
end;


procedure TFViewMap.RefreshData;
begin
  Console.Log('Map.RefreshData');
  LoadPointsAsync(False);
end;


end.

