From 04fb0091c7aeb3731737f77400d0c484ba957dc7 Mon Sep 17 00:00:00 2001 From: Daniel Graf Date: Mon, 29 Dec 2025 16:21:59 +0100 Subject: [PATCH] 591 feature request add pagination to export data (#601) --- .../settings/ExportDataController.java | 30 +++- .../RawLocationPointJdbcService.java | 36 ++++ src/main/resources/messages.properties | 7 +- src/main/resources/messages_de.properties | 84 +++++----- src/main/resources/messages_fr.properties | 155 +++++++++--------- src/main/resources/static/css/main.css | 11 +- .../templates/fragments/main-navigation.html | 2 +- .../templates/settings/export-data.html | 74 ++++++++- 8 files changed, 256 insertions(+), 143 deletions(-) diff --git a/src/main/java/com/dedicatedcode/reitti/controller/settings/ExportDataController.java b/src/main/java/com/dedicatedcode/reitti/controller/settings/ExportDataController.java index 2a4797e9..3db85923 100644 --- a/src/main/java/com/dedicatedcode/reitti/controller/settings/ExportDataController.java +++ b/src/main/java/com/dedicatedcode/reitti/controller/settings/ExportDataController.java @@ -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 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 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 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"; } diff --git a/src/main/java/com/dedicatedcode/reitti/repository/RawLocationPointJdbcService.java b/src/main/java/com/dedicatedcode/reitti/repository/RawLocationPointJdbcService.java index 99e5d0c8..90d18f82 100644 --- a/src/main/java/com/dedicatedcode/reitti/repository/RawLocationPointJdbcService.java +++ b/src/main/java/com/dedicatedcode/reitti/repository/RawLocationPointJdbcService.java @@ -80,6 +80,42 @@ public class RawLocationPointJdbcService { user.getId(), Timestamp.from(startTime), Timestamp.from(endTime)); } + public List 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 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 " + diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 3f6a2a50..cd536f30 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -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: diff --git a/src/main/resources/messages_de.properties b/src/main/resources/messages_de.properties index 4fbf1a1b..22c660e1 100644 --- a/src/main/resources/messages_de.properties +++ b/src/main/resources/messages_de.properties @@ -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 Logging details → Log to custom URL +integrations.gpslogger.step2=\u00D6ffnen Sie GPSLogger und gehen Sie zu Logging details \u2192 Log to custom URL integrations.gpslogger.step3="Log to custom URL" aktivieren integrations.gpslogger.step4.with.token=Setzen Sie die URL auf: {0} integrations.gpslogger.step4.without.token=Setzen Sie die URL auf: {0} @@ -1303,42 +1308,41 @@ integrations.gpslogger.step6=Setzen Sie HTTP Body auf: integrations.gpslogger.step7=HTTP-Header auf: Content-Typ: Anwendung/json 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 Settings → Connection +integrations.owntracks.step2=\u00D6ffnen Sie OwnTracks und gehen Sie zu Settings \u2192 Connection integrations.owntracks.step3=Modus auf HTTP einstellen integrations.owntracks.step4.with.token=Endpoint auf: {0} integrations.owntracks.step4.without.token=Endpoint auf: {0} integrations.owntracks.step5=Deaktivieren Authentication (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 Settings -integrations.overland.step3=Important: Tippen Sie auf die Request Permission Schaltfläche, um den Standortzugriff zu gewähren - Overland wird ohne diese Berechtigung nichts tracken -integrations.overland.step4=Tippen Sie auf Empfänger Endpoint +integrations.overland.step2=\u00D6ffnen Sie Overland und gehen Sie auf die Registerkarte Settings +integrations.overland.step3=Important: Tippen Sie auf die Request Permission Schaltfl\u00E4che, um den Standortzugriff zu gew\u00E4hren - Overland wird ohne diese Berechtigung nichts tracken +integrations.overland.step4=Tippen Sie auf Empf\u00E4nger Endpoint integrations.overland.step5.with.token=Endpoint URL auf: {0} integrations.overland.step5.without.token=Setz den Endpunkt URL zu: {0} integrations.overland.step6=Lassen Sie das Feld Device ID leer oder setzen Sie eine benutzerdefinierte Kennung integrations.overland.step7=Lassen Sie das Feld Access Token leer (wir verwenden das Token in der URL) -integrations.overland.step8=Konfigurieren Sie Tracking-Einstellungen:
  • Desired Accuracy: Best (für hohe Genauigkeit) oder 100m (für Batteriesparen)
  • Points per Batch: 50-200 (niedriger für unzuverlässige Verbindungen)
  • Significant Location: Deaktiviert
+integrations.overland.step8=Konfigurieren Sie Tracking-Einstellungen:
  • Desired Accuracy: Best (f\u00FCr hohe Genauigkeit) oder 100m (f\u00FCr Batteriesparen)
  • Points per Batch: 50-200 (niedriger f\u00FCr unzuverl\u00E4ssige Verbindungen)
  • Significant Location: Deaktiviert
integrations.overland.step9=Gehen Sie zum Tracker Tab und schalten Sie Tracking auf On 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 diff --git a/src/main/resources/messages_fr.properties b/src/main/resources/messages_fr.properties index 1da43800..e286897d 100644 --- a/src/main/resources/messages_fr.properties +++ b/src/main/resources/messages_fr.properties @@ -156,7 +156,7 @@ users.home.location.clear=Effacer # Places places.title=Lieux significatifs -places.no.places=Aucun lieu significatif trouvé. +places.no.places=Aucun lieu significatif 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|1Logging details → Log to custom URL -integrations.gpslogger.step3=Activer « Log to custom URL » -integrations.gpslogger.step4.with.token=Remplir avec l’URL suivante : {0} -integrations.gpslogger.step4.without.token=Remplir avec l’URL suivante : {0} -integrations.gpslogger.step5=Mettre la méthode HTTP à POST -integrations.gpslogger.step6=Remplir « HTTP Body » avec : -integrations.gpslogger.step7=Remplir « HTTP header » avec : Content-Type: application/json -integrations.gpslogger.step8=Commencez l’enregistrement ! -integrations.owntracks.step1=Téléchargez OwnTracks depuis l’App Store ou le Google Play Store -integrations.owntracks.step2=Ouvrez OwnTracks et rendez-vous sur Settings → Connection -integrations.owntracks.step3=Mettre le « Mode » à HTTP -integrations.owntracks.step4.with.token=Remplir « Endpoint » avec : {0} -integrations.owntracks.step4.without.token=Remplir « Endpoint » avec : {0} -integrations.owntracks.step5=Désactiver Authentication (on utilise le token dans l’URL à 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=L’application 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 Logging details \u2192 Log to custom URL +integrations.gpslogger.step3=Activer \u00AB\u202FLog to custom URL\u202F\u00BB +integrations.gpslogger.step4.with.token=Remplir avec l\u2019URL suivante\u00A0: {0} +integrations.gpslogger.step4.without.token=Remplir avec l\u2019URL suivante\u00A0: {0} +integrations.gpslogger.step5=Mettre la m\u00E9thode HTTP \u00E0 POST +integrations.gpslogger.step6=Remplir \u00AB\u202FHTTP Body\u202F\u00BB avec\u00A0: +integrations.gpslogger.step7=Remplir \u00AB\u202FHTTP header\u202F\u00BB avec\u00A0: Content-Type: application/json +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 Settings \u2192 Connection +integrations.owntracks.step3=Mettre le \u00AB\u202FMode\u202F\u00BB \u00E0 HTTP +integrations.owntracks.step4.with.token=Remplir \u00AB\u202FEndpoint\u202F\u00BB avec\u00A0: {0} +integrations.owntracks.step4.without.token=Remplir \u00AB\u202FEndpoint\u202F\u00BB avec\u00A0: {0} +integrations.owntracks.step5=D\u00E9sactiver Authentication (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 Settings -integrations.overland.step3=Important : Touchez le bouton Request Permission pour permettre l’accès aux données GPS. Overland ne fonctionnera pas sans cette permission +integrations.overland.step3=Important\u00A0: Touchez le bouton Request Permission pour permettre l\u2019acc\u00E8s aux donn\u00E9es GPS. Overland ne fonctionnera pas sans cette permission integrations.overland.step4=Touchez Receiver Endpoint -integrations.overland.step5.with.token=Remplir « Enpoint URL » avec : {0} -integrations.overland.step5.without.token=Remplir « Enpoint URL » avec : {0} -integrations.overland.step6=Laissez le champ Device ID vide, ou mettez-y un identifiant personnalisé -integrations.overland.step7=Laissez le champ Access Token vide (c’est le token dans l’URL qui est utilisé) -integrations.overland.step8=Configurez les paramètres de suivi :
  • Desired Accuracy: Best (pour une précision élevée) ou 100m (pour économiser la batterie)
  • Points per Batch: 50 à 200 (plus bas pour les connexions peu fiables)
  • Significant Location: Disabled pour un suivi continu
-integrations.overland.step9=Allez sur l’onglet Tracker et activez le suivi -integrations.overland.step10=Ajustez l’intervalle d’envoi (1 seconde à 30 minutes) -integrations.overland.step11=L’application commencera à envoyer les données de localisation automatiquement +integrations.overland.step5.with.token=Remplir \u00AB\u202FEnpoint URL\u202F\u00BB avec\u00A0: {0} +integrations.overland.step5.without.token=Remplir \u00AB\u202FEnpoint URL\u202F\u00BB avec\u00A0: {0} +integrations.overland.step6=Laissez le champ Device ID vide, ou mettez-y un identifiant personnalis\u00E9 +integrations.overland.step7=Laissez le champ Access Token vide (c\u2019est le token dans l\u2019URL qui est utilis\u00E9) +integrations.overland.step8=Configurez les param\u00E8tres de suivi\u00A0:
  • Desired Accuracy: Best (pour une pr\u00E9cision \u00E9lev\u00E9e) ou 100m (pour \u00E9conomiser la batterie)
  • Points per Batch: 50 \u00E0 200 (plus bas pour les connexions peu fiables)
  • Significant Location: Disabled pour un suivi continu
+integrations.overland.step9=Allez sur l\u2019onglet Tracker et activez 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 l’URL de base, le nom d’utilisateur et l’ID de l’appareil -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 d’historique… +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 d’aperç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 diff --git a/src/main/resources/static/css/main.css b/src/main/resources/static/css/main.css index 05606b10..14e3717b 100644 --- a/src/main/resources/static/css/main.css +++ b/src/main/resources/static/css/main.css @@ -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 { diff --git a/src/main/resources/templates/fragments/main-navigation.html b/src/main/resources/templates/fragments/main-navigation.html index adefde3e..69c2e0f3 100644 --- a/src/main/resources/templates/fragments/main-navigation.html +++ b/src/main/resources/templates/fragments/main-navigation.html @@ -11,7 +11,7 @@
- diff --git a/src/main/resources/templates/settings/export-data.html b/src/main/resources/templates/settings/export-data.html index 1a63c2e9..67cf16dc 100644 --- a/src/main/resources/templates/settings/export-data.html +++ b/src/main/resources/templates/settings/export-data.html @@ -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 @@
-
+

No location data found for the selected date range

-
-

- Found 123234 location points -

+
+
+
+

+ Showing 1 - 100 of 1000 +

+
+ +
+ +
+ + +
+ + +
+ + + + Page 1 of 10 + + + +
+
+
- +
Timestamp Latitude