Merge branch 'main' into weblate-main

# Conflicts:
#	src/main/resources/messages_fr.properties
This commit is contained in:
Daniel Graf
2025-12-29 16:25:25 +01:00
8 changed files with 284 additions and 171 deletions

View File

@@ -13,6 +13,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -67,21 +68,36 @@ public class ExportDataController {
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate,
@RequestParam(required = false, defaultValue = "UTC") ZoneId timezone,
@RequestParam(required = false, defaultValue = "0") int page,
@RequestParam(required = false, defaultValue = "100") int size,
Model model) {
LocalDate start = startDate != null ? LocalDate.parse(startDate) : LocalDate.now();
LocalDate end = endDate != null ? LocalDate.parse(endDate) : LocalDate.now();
LocalDate start = StringUtils.hasText(startDate) ? LocalDate.parse(startDate) : LocalDate.now();
LocalDate end = StringUtils.hasText(endDate) ? LocalDate.parse(endDate) : LocalDate.now();
ZonedDateTime startDateTime = start.atStartOfDay(timezone);
ZonedDateTime endDateTime = end.plusDays(1).atStartOfDay(timezone);
model.addAttribute("startDate", start);
model.addAttribute("endDate", end);
// Get raw location points for the date range
List<RawLocationPoint> rawLocationPoints = rawLocationPointJdbcService.findByUserAndTimestampBetweenOrderByTimestampAsc(
user, startDateTime.toInstant(), endDateTime.toInstant(), false, true);
model.addAttribute("rawLocationPoints", rawLocationPoints.stream()
.map(p -> new DataLine(TimeUtil.adjustInstant(p.getTimestamp(), timezone), p.getLatitude(), p.getLongitude(), p.getAccuracyMeters(), p.isProcessed()))
.toList());
List<RawLocationPoint> allPoints = rawLocationPointJdbcService.findByUserAndTimestampBetweenOrderByTimestampAsc(
user, startDateTime.toInstant(), endDateTime.toInstant(), false, true, page, size);
long totalElements = rawLocationPointJdbcService.countByUserAndTimestampBetweenOrderByTimestampAsc(user, startDateTime.toInstant(), endDateTime.toInstant(), false, true);
int totalPages = (int) Math.ceil((double) totalElements / size);
List<DataLine> paginatedData = allPoints.stream()
.map(p -> new DataLine(TimeUtil.adjustInstant(p.getTimestamp(), timezone),
p.getLatitude(), p.getLongitude(), p.getAccuracyMeters(), p.isProcessed()))
.toList();
model.addAttribute("rawLocationPoints", paginatedData);
model.addAttribute("currentPage", page);
model.addAttribute("pageSize", size);
model.addAttribute("totalElements", totalElements);
model.addAttribute("totalPages", totalPages);
model.addAttribute("hasNext", page < totalPages - 1);
model.addAttribute("hasPrevious", page > 0);
return "settings/export-data :: data-content";
}

View File

