Locations
Locations represent sender (Absender) and receiver (Empfänger) addresses for trips. Each location can have GPS coordinates and multiple contacts.
Location Fields
| Field | Type | Description |
|---|---|---|
id | UUID | Location ID |
name | string | Display name |
street | string? | Street and number |
zip | string? | Postal code |
city | string? | City |
country | string? | Country |
latitude | float? | GPS latitude |
longitude | float? | GPS longitude |
notes | string? | Free-text notes |
is_active | bool | Inactive locations hidden in selects |
Contact Fields
| Field | Type | Description |
|---|---|---|
id | UUID | Contact ID |
location_id | UUID | Parent location |
name | string | Contact name |
phone | string? | Phone number |
email | string? | Email address |
role | string? | Role label (e.g. "Disposant") |
REST Endpoints
| Method | Path | Permission | Description |
|---|---|---|---|
| GET | /v1/locations | locations:read | List locations (paginated) |
| GET | /v1/locations/{id} | locations:read | Get location with contacts |
| POST | /v1/locations | locations:write | Create location (with contacts) |
| PATCH | /v1/locations/{id} | locations:write | Update location |
| DELETE | /v1/locations/{id} | locations:delete | Delete location (cascades contacts) |
| POST | /v1/locations/{id}/contacts | locations:write | Add contact |
| PATCH | /v1/locations/{id}/contacts/{cid} | locations:write | Update contact |
| DELETE | /v1/locations/{id}/contacts/{cid} | locations:write | Remove contact |
GraphQL
locations(page, limit)→PaginatedLocationslocation(id)→GqlLocation(includescontacts: [GqlLocationContact])createLocation(input)→GqlLocationupdateLocation(id, input)→GqlLocationdeleteLocation(id)→BooleanaddLocationContact(locationId, input)→GqlLocationContactupdateLocationContact(id, input)→GqlLocationContactdeleteLocationContact(id)→Boolean
Create Location with Contacts
POST /v1/locations
{
"name": "Kieswerk Mannheim",
"street": "Hafenstraße 12",
"zip": "68159",
"city": "Mannheim",
"country": "DE",
"latitude": 49.4875,
"longitude": 8.4660,
"contacts": [
{
"name": "Hans Müller",
"phone": "+49 621 123456",
"email": "h.mueller@kieswerk.de",
"role": "Disposant"
}
]
}
Geocoding
On the location create/edit UI, clicking "Locate" triggers address-to-coordinate resolution via the Photon API:
GET https://photon.komoot.io/api/?q={street} {zip} {city} {country}&limit=5&lang=de
Multiple results are shown as suggestions. Reverse geocoding (drag marker → fill address) uses:
GET https://photon.komoot.io/reverse?lat={lat}&lon={lng}&lang=de
MapLocationPicker Component
The MapLocationPicker component from @elcto/ui combines a MapLibre GL JS map with a draggable marker and an optional geocoding search bar:
import { MapLocationPicker } from '@elcto/ui';
<MapLocationPicker
latitude={lat}
longitude={lng}
onPositionChange={({ lat, lng }) => setCoords({ lat, lng })}
tiles="carto"
geocodingEnabled={true}
/>
Dragging the marker calls onPositionChange and updates the latitude/longitude form fields in real time. Manually editing the fields also moves the marker.
Deletion Behaviour
Deleting a location cascades to all its contacts (ON DELETE CASCADE). Trips referencing the deleted location have their origin_id or destination_id set to NULL.
RBAC Permissions
| Permission | Description |
|---|---|
locations:read | View locations |
locations:write | Create and update locations and contacts |
locations:delete | Delete locations |
Country Selection
The country field uses a CountrySelect component from @elcto/ui that stores ISO alpha-2 country codes (e.g. "DE", "US"). Features:
- Searchable dropdown with flag emojis
- Country names translated based on locale (en/de)
- Value stored as ISO code in database
Map Location Picker
The create and edit pages include an interactive map for setting coordinates:
- FloatingInput with search icon for geocoding (auto-search after 600ms, or Enter for immediate)
- Draggable marker for fine-tuning position
- Coordinates sync bidirectionally between inputs and map
- Uses Photon (Komoot) API for geocoding