Repository: msupply-foundation/open-msupply
Pull request #6946: 6855 display gps coordinates in settings
Author: login: GeronimoJohn
Labels: name: needs design
description: Needs PO/UI/UX designer input
name: feature: GAPS
Created: 2025-03-17T03:07:38Z | Merged: 2025-03-19T23:01:26Z

<!-- IMPORTANT!
  - Every PR must reference an issue; this helps to explain the intent of the PR
 -->

Fixes #6855

# 👩🏻‍💻 What does this PR do?

Display GPS Coordinates within `Store Properties`. Users are able to save the stores current coordinates through clicking on `Update live location` button

<!-- Explain the changes you made -->

<!-- why are the changes needed -->

<!-- Add a screenshot if there are UI changes  -->

_**Taken from a Tablet**_

![Screenshot_20250317_153319_Open mSupply](https://github.com/user-attachments/assets/e07bcc0f-d057-4ad7-a2d3-3b750a490279)

**Initial State when coordinates are not set**

![Screenshot 2025-03-18 at 2 25 33 PM](https://github.com/user-attachments/assets/7b0f669d-b3b0-4c32-8bc2-deba07959fa5)


## 💌 Any notes for the reviewer?

<!-- Do you have any specific questions for the reviewer? -->

<!-- Is there a high risk/complicated change they should focus on? -->

<!-- any general areas of the codebase touched? any side effects caused? -->

<!-- Anything half cooked but going to be finished off in a different PR? -->

TLDR: Locations on Android Emulators a bit buggy. **Please test on an actual android device. APK link below**

Note for Android development: There may be issues with obtaining `locations` on the Android Emulator.
This issue was discussed a few years ago in this GitHub thread: https://github.com/ionic-team/capacitor/issues/4962.
It is unclear whether this issue has been resolved. Please test on a physical device.

APK: https://drive.google.com/drive/folders/1EtMYZZBOSnNhd4_l2W8kZ_T6UW8s0_ZI?usp=sharing (Will have to ask for access)


# 🧪 Testing

<!-- Explain the steps you'd take to test the changes of this PR manually -->

Please test on both Browser and Tablet!

- [ ] Navigate to Settings Page > Configuration > Click on Initialise Gaps
- [ ] Click on `Edit` button next Store Name in the footer
- [ ] GPS Coordinates should render without any values or just a bunch of zero
- [ ] Click on `Update live location` button
- [ ] GPS Coordinates should render!
   - [ ] Renders Latitude in raw format and formatted value (e.g. -36.84716 / S 36° 50' 49.776")
   - [ ] Renders Longitude in raw format and formatter value

# 📃 Documentation

- [ ] **Part of an epic**: documentation will be completed for the feature as a whole
- [ ] **No documentation required**: no user facing changes or a bug fix which isn't a change in behaviour
- [ ] **These areas should be updated or checked**: <!-- _(e.g.)_ New `issued` column in `Requisitions` indicates stock quantity already in shipments -->
  1.
  2.


# 📃 Reviewer Checklist

The PR Reviewer(s) should fill out this section before approving the PR

**Breaking Changes**
- [ ] No Breaking Changes in the Graphql API
- [ ] Technically some Breaking Changes but not expected to impact any integrations

**Issue Review**
- [ ] All requirements in original issue have been covered
- [ ] A follow up issue(s) have been created to cover additional requirements

**Tests Pass**
- [ ] Postgres
- [ ] SQLite
- [ ] Frontend



Linked issue #6855: Display GPS co-ordinates
Add current GPS co-ordinate to settings page

![Image](https://github.com/user-attachments/assets/9d8ed973-caff-435c-8325-cffeaffa4c1b)


Note: for now this will just be display only and the location for capturing the GPS will likely change.
Linked issue comments: user: login: mariyamsupply
created_at: 2025-03-07T01:19:28Z
body: Setting page or Help page??

What does 'Capture location now' button do? Updating your live location or Coping the one presented? 
author_association: CONTRIBUTOR
user: login: adamdewey
created_at: 2025-03-07T03:36:05Z
body: > What does 'Capture location now' button do? Updating your live location or Coping the one presented?

It's supposed to save the GPS values to the current Store so that they can be used to identify where the store is located
author_association: CONTRIBUTOR
user: login: mariyamsupply
created_at: 2025-03-09T22:37:18Z
body: After chatting w James and Mark I suggest we put GPS coordinates in Setting together with Store properties (taking them out from a footer modal) . Footer is a weird place for it.
so the GPS location can go to the same place.

<img width="632" alt="Image" src="https://github.com/user-attachments/assets/1f3353d5-80ee-48b4-8b7a-d638dfa0f61d" />

<img width="884" alt="Image" src="https://github.com/user-attachments/assets/b2ca54e6-16a3-4702-85a4-aa68270ff3de" />

--

the button should be called 'Update live location' and update coordinates when clicked. 
(we already present the location, just to make sure it's still correct).
plus 'Copy' coordinates could be useful.

<img width="1080" alt="Image" src="https://github.com/user-attachments/assets/ab570904-b259-470e-bfe0-4bd98e74ba73" />

<img width="1080" alt="Image" src="https://github.com/user-attachments/assets/9939ddac-8acd-4af5-8334-7a9cd4085538" />
author_association: CONTRIBUTOR
user: login: adamdewey
created_at: 2025-03-09T22:54:18Z
body: Hmm, I kind of disagree - I find it weird to have the store properties mixed up with the administrative settings?

Can't we put them in `Manage > Facilities`? 
author_association: CONTRIBUTOR
user: login: mark-prins
created_at: 2025-03-10T21:07:46Z
body: that would require adding `Manage` for all users, and having a menu item with only one item in it. You'd also have `Facilities` with only on facility in it. We have issues with the length of the menu already, and are trying to reduce the size of it. The `Manage` menu item is only shown on the central server - the intention of that menu item is for the management tasks (edit users, stores etc)

as discussed - the idea with the footer facility name having an edit is analogous to editing a user profile by clicking on your username / icon. 

Open to options - would prefer not to add more menu items though if we can avoid that.
author_association: COLLABORATOR
user: login: GeronimoJohn
created_at: 2025-03-12T04:13:07Z
body: Finally heading back into this ticket. 

Going to add GPS Coords in `Store Properties` as a start. Will only show it if open through Store Properties if that makes sense.

As to where we should move `Store Properties`. It was my first time initialising GAPS and going through the feature if you could say that. If I were to look for a `Store Properties` or any general properties of some sort my initial hunch would be to go to settings.
author_association: CONTRIBUTOR
user: login: jmbrunskill
created_at: 2025-03-12T04:50:26Z
body: Thanks @GeronimoJohn, I think @mark-prins wants to keep it under store properties in the bottom bar for now.
Maybe we could do the full screen modal and the left and right aligned columns as per Mariya's design though?
author_association: CONTRIBUTOR
user: login: GeronimoJohn
created_at: 2025-03-12T20:08:51Z
body: Sounds good to me!
author_association: CONTRIBUTOR
user: login: mark-prins
created_at: 2025-03-12T20:58:30Z
body: we haven't reached an agreement on where to put the store properties yet!
the full screen modal is a great idea 🙏 
that gets us working without spending too much time on it 
author_association: COLLABORATOR
user: login: adamdewey
created_at: 2025-03-12T21:21:00Z
body: One minor gripe about the alignment is that it's hard to distinguish the right-aligned numbers from the drop down arrows so it's hard to spot if there are any empty fields. 

Are there any easy alternatives? Left-aligning numbers, different colours between numbers and arrows?


author_association: CONTRIBUTOR
user: login: AnushaUp
created_at: 2025-04-18T06:44:28Z
body: # 🧪 Testing
Tested in V2.7.00-RC5
<!-- Explain the steps you'd take to test the changes of this PR manually -->

Please test on both Browser and Tablet!

- [x] Navigate to Settings Page > Configuration > Click on Initialise Gaps
- [x] Click on `Edit` button next Store Name in the footer
- [x] GPS Coordinates should render without any values or just a bunch of zero
- [x] Click on `Update live location` button
- [x] GPS Coordinates should render!
   - [x] Renders Latitude in raw format and formatted value (e.g. -36.84716 / S 36° 50' 49.776")
   - [x] Renders Longitude in raw format and formatter value
   ![Image](https://github.com/user-attachments/assets/71ddcd4c-b9a8-43ee-8aa0-aba60a34b4b1)

author_association: NONE

Diff:
diff --git a/client/packages/android/app/build.gradle b/client/packages/android/app/build.gradle
index 70f8cef1032..aa6c63be694 100644
--- a/client/packages/android/app/build.gradle
+++ b/client/packages/android/app/build.gradle
@@ -79,6 +79,7 @@ dependencies {
     androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
     androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
     implementation project(':capacitor-cordova-android-plugins')
+    implementation "com.google.android.gms:play-services-location:$playServicesLocationVersion"
 }
 
 apply from: 'capacitor.build.gradle'
diff --git a/client/packages/android/app/capacitor.build.gradle b/client/packages/android/app/capacitor.build.gradle
index 95bd5afaae7..e7c8eaea416 100644
--- a/client/packages/android/app/capacitor.build.gradle
+++ b/client/packages/android/app/capacitor.build.gradle
@@ -9,6 +9,7 @@ android {
 
 apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
 dependencies {
+    implementation project(':capacitor-geolocation')
     implementation project(':bcyesil-capacitor-plugin-printer')
     implementation project(':capacitor-community-barcode-scanner')
     implementation project(':capacitor-community-keep-awake')
diff --git a/client/packages/android/app/src/main/AndroidManifest.xml b/client/packages/android/app/src/main/AndroidManifest.xml
index 4d7ca380414..a3cd90212d7 100644
--- a/client/packages/android/app/src/main/AndroidManifest.xml
+++ b/client/packages/android/app/src/main/AndroidManifest.xml
@@ -38,4 +38,8 @@
     <!-- Permissions -->
 
     <uses-permission android:name="android.permission.INTERNET" />
+    <!-- Geolocation Plugin -->
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-feature android:name="android.hardware.location.gps" />
 </manifest>
diff --git a/client/packages/android/app/src/main/assets/capacitor.plugins.json b/client/packages/android/app/src/main/assets/capacitor.plugins.json
index 87e390516e0..6aac751a4c7 100644
--- a/client/packages/android/app/src/main/assets/capacitor.plugins.json
+++ b/client/packages/android/app/src/main/assets/capacitor.plugins.json
@@ -1,4 +1,8 @@
 [
+  {
+    "pkg": "@capacitor/geolocation",
+    "classpath": "com.capacitorjs.plugins.geolocation.GeolocationPlugin"
+  },
   {
     "pkg": "@bcyesil/capacitor-plugin-printer",
     "classpath": "com.bcyesil.capacitor.printer.PrinterPlugin"
diff --git a/client/packages/android/app/src/main/java/org/openmsupply/client/ExtendedWebViewClient.java b/client/packages/android/app/src/main/java/org/openmsupply/client/ExtendedWebViewClient.java
index 42f0fd6142a..8f0df1345b2 100644
--- a/client/packages/android/app/src/main/java/org/openmsupply/client/ExtendedWebViewClient.java
+++ b/client/packages/android/app/src/main/java/org/openmsupply/client/ExtendedWebViewClient.java
@@ -58,7 +58,7 @@ String generatePluginScript() {
         // see what plugins to add
 
         // This function needs to run after plugins are registered, so can't be part of the constructor as order doesn't appear to be consistent.
-        List<String> pluginNames =  Arrays.asList("NativeApi","Keyboard", "WebView","BarcodeScanner","Preferences", "KeepAwake", "App", "Printer");
+        List<String> pluginNames =  Arrays.asList("NativeApi","Keyboard", "WebView","BarcodeScanner","Preferences", "KeepAwake", "App", "Printer", "Geolocation");
         List<PluginHandle> pluginList = new ArrayList<>();
         for (String pluginName : pluginNames) {
             PluginHandle plugin = bridge.getPlugin(pluginName);
diff --git a/client/packages/android/capacitor-cordova-android-plugins/build.gradle b/client/packages/android/capacitor-cordova-android-plugins/build.gradle
index 74a62408480..2d9d0f217cf 100644
--- a/client/packages/android/capacitor-cordova-android-plugins/build.gradle
+++ b/client/packages/android/capacitor-cordova-android-plugins/build.gradle
@@ -9,7 +9,7 @@ buildscript {
         mavenCentral()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:8.0.0'
+        classpath 'com.android.tools.build:gradle:8.2.1'
     }
 }
 
@@ -17,10 +17,10 @@ apply plugin: 'com.android.library'
 
 android {
     namespace "capacitor.cordova.android.plugins"
-    compileSdkVersion project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 33
+    compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 34
     defaultConfig {
         minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 22
-        targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 33
+        targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 34
         versionCode 1
         versionName "1.0"
     }
diff --git a/client/packages/android/capacitor.settings.gradle b/client/packages/android/capacitor.settings.gradle
index 8bb91b774e9..6d2b3a338b1 100644
--- a/client/packages/android/capacitor.settings.gradle
+++ b/client/packages/android/capacitor.settings.gradle
@@ -2,6 +2,9 @@
 include ':capacitor-android'
 project(':capacitor-android').projectDir = new File('../../node_modules/@capacitor/android/capacitor')
 
+include ':capacitor-geolocation'
+project(':capacitor-geolocation').projectDir = new File('../../node_modules/@capacitor/geolocation/android')
+
 include ':bcyesil-capacitor-plugin-printer'
 project(':bcyesil-capacitor-plugin-printer').projectDir = new File('../../node_modules/@bcyesil/capacitor-plugin-printer/android')
 
diff --git a/client/packages/android/package.json b/client/packages/android/package.json
index aa7427f0cb9..cd1a8ec9400 100644
--- a/client/packages/android/package.json
+++ b/client/packages/android/package.json
@@ -19,6 +19,7 @@
   },
   "dependencies": {
     "@capacitor/android": "^6.1.0",
-    "@capacitor/core": "^6.1.0"
+    "@capacitor/core": "^6.1.0",
+    "@capacitor/geolocation": "^6.0.1"
   }
 }
diff --git a/client/packages/android/variables.gradle b/client/packages/android/variables.gradle
index 5946adabdd2..8bbe5c0173e 100644
--- a/client/packages/android/variables.gradle
+++ b/client/packages/android/variables.gradle
@@ -13,4 +13,5 @@ ext {
     androidxJunitVersion = '1.1.5'
     androidxEspressoCoreVersion = '3.5.1'
     cordovaAndroidVersion = '10.1.1'
+    playServicesLocationVersion = '21.1.0'
 }
\ No newline at end of file
diff --git a/client/packages/common/src/hooks/useDialog/useDialog.tsx b/client/packages/common/src/hooks/useDialog/useDialog.tsx
index 82fb2df54fb..73829d264db 100644
--- a/client/packages/common/src/hooks/useDialog/useDialog.tsx
+++ b/client/packages/common/src/hooks/useDialog/useDialog.tsx
@@ -47,6 +47,7 @@ export interface ModalProps {
   deleteButton?: JSX.Element;
   disableOkKeyBinding?: boolean;
   enableAutocomplete?: boolean;
+  fullscreen?: boolean;
 }
 
 export interface DialogProps {
@@ -159,6 +160,7 @@ export const useDialog = (dialogProps?: DialogProps): DialogState => {
     enableAutocomplete,
     sx = {},
     deleteButton,
+    fullscreen = false,
   }) => {
     // The slide animation is triggered by cloning the next button and wrapping the passed
     // on click with a trigger to slide.
@@ -223,7 +225,7 @@ export const useDialog = (dialogProps?: DialogProps): DialogState => {
       width: width ? Math.min(window.innerWidth - 50, width) : undefined,
     };
 
-    const fullScreen = isSmallerScreen && !disableMobileFullScreen;
+    const defaultFullscreen = isSmallerScreen && !disableMobileFullScreen;
 
     return (
       <BasicModal
@@ -234,9 +236,9 @@ export const useDialog = (dialogProps?: DialogProps): DialogState => {
         sx={sx}
         TransitionComponent={Transition}
         disableEscapeKeyDown={false}
-        fullScreen={fullScreen}
+        fullScreen={fullscreen ?? defaultFullscreen}
       >
-        {fullScreen && (
+        {defaultFullscreen && (
           <IconButton
             icon={<CloseIcon />}
             color="primary"
diff --git a/client/packages/common/src/intl/locales/en/common.json b/client/packages/common/src/intl/locales/en/common.json
index c71fd1fff31..553e6c212b2 100644
--- a/client/packages/common/src/intl/locales/en/common.json
+++ b/client/packages/common/src/intl/locales/en/common.json
@@ -272,6 +272,7 @@
   "error.fridge-tag-import": "Unable to import Fridge-tag logs: {{message}}",
   "error.fridge-tag-import-empty": "No data imported",
   "error.general-query-error": "There was an error communicating with the server 😢",
+  "error.geolocation-not-supported": "Geolocation is not supported by your browser.",
   "error.immunisations-not-found": "Immunizations not found",
   "error.inbound-shipment-not-editable": "Inbound shipment is not editable.",
   "error.integration-timeout-reached": "The central server took too long integrating pushed records. Please retry later.",
@@ -351,9 +352,11 @@
   "error.other-party-not-a-supplier": "The other party is not a supplier",
   "error.other-party-not-visible": "Other party not visible in this store",
   "error.pack-variant-exists": "Pack variant with the same pack size already exists for item",
+  "error.permission-denied": "Permission to access location was denied.",
   "error.plugin": "There was an error loading the plugin {{name}}",
   "error.plugin-payment-amount-outstanding": "Amount paid is less than the amount outstanding",
   "error.plugin-unavailable": "Plugin unavailable",
+  "error.position-unavailable": "Location information is unavailable.",
   "error.prescription-not-found": "Prescription not found.",
   "error.printing-label": "Error printing label.",
   "error.problem-saving": "An error occurred: there was a problem saving the data.",
@@ -393,6 +396,7 @@
   "error.sync-v6-api-incompatible": "Sync V6 API version is not compatible",
   "error.sync-v6-api-incompatible-hint": "The Open mSupply Central Server needs to be upgraded to a newer version before you can connect - please contact support@msupply.foundation",
   "error.too-many-datapoints": "Warning: There are too many data points to display. Please reduce the date range or number of sensors to view all the data.",
+  "error.timeout": "The request to get your location timed out.",
   "error.unable-to-calculate": "Unable to calculate",
   "error.unable-to-connect": "Unable to connect to server {{server}}",
   "error.unable-to-connect-to-printer": "Unable to connect to printer.",
@@ -417,6 +421,7 @@
   "error.unable-to-update-immunisation-program": "Unable to update Immunisation Program",
   "error.unable-to-update-vaccine-course": "Unable to update vaccine course",
   "error.unique-value-violation": "A record with this {{ field }} already exists",
+  "error.unknown-geolocation-error": "An unknown error occurred while retrieving your location.",
   "error.unknown-sync-error": "Unknown sync error",
   "error.upserting-vaccine-courses": "Unknown error. Cannot change vaccine courses",
   "error.v6-server-not-configured": "V6 central server not configured",
@@ -735,6 +740,7 @@
   "label.discount-percentage": "Discount percentage",
   "label.discount-rate": "Discount rate",
   "label.dispensing-date": "Dispensing Date",
+  "label.distance": "Your Distance (km)",
   "label.document-edit-history": "Document Edit History",
   "label.documents": "Documents",
   "label.donor": "Donor",
@@ -794,6 +800,7 @@
   "label.fc-line-total": "FC Line Total",
   "label.fc-sell-price": "FC Sell Price",
   "label.feedback": "Feedback",
+  "label.fetching-coordinates": "Fetching coordinates...",
   "label.filters": "Filters",
   "label.finalise": "Finalise",
   "label.finalised": "Finalised",
@@ -811,6 +818,7 @@
   "label.general": "General",
   "label.general-population": "General Population",
   "label.generate": "Generate",
+  "label.gps-coordinates": "GPS coordinates",
   "label.group-by-item": "Group by Item",
   "label.growth-on-previous-year": "Growth on previous year",
   "label.have-not-shipped": "Have not been shipped",
@@ -866,6 +874,7 @@
   "label.last-record": "Last recording date / time",
   "label.last-recording-value": "Last temperature recording value",
   "label.last-updated": "Last updated",
+  "label.latitude": "Latitude",
   "label.lead-time": "Months lead time",
   "label.leave-as-pending": "Leave as pending",
   "label.ledger": "Ledger",
@@ -877,6 +886,7 @@
   "label.location": "Location",
   "label.locked": "Locked",
   "label.log": "Log",
+  "label.longitude": "Longitude",
   "label.losses": "Losses",
   "label.low-stock": "Low stock",
   "label.low-stock-items_few": "Items with less than 3 months of stock",
@@ -1178,6 +1188,7 @@
   "label.universal-code": "Universal code",
   "label.universal-name": "Universal name",
   "label.update-transactions": "Update stock transactions",
+  "label.update-live-location": "Update live location",
   "label.upload": "Upload",
   "label.upload-files": "Upload files",
   "label.use-catalogue": "Use asset catalogue",
diff --git a/client/packages/common/src/ui/icons/Icon.stories.tsx b/client/packages/common/src/ui/icons/Icon.stories.tsx
index f06dc06a921..123aca997bf 100644
--- a/client/packages/common/src/ui/icons/Icon.stories.tsx
+++ b/client/packages/common/src/ui/icons/Icon.stories.tsx
@@ -44,6 +44,7 @@ import { InfoOutlineIcon } from './InfoOutline';
 import { InvoiceIcon } from './Invoice';
 import { LinkIcon } from './Link';
 import { ListIcon } from './List';
+import { LocationIcon } from './Location';
 import { MSupplyGuy, AnimatedMSupplyGuy } from './MSupplyGuy';
 import { MedicineIcon } from './MedicineIcon';
 import { MenuDotsIcon } from './MenuDots';
@@ -147,6 +148,7 @@ const Template: StoryFn<React.FC<SvgIconProps>> = args => {
     { icon: <InvoiceIcon {...args} />, name: 'Invoice' },
     { icon: <LinkIcon {...args} />, name: 'Link' },
     { icon: <ListIcon {...args} />, name: 'List' },
+    { icon: <LocationIcon {...args} />, name: 'Location' },
     { icon: <MSupplyGuy {...args} size="medium" />, name: 'MSupplyGuy' },
     {
       icon: <AnimatedMSupplyGuy {...args} size="medium" />,
diff --git a/client/packages/common/src/ui/icons/Location.tsx b/client/packages/common/src/ui/icons/Location.tsx
new file mode 100644
index 00000000000..9bc1de5d886
--- /dev/null
+++ b/client/packages/common/src/ui/icons/Location.tsx
@@ -0,0 +1,32 @@
+import React from 'react';
+import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon';
+
+export const LocationIcon = (props: SvgIconProps): JSX.Element => {
+  return (
+    <SvgIcon {...props} viewBox="0 0 24 24">
+      <path
+        fillRule="evenodd"
+        clipRule="evenodd"
+        d="M14.0292 22.7354C15.1183 21.8682 16.207 20.8883 17.226 19.8127C20.1976 16.676 22 13.3716 22 10C22 4.47715 17.5228 0 12 0C6.47715 0 2 4.47715 2 10C2 13.3716 3.8024 16.676 6.77405 19.8127C7.793 20.8883 8.88175 21.8682 9.97082 22.7354C10.3526 23.0394 10.7078 23.3081 11.0278 23.5392C11.2228 23.68 11.3649 23.7784 11.4453 23.8321C11.7812 24.056 12.2188 24.056 12.5547 23.8321C12.6351 23.7784 12.7772 23.68 12.9722 23.5392C13.2922 23.3081 13.6474 23.0394 14.0292 22.7354ZM15.774 18.4373C14.8242 19.4398 13.8036 20.3584 12.7833 21.1708C12.5048 21.3926 12.2423 21.5936 12 21.7726C11.7577 21.5936 11.4952 21.3926 11.2167 21.1708C10.1964 20.3584 9.17575 19.4398 8.22595 18.4373C5.5726 15.6365 4 12.7534 4 10C4 5.58172 7.58172 2 12 2C16.4183 2 20 5.58172 20 10C20 12.7534 18.4274 15.6365 15.774 18.4373ZM12 14C9.79086 14 8 12.2091 8 10C8 7.79086 9.79086 6 12 6C14.2091 6 16 7.79086 16 10C16 12.2091 14.2091 14 12 14ZM14 10C14 11.1046 13.1046 12 12 12C10.8954 12 10 11.1046 10 10C10 8.89543 10.8954 8 12 8C13.1046 8 14 8.89543 14 10Z"
+      />
+      <mask
+        id="mask0_59008_1828"
+        maskUnits="userSpaceOnUse"
+        x="2"
+        y="0"
+        width="20"
+        height="24"
+        style={{
+          maskType: 'luminance',
+        }}
+      >
+        <path
+          fillRule="evenodd"
+          clipRule="evenodd"
+          d="M14.0292 22.7354C15.1183 21.8682 16.207 20.8883 17.226 19.8127C20.1976 16.676 22 13.3716 22 10C22 4.47715 17.5228 0 12 0C6.47715 0 2 4.47715 2 10C2 13.3716 3.8024 16.676 6.77405 19.8127C7.793 20.8883 8.88175 21.8682 9.97082 22.7354C10.3526 23.0394 10.7078 23.3081 11.0278 23.5392C11.2228 23.68 11.3649 23.7784 11.4453 23.8321C11.7812 24.056 12.2188 24.056 12.5547 23.8321C12.6351 23.7784 12.7772 23.68 12.9722 23.5392C13.2922 23.3081 13.6474 23.0394 14.0292 22.7354ZM15.774 18.4373C14.8242 19.4398 13.8036 20.3584 12.7833 21.1708C12.5048 21.3926 12.2423 21.5936 12 21.7726C11.7577 21.5936 11.4952 21.3926 11.2167 21.1708C10.1964 20.3584 9.17575 19.4398 8.22595 18.4373C5.5726 15.6365 4 12.7534 4 10C4 5.58172 7.58172 2 12 2C16.4183 2 20 5.58172 20 10C20 12.7534 18.4274 15.6365 15.774 18.4373ZM12 14C9.79086 14 8 12.2091 8 10C8 7.79086 9.79086 6 12 6C14.2091 6 16 7.79086 16 10C16 12.2091 14.2091 14 12 14ZM14 10C14 11.1046 13.1046 12 12 12C10.8954 12 10 11.1046 10 10C10 8.89543 10.8954 8 12 8C13.1046 8 14 8.89543 14 10Z"
+          fill="white"
+        />
+      </mask>
+    </SvgIcon>
+  );
+};
diff --git a/client/packages/common/src/ui/icons/index.ts b/client/packages/common/src/ui/icons/index.ts
index eecb582e2eb..f674891771d 100644
--- a/client/packages/common/src/ui/icons/index.ts
+++ b/client/packages/common/src/ui/icons/index.ts
@@ -40,6 +40,7 @@ export { InfoOutlineIcon } from './InfoOutline';
 export { InvoiceIcon } from './Invoice';
 export { LinkIcon } from './Link';
 export { ListIcon } from './List';
+export { LocationIcon } from './Location';
 export { MaximiseIcon } from './Maximise';
 export { MailIcon } from './Mail';
 export { MSupplyGuy, AnimatedMSupplyGuy } from './MSupplyGuy';
diff --git a/client/packages/host/src/Help/Help.tsx b/client/packages/host/src/Help/Help.tsx
index 74c9a8e913c..f052117496e 100644
--- a/client/packages/host/src/Help/Help.tsx
+++ b/client/packages/host/src/Help/Help.tsx
@@ -24,7 +24,6 @@ export const Help = () => {
       <Typography style={{ paddingBottom: 10 }}>
         {t('message.keyboard-shortcuts')}
       </Typography>
-
       <ContactFormSection />
       <AppBarButtonsPortal>
         <AppVersion SiteInfo={<SiteInfo siteName={initStatus?.siteName} />} />
diff --git a/client/packages/host/src/api/hooks/settings/namePropertyData.ts b/client/packages/host/src/api/hooks/settings/namePropertyData.ts
index 12b42ee05b4..9e4d9e005df 100644
--- a/client/packages/host/src/api/hooks/settings/namePropertyData.ts
+++ b/client/packages/host/src/api/hooks/settings/namePropertyData.ts
@@ -8,6 +8,24 @@ export const gapsNameProperties: {
   en: ConfigureNamePropertyInput[];
 } & Partial<Record<SupportedLocales, ConfigureNamePropertyInput[]>> = {
   en: [
+    {
+      id: '0ed01a18-c9ac-4b51-bb56-d5fea4f15feb',
+      propertyId: 'latitude',
+      key: 'latitude',
+      name: 'Latitude',
+      valueType: PropertyNodeValueType.Float,
+      allowedValues: null,
+      remoteEditable: false,
+    },
+    {
+      id: '9d595b3e-2eca-4b1a-983e-77aa34b14e62',
+      propertyId: 'longitude',
+      key: 'longitude',
+      name: 'Longitude',
+      valueType: PropertyNodeValueType.Float,
+      allowedValues: null,
+      remoteEditable: false,
+    },
     {
       id: '3285c231-ffc2-485b-9a86-5ccafed9a5c5',
       propertyId: 'supply_level',
diff --git a/client/packages/host/src/components/Footer/Footer.tsx b/client/packages/host/src/components/Footer/Footer.tsx
index ef9e1504ac5..23c1a0d5b40 100644
--- a/client/packages/host/src/components/Footer/Footer.tsx
+++ b/client/packages/host/src/components/Footer/Footer.tsx
@@ -52,13 +52,6 @@ export const Footer: React.FC = () => {
       paddingY={0.75}
       paddingX={0}
     >
-      {isOpen && (
-        <FacilityEditModal
-          nameId={store?.nameId ?? ''}
-          isOpen={isOpen}
-          onClose={onClose}
-        />
-      )}
       <StoreSelector>
         <PaddedCell>
           <HomeIcon sx={iconStyles} />
@@ -116,6 +109,13 @@ export const Footer: React.FC = () => {
           </Typography>
         </Box>
       ) : null}
+      {isOpen && (
+        <FacilityEditModal
+          nameId={store?.nameId ?? ''}
+          isOpen={isOpen}
+          onClose={onClose}
+        />
+      )}
     </Box>
   );
 };
diff --git a/client/packages/system/package.json b/client/packages/system/package.json
index 3ea614ccccc..e0ad07545ba 100644
--- a/client/packages/system/package.json
+++ b/client/packages/system/package.json
@@ -12,6 +12,7 @@
     "eslint": "eslint ./src"
   },
   "dependencies": {
+    "@capacitor/geolocation": "^6.0.1",
     "@openmsupply-client/common": "^0.0.1",
     "@openmsupply-client/config": "^0.0.0",
     "@openmsupply-client/requisitions": "^0.0.0",
diff --git a/client/packages/system/src/Name/ListView/Facilities/DisplayCoordinates.tsx b/client/packages/system/src/Name/ListView/Facilities/DisplayCoordinates.tsx
new file mode 100644
index 00000000000..5472d07610f
--- /dev/null
+++ b/client/packages/system/src/Name/ListView/Facilities/DisplayCoordinates.tsx
@@ -0,0 +1,199 @@
+import React, { ReactElement, useEffect, useState } from 'react';
+import {
+  Alert,
+  ButtonWithIcon,
+  Box,
+  InputWithLabelRow,
+  ReadOnlyInput,
+  Stack,
+  Typography,
+  useTranslation,
+  LocationIcon,
+} from '@openmsupply-client/common';
+import { Geolocation } from '@capacitor/geolocation';
+import { Capacitor } from '@capacitor/core';
+
+interface DisplayCoordinatesProps {
+  latitude: number;
+  longitude: number;
+  onDraftPropertiesChange: (latitude: number, longitude: number) => void;
+}
+
+export const DisplayCoordinates = ({
+  latitude,
+  longitude,
+  onDraftPropertiesChange,
+}: DisplayCoordinatesProps): ReactElement => {
+  const t = useTranslation();
+
+  const [loading, setLoading] = useState(false);
+  const [distance, setDistance] = useState<number>();
+  const [errorMessage, setErrorMessage] = useState<string>();
+
+  // Follows haversine formula to calculate the distance
+  const calculateDistance = (targetLat: number, targetLng: number) => {
+    const R = 6371; // Earth radius in kilometers
+    const toRadians = (degrees: number) => degrees * (Math.PI / 180);
+
+    const lat1 = toRadians(latitude);
+    const lat2 = toRadians(targetLat);
+    const deltaLat = toRadians(targetLat - latitude);
+    const deltaLng = toRadians(targetLng - longitude);
+
+    const a =
+      Math.sin(deltaLat / 2) ** 2 +
+      Math.cos(lat1) * Math.cos(lat2) * Math.sin(deltaLng / 2) ** 2;
+
+    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+
+    const distance = R * c; // in Kilometers
+    setDistance(parseFloat(distance.toFixed(6)));
+  };
+
+  const updateCoordinates = (latitude: number, longitude: number): void => {
+    const fixedLatitude = parseFloat(latitude.toFixed(6));
+    const fixedLongitude = parseFloat(longitude.toFixed(6));
+    onDraftPropertiesChange(fixedLatitude, fixedLongitude);
+  };
+
+  const handleGeolocationWebError = (error: GeolocationPositionError) => {
+    switch (error.code) {
+      case error.PERMISSION_DENIED:
+        setErrorMessage(t('error.permission-denied'));
+        break;
+      case error.POSITION_UNAVAILABLE:
+        setErrorMessage(t('error.position-unavailable'));
+        break;
+      case error.TIMEOUT:
+        setErrorMessage(t('error.timeout'));
+        break;
+      default:
+        setErrorMessage(t('error.unknown-geolocation-error'));
+        break;
+    }
+  };
+
+  const fetchCoordinates = async (
+    onSuccess: (latitude: number, longitude: number) => void
+  ) => {
+    setLoading(true);
+    try {
+      const isNativePlatform = Capacitor.isNativePlatform();
+      const isGeolocationPluginAvailable =
+        Capacitor.isPluginAvailable('Geolocation');
+
+      // Sets coordinates for Android devices
+      if (isGeolocationPluginAvailable && isNativePlatform) {
+        const geolocationPermission = await Geolocation.checkPermissions();
+
+        if (
+          geolocationPermission.location !== 'granted' ||
+          geolocationPermission.coarseLocation !== 'granted'
+        ) {
+          await Geolocation.requestPermissions();
+        }
+
+        const position = await Geolocation.getCurrentPosition({
+          enableHighAccuracy: true,
+        });
+        onSuccess(position.coords.latitude, position.coords.longitude);
+        return;
+      }
+
+      // Sets coordinates for browsers
+      if ('geolocation' in navigator && !isNativePlatform) {
+        navigator.geolocation.getCurrentPosition(
+          (position: GeolocationPosition) =>
+            onSuccess(position.coords.latitude, position.coords.longitude),
+          (error: GeolocationPositionError) => handleGeolocationWebError(error),
+          { enableHighAccuracy: true }
+        );
+        return;
+      }
+
+      setErrorMessage(t('error.geolocation-not-supported'));
+      return;
+    } catch (error) {
+      setErrorMessage(t('error.unknown-geolocation-error'));
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  useEffect(() => {
+    // If latitude and longitude are provided, calculate the user's distance from them
+    if (latitude !== 0 && longitude !== 0) fetchCoordinates(calculateDistance);
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [latitude, longitude]);
+
+  const formatCoordinate = (
+    coordinate: number,
+    isLatitude?: boolean
+  ): string => {
+    const direction = isLatitude
+      ? coordinate >= 0
+        ? 'N'
+        : 'S'
+      : coordinate >= 0
+        ? 'E'
+        : 'W';
+
+    const absolute = Math.abs(coordinate);
+    const degrees = Math.floor(absolute);
+    const minutes = Math.floor((absolute - degrees) * 60);
+    const seconds = ((absolute * 3600) % 60).toFixed(3);
+    return `${direction} ${degrees}° ${minutes}' ${seconds}"`;
+  };
+
+  const isLatitude = true;
+  const formattedLatitude = formatCoordinate(latitude ?? 0, isLatitude);
+  const formattedLongitude = formatCoordinate(longitude ?? 0, !isLatitude);
+
+  return (
+    <>
+      <Typography fontWeight="bold">{t('label.gps-coordinates')}:</Typography>
+      {loading ? (
+        <Box sx={{ pb: 2, display: 'flex', justifyContent: 'center' }}>
+          <Typography>{t('label.fetching-coordinates')}</Typography>
+        </Box>
+      ) : errorMessage && latitude === 0 && longitude === 0 ? (
+        <Box sx={{ pb: 2, display: 'flex', justifyContent: 'center' }}>
+          <Alert severity="error">{errorMessage}</Alert>
+        </Box>
+      ) : (
+        <Stack direction="row">
+          <Box sx={{ flexGrow: 1 }}>
+            <InputWithLabelRow
+              label={t('label.latitude')}
+              Input={
+                <ReadOnlyInput value={`${latitude} / ${formattedLatitude}`} />
+              }
+            />
+            <InputWithLabelRow
+              label={t('label.longitude')}
+              Input={
+                <ReadOnlyInput value={`${longitude} / ${formattedLongitude}`} />
+              }
+            />
+          </Box>
+          <ButtonWithIcon
+            onClick={() => {
+              fetchCoordinates(updateCoordinates);
+            }}
+            Icon={<LocationIcon />}
+            label={t('label.update-live-location')}
+          />
+        </Stack>
+      )}
+      <InputWithLabelRow
+        label={t('label.distance')}
+        labelProps={{
+          sx: {
+            width: 180,
+          },
+        }}
+        Input={<ReadOnlyInput value={`${distance ?? 0}`} />}
+      />
+    </>
+  );
+};
diff --git a/client/packages/system/src/Name/ListView/Facilities/FacilityEditModal.tsx b/client/packages/system/src/Name/ListView/Facilities/FacilityEditModal.tsx
index abaa40151b3..e838c296d85 100644
--- a/client/packages/system/src/Name/ListView/Facilities/FacilityEditModal.tsx
+++ b/client/packages/system/src/Name/ListView/Facilities/FacilityEditModal.tsx
@@ -1,4 +1,10 @@
-import React, { FC, useEffect, useState } from 'react';
+import React, {
+  Dispatch,
+  FC,
+  SetStateAction,
+  useEffect,
+  useState,
+} from 'react';
 import {
   useTranslation,
   DetailContainer,
@@ -17,18 +23,21 @@ import {
 } from '@openmsupply-client/common';
 import { useName } from '../../api';
 import { NameRenderer } from '../..';
+import { DisplayCoordinates } from './DisplayCoordinates';
 
-interface FacilityEditModalProps {
-  nameId: string;
-  isOpen: boolean;
-  onClose: () => void;
-  setNextFacility?: (nameId: string) => void;
+export type DraftProperty = Record<string, string | number | boolean | null>;
+
+interface DraftFacilityProperties {
+  draftProperties: DraftProperty;
+  setDraftProperties: Dispatch<SetStateAction<DraftProperty>>;
 }
 
-const useDraftFacilityProperties = (initialProperties?: string | null) => {
-  const [draftProperties, setDraftProperties] = useState<
-    Record<string, string | number | boolean | null>
-  >(ObjUtils.parse(initialProperties));
+const useDraftFacilityProperties = (
+  initialProperties?: string | null
+): DraftFacilityProperties => {
+  const [draftProperties, setDraftProperties] = useState<DraftProperty>(
+    ObjUtils.parse(initialProperties)
+  );
 
   useEffect(() => {
     const parsedProperties = ObjUtils.parse(initialProperties);
@@ -42,6 +51,13 @@ const useDraftFacilityProperties = (initialProperties?: string | null) => {
   };
 };
 
+interface FacilityEditModalProps {
+  nameId: string;
+  isOpen: boolean;
+  onClose: () => void;
+  setNextFacility?: (nameId: string) => void;
+}
+
 export const FacilityEditModal: FC<FacilityEditModalProps> = ({
   nameId,
   isOpen,
@@ -104,18 +120,31 @@ export const FacilityEditModal: FC<FacilityEditModalProps> = ({
       }
       height={600}
       width={700}
+      fullscreen
     >
       <DetailContainer>
-        <Box display="flex" flexDirection="column" alignItems="center" gap={2}>
+        <Box display="flex" flexDirection="column" gap={2}>
           <NameRenderer
             isStore={!!data.store}
             label={data.name}
             sx={{ fontWeight: 'bold', fontSize: 18 }}
           />
-
-          <Box display="flex">
-            <Typography fontWeight="bold">{t('label.code')}:</Typography>
-            <Typography paddingX={1}>{data.code}</Typography>
+          <Box display="flex" flexDirection="column">
+            <Box display="flex" flexDirection="row">
+              <Typography fontWeight="bold">{t('label.code')}:</Typography>
+              <Typography paddingX={1}>{data.code}</Typography>
+            </Box>
+            <DisplayCoordinates
+              latitude={(draftProperties['latitude'] as number) ?? 0}
+              longitude={(draftProperties['longitude'] as number) ?? 0}
+              onDraftPropertiesChange={(latitude, longitude) => {
+                setDraftProperties({
+                  ...draftProperties,
+                  latitude,
+                  longitude,
+                });
+              }}
+            />
           </Box>
           <DetailSection title="">
             {!properties?.length ? (
@@ -130,29 +159,35 @@ export const FacilityEditModal: FC<FacilityEditModalProps> = ({
                     minWidth: '340px',
                     paddingX: '2em',
                   },
-                  width: '500px',
+                  width: '600px',
                   display: 'grid',
                   gap: 1,
                 })}
               >
-                {properties.map(p => (
-                  <Row
-                    key={p.id}
-                    label={p.property.name}
-                    isGapsStore={isGapsStore}
-                    inputProperties={{
-                      disabled: !isCentralServer && !p.remoteEditable,
-                      valueType: p.property.valueType,
-                      allowedValues: p.property.allowedValues?.split(','),
-                      value: draftProperties[p.property.key],
-                      onChange: v =>
-                        setDraftProperties({
-                          ...draftProperties,
-                          [p.property.key]: v ?? null,
-                        }),
-                    }}
-                  />
-                ))}
+                {properties
+                  .filter(
+                    p =>
+                      p.property.key !== 'latitude' &&
+                      p.property.key !== 'longitude'
+                  )
+                  .map(p => (
+                    <Row
+                      key={p.id}
+                      label={p.property.name}
+                      isGapsStore={isGapsStore}
+                      inputProperties={{
+                        disabled: !isCentralServer && !p.remoteEditable,
+                        valueType: p.property.valueType,
+                        allowedValues: p.property.allowedValues?.split(','),
+                        value: draftProperties[p.property.key],
+                        onChange: v =>
+                          setDraftProperties({
+                            ...draftProperties,
+                            [p.property.key]: v ?? null,
+                          }),
+                      }}
+                    />
+                  ))}
               </Box>
             )}
           </DetailSection>
@@ -193,7 +228,6 @@ const Row = ({
             width: '250px',
             fontSize: '16px',
             paddingRight: 2,
-            textAlign: 'right',
           },
         }}
         Input={
diff --git a/client/yarn.lock b/client/yarn.lock
index cdcc6564f6f..da9e60e3045 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -1319,6 +1319,11 @@
   dependencies:
     tslib "^2.1.0"
 
+"@capacitor/geolocation@^6.0.1":
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/@capacitor/geolocation/-/geolocation-6.1.0.tgz#cce926a19730a4bccdf2cecb49da1adabc876a00"
+  integrity sha512-jEY5DcZirxX1gOBuOvf/FL7FlPMOKcsnF8PlfYxu7OFwRDD7HRwehEPWtJXR6wSYLgsCujm7yRqKrjtyN+f14g==
+
 "@capacitor/keyboard@^6.0.1":
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/@capacitor/keyboard/-/keyboard-6.0.1.tgz#76add27a20239eefc3353b970fd506036d948d18"


Reviews:
user: login: jmbrunskill
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T04:30:13Z
user: login: jmbrunskill
body: Works good for me, like @adamdewey said would be good to try it on a real phone. I think he's bringing one up tomorrow.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T04:30:40Z
user: login: GeronimoJohn
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T20:10:25Z
user: login: jmbrunskill
body: Looks good John. Happy to merge, or we can wait an re-view with the saving changes up to you.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T22:38:11Z
user: login: GeronimoJohn
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T04:10:05Z
user: login: jmbrunskill
body: Looks great thanks John!

There's one small bug but I think we can live with it. 

If you don't allow location permission, you should probably still be able to see the configured lat and long. Especially for the central server would be good to see all the site gps without allowing permissions...

Happy to merge though and come back to that later if we decide too.

state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T03:07:04Z

Review comments:
diff_hunk: @@ -177,32 +181,31 @@ const Row = ({
   isGapsStore,
   inputProperties,
 }: {
-  key: string,
-  label: string,
-  isGapsStore: boolean,
-  inputProperties: PropertyInput,
+  key: string;
+  label: string;
+  isGapsStore: boolean;
+  inputProperties: PropertyInput;
 }) => {
-  if (!isGapsStore) return (
-    <InputWithLabelRow
-      key={key}
-      label={label}
-      sx={{ width: '100%' }}
-      labelProps={{
-        sx: {
-          width: '250px',
-          fontSize: '16px',
-          paddingRight: 2,
-          textAlign: 'right',
-        },
-      }}
-      Input={
-        <Box flex={1}>
-          <PropertyInput
-            {...inputProperties}
-          />
-        </Box>
-      }
-    />)
+  if (!isGapsStore)
path: client/packages/system/src/Name/ListView/Facilities/FacilityEditModal.tsx
user: login: jmbrunskill
body: No entirely related but Interested to see why we need this?
created_at: 2025-03-17T04:30:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -177,32 +181,31 @@ const Row = ({
   isGapsStore,
   inputProperties,
 }: {
-  key: string,
-  label: string,
-  isGapsStore: boolean,
-  inputProperties: PropertyInput,
+  key: string;
+  label: string;
+  isGapsStore: boolean;
+  inputProperties: PropertyInput;
 }) => {
-  if (!isGapsStore) return (
-    <InputWithLabelRow
-      key={key}
-      label={label}
-      sx={{ width: '100%' }}
-      labelProps={{
-        sx: {
-          width: '250px',
-          fontSize: '16px',
-          paddingRight: 2,
-          textAlign: 'right',
-        },
-      }}
-      Input={
-        <Box flex={1}>
-          <PropertyInput
-            {...inputProperties}
-          />
-        </Box>
-      }
-    />)
+  if (!isGapsStore)
path: client/packages/system/src/Name/ListView/Facilities/FacilityEditModal.tsx
user: login: GeronimoJohn
body: This component recently got added, I think the changes showing would be the linter kicking on my end.
created_at: 2025-03-17T20:10:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,202 @@
+import React, { ReactElement, useEffect, useState } from 'react';
+import {
+  Alert,
+  ButtonWithIcon,
+  Box,
+  InputWithLabelRow,
+  ReadOnlyInput,
+  Stack,
+  Typography,
+  useTranslation,
+  LocationIcon,
+} from '@openmsupply-client/common';
+import { Geolocation } from '@capacitor/geolocation';
+import { Capacitor } from '@capacitor/core';
+
+interface DisplayCoordinatesProps {
+  latitude?: number;
+  longitude?: number;
+  onDraftPropertiesChange: (latitude: number, longitude: number) => void;
+}
+
+export const DisplayCoordinates = ({
+  latitude,
+  longitude,
+  onDraftPropertiesChange,
+}: DisplayCoordinatesProps): ReactElement => {
+  const t = useTranslation();
+
+  const [loading, setLoading] = useState(false);
+  const [errorMessage, setErrorMessage] = useState<string>();
+  const [distance, setDistance] = useState<number>();
+
+  // Follows haversine formula to calculate the distance
path: client/packages/system/src/Name/ListView/Facilities/DisplayCoordinates.tsx
user: login: GeronimoJohn
body: https://www.movable-type.co.uk/scripts/latlong.html
created_at: 2025-03-18T04:10:05Z
author_association: CONTRIBUTOR

PR comments:
user: login: adamdewey
created_at: 2025-03-17T03:18:23Z
body: Please test on a phone :)
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-17T03:21:29Z
body: <!-- GITHUB_ACTION_COMMENT_PR -->
## Bundle size difference
Comparing this PR to `main`

|Old size      | New size     | Diff                     |
|:------------ |:-------------|:-------------------------|
|6.06 MB         |6.07 MB         | 8.43 KB (0.14%) |

author_association: CONTRIBUTOR
user: login: GeronimoJohn
created_at: 2025-03-17T23:54:43Z
body: Thanks @jmbrunskill, can we wait for a re-review please. The changes added are minimal, almost done with it
author_association: CONTRIBUTOR
user: login: GeronimoJohn
created_at: 2025-03-18T01:37:09Z
body: Ready for another set of review @jmbrunskill please. Enabled the `Update live location` button. Enables the user to save the current coordinates of the store: https://github.com/msupply-foundation/open-msupply/pull/6946/commits/4704cb9cc392bcd2361ba27d052ce6e103a0e0fb 

Further code clean up: https://github.com/msupply-foundation/open-msupply/pull/6946/commits/7a9bb94d29b8589e32464df7ff034de85405a315

User distance from coordinates: https://github.com/msupply-foundation/open-msupply/pull/6946/commits/a92a8225e0f5a5360fb2e8c2c28346f0f8bce45c

Will test on phone soon
author_association: CONTRIBUTOR
user: login: GeronimoJohn
created_at: 2025-03-19T22:57:58Z
body: > Looks great thanks John!
> 
> There's one small bug but I think we can live with it.
> 
> If you don't allow location permission, you should probably still be able to see the configured lat and long. Especially for the central server would be good to see all the site gps without allowing permissions...
> 
> Happy to merge though and come back to that later if we decide too.

Fixed here: https://github.com/msupply-foundation/open-msupply/pull/6946/commits/07820d215b3303c048ac0fbe22772826f63c883a 🙏🏽 
author_association: CONTRIBUTOR