@@ -80,6 +80,42 @@ public class RawLocationPointJdbcService {
user.getId(), Timestamp.from(startTime), Timestamp.from(endTime));
}
public List<RawLocationPoint> findByUserAndTimestampBetweenOrderByTimestampAsc(
User user, Instant startTime, Instant endTime, boolean includeSynthetic, boolean includeIgnored, int page, int pageSize) {
StringBuilder sql = new StringBuilder()
.append("SELECT rlp.id, rlp.accuracy_meters, rlp.elevation_meters, rlp.timestamp, rlp.user_id, ST_AsText(rlp.geom) as geom, rlp.processed, rlp.synthetic, rlp.ignored, rlp.version ")
.append("FROM raw_location_points rlp ")
.append("WHERE rlp.user_id = ? ");
if (!includeSynthetic) {
sql.append("AND rlp.synthetic = false ");
}
if (!includeIgnored) {
sql.append("AND rlp.ignored = false ");
}
sql.append("AND rlp.timestamp BETWEEN ? AND ? ").append("ORDER BY rlp.timestamp")
.append(" OFFSET ").append(page * pageSize).append(" LIMIT ").append(pageSize);
return jdbcTemplate.query(sql.toString(), rawLocationPointRowMapper,
user.getId(), Timestamp.from(startTime), Timestamp.from(endTime));
}
@SuppressWarnings("DataFlowIssue")
public long countByUserAndTimestampBetweenOrderByTimestampAsc(
User user, Instant startTime, Instant endTime, boolean includeSynthetic, boolean includeIgnored) {
StringBuilder sql = new StringBuilder()
.append("SELECT COUNT(*)")
.append("FROM raw_location_points rlp ")
.append("WHERE rlp.user_id = ? ");
if (!includeSynthetic) {
sql.append("AND rlp.synthetic = false ");
}
if (!includeIgnored) {
sql.append("AND rlp.ignored = false ");
}
sql.append("AND rlp.timestamp BETWEEN ? AND ? ");
return jdbcTemplate.queryForObject(sql.toString(), Long.class,
user.getId(), Timestamp.from(startTime), Timestamp.from(endTime));
}
public List<RawLocationPoint> findByUserAndProcessedIsFalseOrderByTimestampWithLimit(User user, int limit, int offset) {
String sql = "SELECT rlp.id, rlp.accuracy_meters, rlp.elevation_meters, rlp.timestamp, rlp.user_id, ST_AsText(rlp.geom) as geom, rlp.processed, rlp.synthetic, rlp.ignored, rlp.version " +
"FROM raw_location_points rlp " +

View File

@@ -961,10 +961,15 @@ export.raw.data.table.accuracy=Accuracy (m)
export.raw.data.table.processed=Processed
export.raw.data.no.data=No location data found for the selected date range
export.raw.data.loading=Loading location data...
export.raw.data.found.data=Found {0} location points for the selected date range
export.raw.data.showing=Showing {0} - {1} of {2}
export.raw.data.show=Show:
export.raw.data.previous=Previous
export.raw.data.next=Next
export.raw.data.page.info=Page {0} of {1}
export.gpx.success=GPX file exported successfully
export.gpx.error=Error exporting GPX file: {0}
# Generic Labels
label.warning=Warning:
label.info=Info:

View File

@@ -447,6 +447,11 @@ export.raw.data.table.longitude=L\u00E4ngengrad
export.raw.data.table.accuracy=Genauigkeit (m)
export.raw.data.table.processed=Verarbeitet
export.raw.data.no.data=Keine Standortdaten f\u00FCr den ausgew\u00E4hlten Datumsbereich gefunden
export.raw.data.showing=Zeige {0} - {1} von {2}
export.raw.data.show=Anzeigen:
export.raw.data.previous=Zur\u00FCck
export.raw.data.next=Weiter
export.raw.data.page.info=Seite {0} von {1}
export.gpx.success=GPX-Datei erfolgreich exportiert
export.gpx.error=Fehler beim Exportieren der GPX-Datei: {0}
@@ -460,7 +465,7 @@ error.action.back=Zur\u00FCck
error.action.retry=Erneut versuchen
places.geocoding.response.button=Geokodierung-Antworten anzeigen
places.geocoding.response.title=Geokodierungsantwort f\u00FCr {0}
places.geocoding.response.no.data=Keine Geokodierungsantwort für diesen Ort verfügbar
places.geocoding.response.no.data=Keine Geokodierungsantwort f\u00FCr diesen Ort verf\u00FCgbar
places.geocoding.response.back=Zur\u00FCck zu Orten
places.geocoding.response.provider=Anbieter
places.geocoding.response.status=Status
@@ -1259,25 +1264,25 @@ timeline.state.hide.title=Zeitleiste verstecken
timeline.state.show.title=Zeitleiste anzeigen
datepicker.state.hide.title=Datumsauswahl verstecken
datepicker.state.show.title=Zeitleiste anzeigen
datepicker.today.title=Heute auswählen
datepicker.today.title=Heute ausw\u00E4hlen
settings.logging=Protokollierung
users.color.theme.label=Farbschema
users.color.theme.description=Wählen Sie Ihre bevorzugte Akzentfarbe für die Karte aus.
users.color.theme.description=W\u00E4hlen Sie Ihre bevorzugte Akzentfarbe f\u00FCr die Karte aus.
users.color.theme.reset=Standard wiederherstellen
users.color.theme.custom=Benutzerdefinierte Farbe
users.color.theme.custom.input=Benutzerdefinierte Farbe:
users.avatar.error.to-large=Bild zu groß. Die maximale Größe beträgt 2MB.
users.avatar.error.invalid-file-type=Ungültiger Dateityp. Nur JPEG-, PNG-, GIF- und WebP-Bilder sind erlaubt.
users.avatar.error.to-large=Bild zu gro\u00DF. Die maximale Gr\u00F6\u00DFe betr\u00E4gt 2MB.
users.avatar.error.invalid-file-type=Ung\u00FCltiger Dateityp. Nur JPEG-, PNG-, GIF- und WebP-Bilder sind erlaubt.
users.avatar.error.generic=Fehler beim Verarbeiten der Datei {0}
users.custom.css.remove.confirm=Sind Sie sicher, dass Sie die benutzerdefinierte CSS-Datei entfernen möchten?
users.custom.css.error.to-large=CSS-Datei zu groß. Die maximale Größe beträgt 1MB.
users.custom.css.error.invalid-file-type=Ungültiger Dateityp. Nur CSS-Dateien sind erlaubt.
users.custom.css.remove.confirm=Sind Sie sicher, dass Sie die benutzerdefinierte CSS-Datei entfernen m\u00F6chten?
users.custom.css.error.to-large=CSS-Datei zu gro\u00DF. Die maximale Gr\u00F6\u00DFe betr\u00E4gt 1MB.
users.custom.css.error.invalid-file-type=Ung\u00FCltiger Dateityp. Nur CSS-Dateien sind erlaubt.
users.custom.css.error.generic=Fehler beim Verarbeiten der CSS Datei: {0}
places.search.placeholder=Ort suchen
places.search.placeholder=Ort suchen\u2026
place.unknown.label=Unbekannter Ort
integrations.reitti.update=Konfiguration aktualisieren
integrations.reitti.test.missing.fields=Bitte füllen Sie URL und API Token aus
integrations.reitti.test.loading=Verbindung testen
integrations.reitti.test.missing.fields=Bitte f\u00FCllen Sie URL und API Token aus
integrations.reitti.test.loading=Verbindung testen\u2026
integrations.reitti.test.failed=Verbindungstest fehlgeschlagen
integrations.reitti.info.server.title=Server-Informationen
integrations.reitti.info.server.name=Name:
@@ -1289,12 +1294,12 @@ integrations.reitti.info.user.displayname=Anzeigename:
integrations.reitti.info.user.id=Benutzer-ID:
integrations.reitti.info.user.version=Version:
integrations.reitti.info.connection.success=Verbindung erfolgreich!
integrations.reitti.info.connection.access=Sie können auf die Standortdaten dieses Nutzers zugreifen.
integrations.no.token.title=⚠️ Keine API Token verfügbar
integrations.reitti.info.connection.access=Sie k\u00F6nnen auf die Standortdaten dieses Nutzers zugreifen.
integrations.no.token.title=\u26A0\uFE0F Keine API Token verf\u00FCgbar
integrations.download=Download:
integrations.homepage=Homepage:
integrations.gpslogger.step1=GPSLogger im Google Play Store herunterladen
integrations.gpslogger.step2=Öffnen Sie GPSLogger und gehen Sie zu <strong>Logging details Log to custom URL</strong>
integrations.gpslogger.step2=\u00D6ffnen Sie GPSLogger und gehen Sie zu <strong>Logging details \u2192 Log to custom URL</strong>
integrations.gpslogger.step3="Log to custom URL" aktivieren
integrations.gpslogger.step4.with.token=Setzen Sie die URL auf: <code>{0}</code>
integrations.gpslogger.step4.without.token=Setzen Sie die URL auf: <code>{0}</code>
@@ -1303,42 +1308,41 @@ integrations.gpslogger.step6=Setzen Sie HTTP Body auf:
integrations.gpslogger.step7=HTTP-Header auf: <code>Content-Typ: Anwendung/json</code>
integrations.gpslogger.step8=Starten Sie die Protokollierung!
integrations.owntracks.step1=Laden Sie OwnTracks aus dem App Store oder Google Play Store
integrations.owntracks.step2=Öffnen Sie OwnTracks und gehen Sie zu <strong>Settings Connection</strong>
integrations.owntracks.step2=\u00D6ffnen Sie OwnTracks und gehen Sie zu <strong>Settings \u2192 Connection</strong>
integrations.owntracks.step3=Modus auf <strong>HTTP</strong> einstellen
integrations.owntracks.step4.with.token=Endpoint auf: <code>{0}</code>
integrations.owntracks.step4.without.token=Endpoint auf: <code>{0}</code>
integrations.owntracks.step5=Deaktivieren <strong>Authentication</strong> (wir verwenden stattdessen das Token in der URL)
integrations.owntracks.step6=Konfigurieren Sie die Tracking-Einstellungen wie gewünscht. Achten Sie darauf, dass Owntracks mindestens alle 30 Sekunden einen Punkt aufzeichnet.
integrations.owntracks.step6=Konfigurieren Sie die Tracking-Einstellungen wie gew\u00FCnscht. Achten Sie darauf, dass Owntracks mindestens alle 30 Sekunden einen Punkt aufzeichnet.
integrations.owntracks.step7=Auf der Kartenansicht den Tracking-Modus auf "Movement" einstellen
integrations.owntracks.step8=Die App startet automatisch den Versand von Standort-Updates
integrations.overland.step1=Overland installieren
integrations.overland.step2=Öffnen Sie Overland und gehen Sie auf die Registerkarte <strong>Settings</strong>
integrations.overland.step3=<strong>Important:</strong> Tippen Sie auf die <strong>Request Permission</strong> Schaltfläche, um den Standortzugriff zu gewähren - Overland wird ohne diese Berechtigung nichts tracken
integrations.overland.step4=Tippen Sie auf <strong>Empfänger Endpoint</strong>
integrations.overland.step2=\u00D6ffnen Sie Overland und gehen Sie auf die Registerkarte <strong>Settings</strong>
integrations.overland.step3=<strong>Important:</strong> Tippen Sie auf die <strong>Request Permission</strong> Schaltfl\u00E4che, um den Standortzugriff zu gew\u00E4hren - Overland wird ohne diese Berechtigung nichts tracken
integrations.overland.step4=Tippen Sie auf <strong>Empf\u00E4nger Endpoint</strong>
integrations.overland.step5.with.token=Endpoint URL auf: <code>{0}</code>
integrations.overland.step5.without.token=Setz den Endpunkt URL zu: <code>{0}</code>
integrations.overland.step6=Lassen Sie das Feld <strong>Device ID</strong> leer oder setzen Sie eine benutzerdefinierte Kennung
integrations.overland.step7=Lassen Sie das Feld <strong>Access Token</strong> leer (wir verwenden das Token in der URL)
integrations.overland.step8=Konfigurieren Sie Tracking-Einstellungen:<ul><li><strong>Desired Accuracy:</strong> Best (für hohe Genauigkeit) oder 100m (für Batteriesparen)</li><li><strong>Points per Batch:</strong> 50-200 (niedriger für unzuverlässige Verbindungen)</li><li><strong>Significant Location:</strong> Deaktiviert</li></ul>
integrations.overland.step8=Konfigurieren Sie Tracking-Einstellungen:<ul><li><strong>Desired Accuracy:</strong> Best (f\u00FCr hohe Genauigkeit) oder 100m (f\u00FCr Batteriesparen)</li><li><strong>Points per Batch:</strong> 50-200 (niedriger f\u00FCr unzuverl\u00E4ssige Verbindungen)</li><li><strong>Significant Location:</strong> Deaktiviert</li></ul>
integrations.overland.step9=Gehen Sie zum <strong>Tracker</strong> Tab und schalten Sie Tracking auf <strong>On</strong>
integrations.overland.step10=Einstellen des Sendeintervallschiebers (1 Sekunde bis 30 Minuten)
integrations.overland.step11=Die App startet das Senden von Standortdaten automatisch
integrations.owntracks.recorder.test.missing.fields=Bitte füllen Sie Basis-URL, Benutzername und Geräte-ID aus
integrations.owntracks.recorder.test.loading=Verbindung testen
integrations.owntracks.recorder.test.missing.fields=Bitte f\u00FCllen Sie Basis-URL, Benutzername und Ger\u00E4te-ID aus
integrations.owntracks.recorder.test.loading=Verbindung testen\u2026
integrations.owntracks.recorder.test.failed=Verbindungstest fehlgeschlagen
integrations.owntracks.recorder.loading.historical=Lade historische Daten
geocoding.service.name.placeholder=Geben Sie einen Namen für den Dienst ein
integrations.owntracks.recorder.loading.historical=Lade historische Daten\u2026
geocoding.service.name.placeholder=Geben Sie einen Namen f\u00FCr den Dienst ein
language.polish=Polnisch
language.chinese=Chinesisch
statistics.title.overall=Gesamtstatistik
statistics.title.year=Statistiken für {0}
statistics.title.month-year=Statistiken für {0} {1}
statistics.title.year=Statistiken f\u00FCr {0}
statistics.title.month-year=Statistiken f\u00FCr {0} {1}
map.auto-update.enable.title=Auto-Update-Modus aktivieren
map.auto-update.disable.title=Auto-Update-Modus deaktivieren
map.fullscreen.toggle.title=Vollbild aktivieren
export.gpx.relevant=Nur relevante Daten exportieren?
export.raw.data.loading=Standortdaten laden...
export.raw.data.found.data={0} Standortpunkte für den ausgewählten Datumsbereich gefunden
label.warning=Warnung:
label.info=Info:
visit.sensitivity.level.very-low=Sehr niedrig
@@ -1350,10 +1354,10 @@ visit.sensitivity.preview.date=Datum der Vorschau:
visit.sensitivity.preview.ready=Bereit
visit.sensitivity.preview.error=Bereit
settings.logging.description=Konfigurieren der Protokollierung und Anzeigen von Protokollen
memory.form.update=Änderungen speichern
memory.form.update=\u00C4nderungen speichern
memory.view.button=Erinnerung ansehen
memory.view.share=Teilen
memory.view.delete=Löschen
memory.view.delete=L\u00F6schen
logging.title=Protokoll
logging.logger.class=Loggerklasse
logging.logger.placeholder=Geben Sie den Logger-Klassennamen ein oder lassen Sie ihn leer, um den Rootlogger zu konfigurieren
@@ -1366,14 +1370,14 @@ logging.level.trace=TRACE
logging.level.info=INFO
logging.level.warn=WAREN
logging.level.error=ERROR
logging.buffer.size=Größe des Puffers
logging.buffer.max.size=Maximale Puffergröße: {0}
logging.buffer.size=Gr\u00F6\u00DFe des Puffers
logging.buffer.max.size=Maximale Puffergr\u00F6\u00DFe: {0}
logging.update=Aktualisieren
logging.configured.loggers=Konfigurierte Logger
logging.remove=Entfernen
logging.confirm.remove=Sind Sie sicher, dass Sie diese Loggerkonfiguration entfernen möchten?
logging.confirm.remove=Sind Sie sicher, dass Sie diese Loggerkonfiguration entfernen m\u00F6chten?
logging.autoscroll=Zu neuen Nachrichten springen
logging.connecting=Zum Log-Stream verbinden
logging.connecting=Zum Log-Stream verbinden\u2026
logging.settings.updated=Einstellungen erfolgreich aktualisiert
logging.error=Fehler
logging.connected=Zum Log-Stream verbunden
@@ -1387,10 +1391,10 @@ places.polygon.remove=Polygon entfernen
places.polygon.editor.subtitle=Ortsgrenzen bearbeiten
places.polygon.editor.instructions=Klicken Sie und ziehen Sie ein Polygon um den Ort. Klicken Sie auf einen vorhandenen Punkt, um ihn zu entfernen.
places.warning.polygon.removal=Die Ortsbegrenzung wird von diesem Ort entfernt, was die Besuchserkennung beeinflussen kann.
places.warning.polygon.addition=Die Ortsbegrenzung wird zu diesem Ort hinzugefügt, dies kann die Besuchserkennung beeinflussen.
places.warning.polygon.significant_change=Die Ortsbegrenzung wird deutlich geändert, was die Besuchserkennung beeinflussen kann.
places.warning.overlapping.visits=Die neue Grenze wird sich mit {0,choice,1#1 vorhandenen Ort<1<{0,number,integer} vorhandenen Orten} überschneiden. Dafür werden diese Orte entfernt. Dies kann die Besuchserkennung beeinflussen
places.warning.overlapping.recalculation_hint=Die neue Grenze wird zur Neuberechung von {0,choice,1#einem Tag<1<{0,number,integer} Tagen} führen. Dies kann einige Zeit dauern.
places.warning.general_error=Ein Fehler beim Überprüfen des Updates: {0}
places.update.confirmation.message=Folgende Änderungen werden vorgenommen:\n\n{0}\n\nWillst du weitermachen?
form.select.placeholder=Auswählen…
places.warning.polygon.addition=Die Ortsbegrenzung wird zu diesem Ort hinzugef\u00FCgt, dies kann die Besuchserkennung beeinflussen.
places.warning.polygon.significant_change=Die Ortsbegrenzung wird deutlich ge\u00E4ndert, was die Besuchserkennung beeinflussen kann.
places.warning.overlapping.visits=Die neue Grenze wird sich mit {0,choice,1#1 vorhandenen Ort<1<{0,number,integer} vorhandenen Orten} \u00FCberschneiden. Daf\u00FCr werden diese Orte entfernt. Dies kann die Besuchserkennung beeinflussen
places.warning.overlapping.recalculation_hint=Die neue Grenze wird zur Neuberechung von {0,choice,1#einem Tag<1<{0,number,integer} Tagen} f\u00FChren. Dies kann einige Zeit dauern.
places.warning.general_error=Ein Fehler beim \u00DCberpr\u00FCfen des Updates: {0}
places.update.confirmation.message=Folgende \u00C4nderungen werden vorgenommen:\n\n{0}\n\nWillst du weitermachen?
form.select.placeholder=Ausw\u00E4hlen\u2026

View File

@@ -156,7 +156,7 @@ users.home.location.clear=Effacer
# Places
places.title=Lieux significatifs
places.no.places=Aucun lieu significatif na été trouvé.
places.no.places=Aucun lieu significatif n\u2019a \u00E9t\u00E9 trouv\u00E9.
places.page.info=Page {0} sur {1}
places.name.label=Nom
places.address.label=Adresse
@@ -898,9 +898,9 @@ country.zw.label=Zimbabwe
format.hours_minutes={0,choice,0#|1#{0} heure|1<{0} heures} {1,choice,0#|1#et {1} minute|1<et {1} minutes}
format.minutes_only={0,choice,1#{0} minute|1<{0} minutes}
places.address.placeholder=Entrer l\u2019adresse
places.geocoding.response.button=Voir la réponse
places.geocoding.response.button=Voir la r\u00E9ponse
places.geocoding.response.title=R\u00E9ponse du G\u00E9ocodage pour {0}
places.geocoding.response.no.data=Aucune réponse de géocodage nest disponible pour ce lieu
places.geocoding.response.no.data=Aucune r\u00E9ponse de g\u00E9ocodage n\u2019est disponible pour ce lieu
places.geocoding.response.back=Retour aux Lieux
places.geocoding.response.provider=Fournisseur
places.geocoding.response.status=\u00C9tat
@@ -1266,119 +1266,118 @@ timeline.state.hide.title=Cacher la chronologie
timeline.state.show.title=Afficher la chronologie
datepicker.state.hide.title=Cacher le choix de date
datepicker.state.show.title=Afficher le choix de date
datepicker.today.title=Aller à aujourdhui
users.color.theme.label=Thème de couleur
users.color.theme.description=Choisissez votre couleur daccentuation préférée sur la carte.
users.color.theme.reset=Réinitialiser par défaut
users.color.theme.custom=Couleur personnalisée
users.color.theme.custom.input=Couleur personnalisée :
users.avatar.error.to-large=Limage est trop grande. La taille maximale est de 2Mo.
users.avatar.error.invalid-file-type=Type de fichier invalide. Seules les images au format JPEG, PNG, GIF, et WebP sont autorisées.
users.avatar.error.generic=Erreur lors du traitement du fichier davatar : {0}
users.custom.css.remove.confirm=Voulez-vous supprimer le fichier CSS personnalisé ?
users.custom.css.error.to-large=Taille du fichier CSS trop importante. La taille maximale est de 1Mo.
users.custom.css.error.invalid-file-type=Type de fichier invalide. Seuls les fichiers CSS sont autorisés.
users.custom.css.error.generic=Erreur lors du traitement du fichier CSS : {0}
places.search.placeholder=Rechercher parmi les lieux
datepicker.today.title=Aller \u00E0 aujourd\u2019hui
users.color.theme.label=Th\u00E8me de couleur
users.color.theme.description=Choisissez votre couleur d\u2019accentuation pr\u00E9f\u00E9r\u00E9e sur la carte.
users.color.theme.reset=R\u00E9initialiser par d\u00E9faut
users.color.theme.custom=Couleur personnalis\u00E9e
users.color.theme.custom.input=Couleur personnalis\u00E9e\u00A0:
users.avatar.error.to-large=L\u2019image est trop grande. La taille maximale est de 2\u202FMo.
users.avatar.error.invalid-file-type=Type de fichier invalide. Seules les images au format JPEG, PNG, GIF, et WebP sont autoris\u00E9es.
users.avatar.error.generic=Erreur lors du traitement du fichier d\u2019avatar\u00A0: {0}
users.custom.css.remove.confirm=Voulez-vous supprimer le fichier CSS personnalis\u00E9\u202F?
users.custom.css.error.to-large=Taille du fichier CSS trop importante. La taille maximale est de 1\u202FMo.
users.custom.css.error.invalid-file-type=Type de fichier invalide. Seuls les fichiers CSS sont autoris\u00E9s.
users.custom.css.error.generic=Erreur lors du traitement du fichier CSS\u00A0: {0}
places.search.placeholder=Rechercher parmi les lieux\u2026
place.unknown.label=Lieu inconnu
integrations.reitti.update=Mettre à jour
integrations.reitti.test.missing.fields=Veuillez remplir lURL de linstance et le jeton dAPI
integrations.reitti.test.loading=Test de la connexion
integrations.reitti.test.failed=Test de la connexion échoué
integrations.reitti.update=Mettre \u00E0 jour
integrations.reitti.test.missing.fields=Veuillez remplir l\u2019URL de l\u2019instance et le jeton d\u2019API
integrations.reitti.test.loading=Test de la connexion\u2026
integrations.reitti.test.failed=Test de la connexion \u00E9chou\u00E9
integrations.reitti.info.server.title=Informations du serveur
integrations.reitti.info.server.name=Nom :
integrations.reitti.info.server.version=Version :
integrations.reitti.info.server.time=Heure système :
integrations.reitti.info.user.title=Information dutilisateur
integrations.reitti.info.user.username=Nom dutilisateur :
integrations.reitti.info.user.displayname=Nom daffichage :
integrations.reitti.info.user.id=Identifiant utilisateur :
integrations.reitti.info.user.version=Version :
integrations.reitti.info.connection.success=Connexion réussie!
integrations.reitti.info.connection.access=Vous pouvez accéder aux données de localisation de cet utilisateur.
integrations.no.token.title=⚠️ Aucun jeton dAPI disponible
integrations.download=Téléchargement :
integrations.homepage=Page daccueil :
integrations.gpslogger.step1=Télécharger GPSLogger depuis le Google Play Store
integrations.gpslogger.step2=Ouvrir GPSLogger et aller dans <strong>Logging details Log to custom URL</strong>
integrations.gpslogger.step3=Activer « Log to custom URL »
integrations.gpslogger.step4.with.token=Remplir avec lURL suivante : <code>{0}</code>
integrations.gpslogger.step4.without.token=Remplir avec lURL suivante : <code>{0}</code>
integrations.gpslogger.step5=Mettre la méthode HTTP à <strong>POST</strong>
integrations.gpslogger.step6=Remplir «HTTP Body» avec :
integrations.gpslogger.step7=Remplir «HTTP header» avec : <code>Content-Type: application/json</code>
integrations.gpslogger.step8=Commencez lenregistrement!
integrations.owntracks.step1=Téléchargez OwnTracks depuis lApp Store ou le Google Play Store
integrations.owntracks.step2=Ouvrez OwnTracks et rendez-vous sur <strong>Settings Connection</strong>
integrations.owntracks.step3=Mettre le «Mode» à <strong>HTTP</strong>
integrations.owntracks.step4.with.token=Remplir «Endpoint» avec : <code>{0}</code>
integrations.owntracks.step4.without.token=Remplir «Endpoint» avec : <code>{0}</code>
integrations.owntracks.step5=Désactiver <strong>Authentication</strong> (on utilise le token dans lURL à la place)
integrations.owntracks.step6=Configurez les paramètres de suivi comme désiré. Assurez-vous que OwnTracks enregistre un point au moins toutes les 30 secondes.
integrations.owntracks.step7=Sur la carte, mettre le mode de suivi à «Movement»
integrations.owntracks.step8=Lapplication commencera à envoyer automatiquement votre position
integrations.reitti.info.server.name=Nom\u00A0:
integrations.reitti.info.server.version=Version\u00A0:
integrations.reitti.info.server.time=Heure syst\u00E8me\u00A0:
integrations.reitti.info.user.title=Information d\u2019utilisateur
integrations.reitti.info.user.username=Nom d\u2019utilisateur\u00A0:
integrations.reitti.info.user.displayname=Nom d\u2019affichage\u00A0:
integrations.reitti.info.user.id=Identifiant utilisateur\u00A0:
integrations.reitti.info.user.version=Version\u00A0:
integrations.reitti.info.connection.success=Connexion r\u00E9ussie\u202F!
integrations.reitti.info.connection.access=Vous pouvez acc\u00E9der aux donn\u00E9es de localisation de cet utilisateur.
integrations.no.token.title=\u26A0\uFE0F\u202FAucun jeton d\u2019API disponible
integrations.download=T\u00E9l\u00E9chargement\u00A0:
integrations.homepage=Page d\u2019accueil\u00A0:
integrations.gpslogger.step1=T\u00E9l\u00E9charger GPSLogger depuis le Google Play Store
integrations.gpslogger.step2=Ouvrir GPSLogger et aller dans <strong>Logging details \u2192 Log to custom URL</strong>
integrations.gpslogger.step3=Activer \u00AB\u202FLog to custom URL\u202F\u00BB
integrations.gpslogger.step4.with.token=Remplir avec l\u2019URL suivante\u00A0: <code>{0}</code>
integrations.gpslogger.step4.without.token=Remplir avec l\u2019URL suivante\u00A0: <code>{0}</code>
integrations.gpslogger.step5=Mettre la m\u00E9thode HTTP \u00E0 <strong>POST</strong>
integrations.gpslogger.step6=Remplir \u00AB\u202FHTTP Body\u202F\u00BB avec\u00A0:
integrations.gpslogger.step7=Remplir \u00AB\u202FHTTP header\u202F\u00BB avec\u00A0: <code>Content-Type: application/json</code>
integrations.gpslogger.step8=Commencez l\u2019enregistrement\u202F!
integrations.owntracks.step1=T\u00E9l\u00E9chargez OwnTracks depuis l\u2019App Store ou le Google Play Store
integrations.owntracks.step2=Ouvrez OwnTracks et rendez-vous sur <strong>Settings \u2192 Connection</strong>
integrations.owntracks.step3=Mettre le \u00AB\u202FMode\u202F\u00BB \u00E0 <strong>HTTP</strong>
integrations.owntracks.step4.with.token=Remplir \u00AB\u202FEndpoint\u202F\u00BB avec\u00A0: <code>{0}</code>
integrations.owntracks.step4.without.token=Remplir \u00AB\u202FEndpoint\u202F\u00BB avec\u00A0: <code>{0}</code>
integrations.owntracks.step5=D\u00E9sactiver <strong>Authentication</strong> (on utilise le token dans l\u2019URL \u00E0 la place)
integrations.owntracks.step6=Configurez les param\u00E8tres de suivi comme d\u00E9sir\u00E9. Assurez-vous que OwnTracks enregistre un point au moins toutes les 30 secondes.
integrations.owntracks.step7=Sur la carte, mettre le mode de suivi \u00E0 \u00AB\u202FMovement\u202F\u00BB
integrations.owntracks.step8=L\u2019application commencera \u00E0 envoyer automatiquement votre position
integrations.overland.step1=Installez Overland
integrations.overland.step2=Ouvrez Overland et allez dans la section <strong>Settings</strong>
integrations.overland.step3=<strong>Important :</strong> Touchez le bouton <strong>Request Permission</strong> pour permettre laccès aux données GPS. Overland ne fonctionnera pas sans cette permission
integrations.overland.step3=<strong>Important\u00A0:</strong> Touchez le bouton <strong>Request Permission</strong> pour permettre l\u2019acc\u00E8s aux donn\u00E9es GPS. Overland ne fonctionnera pas sans cette permission
integrations.overland.step4=Touchez <strong>Receiver Endpoint</strong>
integrations.overland.step5.with.token=Remplir «Enpoint URL» avec : <code>{0}</code>
integrations.overland.step5.without.token=Remplir «Enpoint URL» avec : <code>{0}</code>
integrations.overland.step6=Laissez le champ <strong>Device ID</strong> vide, ou mettez-y un identifiant personnalisé
integrations.overland.step7=Laissez le champ <strong>Access Token</strong> vide (cest le token dans lURL qui est utilisé)
integrations.overland.step8=Configurez les paramètres de suivi :<ul><li><strong>Desired Accuracy:</strong> Best (pour une précision élevée) ou 100m (pour économiser la batterie)</li><li><strong>Points per Batch:</strong> 50 à 200 (plus bas pour les connexions peu fiables)</li><li><strong>Significant Location:</strong> Disabled pour un suivi continu</li></ul>
integrations.overland.step9=Allez sur longlet <strong>Tracker</strong> et <strong>activez</strong> le suivi
integrations.overland.step10=Ajustez lintervalle denvoi (1 seconde à 30 minutes)
integrations.overland.step11=Lapplication commencera à envoyer les données de localisation automatiquement
integrations.overland.step5.with.token=Remplir \u00AB\u202FEnpoint URL\u202F\u00BB avec\u00A0: <code>{0}</code>
integrations.overland.step5.without.token=Remplir \u00AB\u202FEnpoint URL\u202F\u00BB avec\u00A0: <code>{0}</code>
integrations.overland.step6=Laissez le champ <strong>Device ID</strong> vide, ou mettez-y un identifiant personnalis\u00E9
integrations.overland.step7=Laissez le champ <strong>Access Token</strong> vide (c\u2019est le token dans l\u2019URL qui est utilis\u00E9)
integrations.overland.step8=Configurez les param\u00E8tres de suivi\u00A0:<ul><li><strong>Desired Accuracy:</strong> Best (pour une pr\u00E9cision \u00E9lev\u00E9e) ou 100m (pour \u00E9conomiser la batterie)</li><li><strong>Points per Batch:</strong> 50 \u00E0 200 (plus bas pour les connexions peu fiables)</li><li><strong>Significant Location:</strong> Disabled pour un suivi continu</li></ul>
integrations.overland.step9=Allez sur l\u2019onglet <strong>Tracker</strong> et <strong>activez</strong> le suivi
integrations.overland.step10=Ajustez l\u2019intervalle d\u2019envoi (1 seconde \u00E0 30 minutes)
integrations.overland.step11=L\u2019application commencera \u00E0 envoyer les donn\u00E9es de localisation automatiquement
geocoding.service.name.placeholder=Entrez un nom pour le service
integrations.owntracks.recorder.test.missing.fields=Veuillez remplir lURL de base, le nom dutilisateur et lID de lappareil
integrations.owntracks.recorder.test.loading=Test de la connexion
integrations.owntracks.recorder.test.failed=Échec du test de la connexion
integrations.owntracks.recorder.loading.historical=Chargement des données dhistorique
integrations.owntracks.recorder.test.missing.fields=Veuillez remplir l\u2019URL de base, le nom d\u2019utilisateur et l\u2019ID de l\u2019appareil
integrations.owntracks.recorder.test.loading=Test de la connexion\u2026
integrations.owntracks.recorder.test.failed=\u00C9chec du test de la connexion
integrations.owntracks.recorder.loading.historical=Chargement des donn\u00E9es d\u2019historique\u2026
statistics.title.overall=Statistiques globales
statistics.title.year=Statistiques pour {0}
statistics.title.month-year=Statistiques pour {0} {1}
map.auto-update.enable.title=Activer le mode temps réel
map.auto-update.disable.title=Quitter le mode temps réel
map.fullscreen.toggle.title=Changer le plein-écran
export.gpx.relevant=Exporter seulement les données propres au traitement?
export.raw.data.loading=Chargement des données de localisation
export.raw.data.found.data=Trouvé {0,choice,0#aucun point|1#{0} point|1<{0} points} sur la période
label.warning=Avertissement :
label.info=Info :
visit.sensitivity.level.very-low=Très bas
map.auto-update.enable.title=Activer le mode temps r\u00E9el
map.auto-update.disable.title=Quitter le mode temps r\u00E9el
map.fullscreen.toggle.title=Changer le plein-\u00E9cran
export.gpx.relevant=Exporter seulement les donn\u00E9es propres au traitement\u202F?
export.raw.data.loading=Chargement des donn\u00E9es de localisation\u2026
label.warning=Avertissement\u00A0:
label.info=Info\u00A0:
visit.sensitivity.level.very-low=Tr\u00E8s bas
visit.sensitivity.level.low=Bas
visit.sensitivity.level.medium=Moyen
visit.sensitivity.level.high=Haut
visit.sensitivity.level.very-high=Très haut
visit.sensitivity.preview.date=Date daperçu :
visit.sensitivity.preview.ready=Prêt
visit.sensitivity.preview.error=Prêt
visit.sensitivity.level.very-high=Tr\u00E8s haut
visit.sensitivity.preview.date=Date d\u2019aper\u00E7u\u00A0:
visit.sensitivity.preview.ready=Pr\u00EAt
visit.sensitivity.preview.error=Pr\u00EAt
memory.form.update=Enregistrer
memory.view.button=Voir
memory.view.share=Partager
memory.view.delete=Supprimer
edit-place.page.title=Édition de lieu - Reitti
edit-place.page.title=\u00C9dition de lieu - Reitti
settings.logging=Historique
places.city.label=Ville
places.country.label=Pays
places.polygon.remove=Supprimer le polygone
places.polygon.editor.subtitle=Éditer les limites du lieu
places.polygon.editor.instructions=Cliquer et déplacer pour dessiner un polygone autour du lieu. Cliquer un point existant pour le supprimer.
places.warning.polygon.removal=Le polygone sera supprimé des limites du lieu, cela pourra affecter la détection des visites.
places.warning.polygon.addition=Le polygone sera ajouté aux limites du lieu, cela pourra affecter la détection des visites.
places.warning.polygon.significant_change=Le polygone de la limite sera changé de manière importante, cela pourrait affecter la détection des visites.
places.warning.overlapping.visits=Les nouvelles limites chevaucheront {0,choice,1#un autre lieu existant|1<{0,number,integer} autres lieux existants}, ce qui peut causer les visites à se retrouver réassignées entre les différents lieux, et affecter les calculs de voyages
places.warning.overlapping.recalculation_hint=Les nouvelles limites vont lancer le re-calcul de {0,choice,1#un jour|1<{0,number,integer} jours} de données, ce qui prendra plusieurs minutes à sexécuter.
places.warning.general_error=Une erreur sest produite pendant la vérification de la mise à jour : {0}
places.update.confirmation.message=Les changements suivants seront réalisés :\n\n{0}\n\nVoulez-vous continuer?
form.select.placeholder=Sélectionner
settings.logging.description=Configurer le niveau denregistrement et consulter lhistorique
logging.title=Journal dhistorique
logging.logger.class=Classe dhistorique
logging.logger.placeholder=Entrer le nom de la classe denregistrement, ou laisser vide pour configurer lenregistreur principal
logging.logger.help=Laisser vide pour configurer lenregistreur principal (global)
logging.log.name=Nom de lenregistreur
logging.log.level=Niveau denregistrement
places.polygon.editor.subtitle=\u00C9diter les limites du lieu
places.polygon.editor.instructions=Cliquer et d\u00E9placer pour dessiner un polygone autour du lieu. Cliquer un point existant pour le supprimer.
places.warning.polygon.removal=Le polygone sera supprim\u00E9 des limites du lieu, cela pourra affecter la d\u00E9tection des visites.
places.warning.polygon.addition=Le polygone sera ajout\u00E9 aux limites du lieu, cela pourra affecter la d\u00E9tection des visites.
places.warning.polygon.significant_change=Le polygone de la limite sera chang\u00E9 de mani\u00E8re importante, cela pourrait affecter la d\u00E9tection des visites.
places.warning.overlapping.visits=Les nouvelles limites chevaucheront {0,choice,1#un autre lieu existant|1<{0,number,integer} autres lieux existants}, ce qui peut causer les visites \u00E0 se retrouver r\u00E9assign\u00E9es entre les diff\u00E9rents lieux, et affecter les calculs de voyages
places.warning.overlapping.recalculation_hint=Les nouvelles limites vont lancer le re-calcul de {0,choice,1#un jour|1<{0,number,integer} jours} de donn\u00E9es, ce qui prendra plusieurs minutes \u00E0 s\u2019ex\u00E9cuter.
places.warning.general_error=Une erreur s\u2019est produite pendant la v\u00E9rification de la mise \u00E0 jour\u00A0: {0}
places.update.confirmation.message=Les changements suivants seront r\u00E9alis\u00E9s\u00A0:\n\n{0}\n\nVoulez-vous continuer\u202F?
form.select.placeholder=S\u00E9lectionner\u2026
settings.logging.description=Configurer le niveau d\u2019enregistrement et consulter l\u2019historique
logging.title=Journal d\u2019historique
logging.logger.class=Classe d\u2019historique
logging.logger.placeholder=Entrer le nom de la classe d\u2019enregistrement, ou laisser vide pour configurer l\u2019enregistreur principal
logging.logger.help=Laisser vide pour configurer l\u2019enregistreur principal (global)
logging.log.name=Nom de l\u2019enregistreur
logging.log.level=Niveau d\u2019enregistrement
logging.actions=Actions
logging.level.trace=TRACE
logging.level.debug=DEBUG
@@ -1386,16 +1385,16 @@ logging.level.info=INFO
logging.level.warn=AVERT
logging.level.error=ERREUR
logging.buffer.size=Taille du tampon
logging.buffer.max.size=Taille du tampon maximum : {0}
logging.update=Mettre à jour
logging.configured.loggers=Enregistreurs configurés
logging.buffer.max.size=Taille du tampon maximum\u00A0: {0}
logging.update=Mettre \u00E0 jour
logging.configured.loggers=Enregistreurs configur\u00E9s
logging.remove=Supprimer
logging.confirm.remove=Voulez-vous supprimer cette configuration denregistreur?
logging.autoscroll=Défilement automatique des nouveaux messages
logging.connecting=Connexion au flux denregistrements
logging.settings.updated=Paramètres mis à jours avec succès
logging.confirm.remove=Voulez-vous supprimer cette configuration d\u2019enregistreur\u202F?
logging.autoscroll=D\u00E9filement automatique des nouveaux messages
logging.connecting=Connexion au flux d\u2019enregistrements\u2026
logging.settings.updated=Param\u00E8tres mis \u00E0 jours avec succ\u00E8s
logging.error=Erreur
logging.connected=Connecté au flux denregistrements
logging.connection.lost=Erreur : Perte de la connexion au flux
logging.connected=Connect\u00E9 au flux d\u2019enregistrements
logging.connection.lost=Erreur\u00A0: Perte de la connexion au flux
logging.reconnecting=Tentative de reconnexion
logging.reconnect.failed=Impossible de se reconnecter après
logging.reconnect.failed=Impossible de se reconnecter apr\u00E8s

View File

@@ -590,7 +590,7 @@ a.btn,
button {
font-size: 14px;
color: var(--color-highlight);
border: none;
border: 1px solid var(--color-highlight);
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
@@ -1266,10 +1266,6 @@ body.auto-update-mode .today-fab {
color: var(--color-text-white);
}
.settings-page .settings-section .btn {
border: 1px solid;
}
@media (max-width: 800px) {
.timeline-entry {
padding: 1rem 2rem 0 1rem;
@@ -1425,9 +1421,10 @@ body.auto-update-mode .today-fab {
a.btn:disabled,
button:disabled {
background: #9b9b9b;
background: #686868;
cursor: not-allowed;
color: white;
color: #b4b4b4;
border: 1px solid #9d9d9d;
}
.pulsating-marker {

View File

@@ -11,7 +11,7 @@
<a sec:authorize="hasAnyRole('ROLE_ADMIN', 'ROLE_USER', 'ROLE_MAGIC_LINK_FULL_ACCESS')" th:if="${activeSection == 'index'}" type="button" class="nav-link" id="auto-update-btn" onclick="toggleAutoUpdate()" th:title="#{map.auto-update.enable.title}" title="Auto Update"><i class="lni lni-play"></i></a>
<a sec:authorize="hasAnyRole('ROLE_ADMIN', 'ROLE_USER', 'ROLE_MAGIC_LINK_FULL_ACCESS')" th:if="${activeSection == 'index'}" type="button" class="nav-link" onclick="toggleFullscreen()" th:title="#{map.fullscreen.toggle.title}" title="Toggle Fullscreen"><i class="lni lni-arrow-all-direction"></i></a>
<form th:action="@{/logout}" method="post">
<button type="submit" class="nav-link" th:title="#{nav.logout.tooltip}"><i
<button type="submit" class="nav-link" th:title="#{nav.logout.tooltip}" style="border: none"><i
class="lni lni-exit"></i>
</button>
</form>

View File

@@ -43,8 +43,8 @@
hx-get="/settings/export-data/data-content"
hx-target="#data-content"
hx-swap="innerHTML"
hx-trigger="load, change"
hx-include="#endDate"
hx-trigger="change"
hx-include="#endDate, #pageSizeSelect"
hx-indicator="#loading-indicator"
hx-vals='js:{"timezone": getUserTimezone()}'
required>
@@ -60,7 +60,7 @@
hx-target="#data-content"
hx-swap="innerHTML"
hx-trigger="load, change"
hx-include="#startDate"
hx-include="#startDate, #pageSizeSelect"
hx-indicator="#loading-indicator"
hx-vals='js:{"timezone": getUserTimezone()}'
required>
@@ -95,18 +95,74 @@
</div>
<div id="data-content" th:fragment="data-content">
<div th:if="${rawLocationPoints.isEmpty()}">
<div th:if="${rawLocationPoints.isEmpty() and totalElements == 0}">
<p th:text="#{export.raw.data.no.data}">No location data found for the selected date range</p>
</div>
<div th:if="${!rawLocationPoints.isEmpty()}">
<p style="margin-bottom: 15px; color: var(--color-text-white);" th:text="#{export.raw.data.found.data(${rawLocationPoints.size()})}">
Found 123234 location points
</p>
<div th:if="${totalElements > 0}">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
<div>
<p th:text="#{export.raw.data.showing(${currentPage * pageSize + 1}, ${currentPage * pageSize + rawLocationPoints.size()}, ${totalElements})}">
Showing 1 - 100 of 1000
</p>
</div>
<div style="display: flex; align-items: center; gap: 15px;">
<!-- Page size selector -->
<div style="display: flex; align-items: center; gap: 8px;">
<label for="pageSizeSelect" th:text="#{export.raw.data.show}">Show:</label>
<select id="pageSizeSelect"
name="size"
hx-get="/settings/export-data/data-content"
hx-target="#data-content"
hx-swap="innerHTML"
hx-include="#startDate, #endDate"
hx-vals='js:{"timezone": getUserTimezone(), "page": 0}'
hx-trigger="change">
<option value="100" th:selected="${pageSize == 100}">100</option>
<option value="500" th:selected="${pageSize == 500}">500</option>
<option value="1000" th:selected="${pageSize == 1000}">1000</option>
</select>
</div>
<!-- Page navigation -->
<div style="display: flex; align-items: center; gap: 8px;">
<button type="button"
class="btn btn-sm"
th:disabled="${!hasPrevious}"
hx-get="/settings/export-data/data-content"
hx-target="#data-content"
hx-swap="innerHTML"
hx-include="#startDate, #endDate, #pageSizeSelect"
th:hx-vals="|js:{'timezone': getUserTimezone(), 'page': ${currentPage - 1}}|"
hx-trigger="click"
th:text="#{export.raw.data.previous}">
Previous
</button>
<span th:text="#{export.raw.data.page.info(${currentPage + 1}, ${totalPages})}">
Page 1 of 10
</span>
<button type="button"
class="btn btn-sm"
th:disabled="${!hasNext}"
hx-get="/settings/export-data/data-content"
hx-target="#data-content"
hx-swap="innerHTML"
hx-include="#startDate, #endDate, #pageSizeSelect"
th:hx-vals="|js:{'timezone': getUserTimezone(), 'page': ${currentPage + 1}}|"
hx-trigger="click"
th:text="#{export.raw.data.next}">
Next
</button>
</div>
</div>
</div>
<div class="export-data-table-container">
<table>
<thead style="">
<thead>
<tr>
<th th:text="#{export.raw.data.table.timestamp}">Timestamp</th>
<th th:text="#{export.raw.data.table.latitude}">Latitude</th>