mirror of
https://github.com/dedicatedcode/reitti.git
synced 2026-01-08 00:53:53 -05:00
591 feature request add pagination to export data (#601)
This commit is contained in:
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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 " +
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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|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 le Géocodage
|
||||
places.geocoding.response.button=Voir le G\u00E9ocodage
|
||||
places.geocoding.response.title=R\u00E9ponse du G\u00E9ocodage pour {0}
|
||||
places.geocoding.response.no.data=Aucune réponse de géocodage disponible pour ce lieu
|
||||
places.geocoding.response.no.data=Aucune r\u00E9ponse de g\u00E9ocodage disponible pour ce lieu
|
||||
places.geocoding.response.back=Retour aux Lieux
|
||||
places.geocoding.response.provider=Fournisseur
|
||||
places.geocoding.response.status=\u00C9tat
|
||||
@@ -1266,93 +1266,92 @@ 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 à aujourd’hui
|
||||
users.color.theme.label=Thème de couleur
|
||||
users.color.theme.description=Choisissez votre couleur d’accentuation 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=L’image est trop grande. La taille maximale est de 2 Mo.
|
||||
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 d’avatar : {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 1 Mo.
|
||||
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 l’URL de l’instance et le jeton d’API
|
||||
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 d’utilisateur
|
||||
integrations.reitti.info.user.username=Nom d’utilisateur :
|
||||
integrations.reitti.info.user.displayname=Nom d’affichage :
|
||||
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 d’API disponible
|
||||
integrations.download=Téléchargement :
|
||||
integrations.homepage=Page d’accueil :
|
||||
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 l’URL suivante : <code>{0}</code>
|
||||
integrations.gpslogger.step4.without.token=Remplir avec l’URL 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 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 <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 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 <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 l’accè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 (c’est le token dans l’URL 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 l’onglet <strong>Tracker</strong> et <strong>activez</strong> 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: <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 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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user