From 28868cf804f17f5521bca389b8d5ce07da5e35c6 Mon Sep 17 00:00:00 2001 From: Kevin Cantrell Date: Sat, 30 May 2026 21:01:45 +0900 Subject: [PATCH] updated backbuttons, updated sizes, updated filters, updated graphs and fixed some view pages. --- messages/en.json | 153 ++++++++++- messages/ja.json | 153 ++++++++++- package.json | 2 +- pnpm-lock.yaml | 10 +- .../dashboard/DashboardCards.svelte | 31 ++- .../dashboard/DashboardTable.svelte | 10 +- .../displays/AirDisplay/AirDisplay.svelte | 68 ++++- .../displays/RelayDisplay/RelayDisplay.svelte | 3 +- .../displays/SoilDisplay/SoilDisplay.svelte | 173 +++++++++++-- .../TrafficDisplay/TrafficDisplay.svelte | 3 +- .../displays/WaterDisplay/WaterDisplay.svelte | 3 +- src/lib/i18n/cwuiLabels.ts | 240 ++++++++++++++++++ src/lib/navigation/backTo.ts | 29 +++ src/lib/theme/appTheme.svelte.ts | 44 ++++ src/routes/+layout.svelte | 3 +- src/routes/OverviewDrawer.svelte | 8 +- src/routes/account/billing/+page.svelte | 5 +- src/routes/gateways/+page.svelte | 7 +- src/routes/locations/+page.svelte | 7 +- .../locations/[location_id]/+page.svelte | 8 +- .../devices/[dev_eui]/+page.svelte | 5 +- .../[dev_eui]/DeviceDashboardHeader.svelte | 18 +- .../dialogs/NotesReviewDialog.svelte | 3 + .../[dev_eui]/dialogs/csvExportDialog.svelte | 2 + .../[location_id]/devices/create/+page.svelte | 2 + .../[location_id]/settings/+page.svelte | 1 + .../settings/LocationEditPermissions.svelte | 3 +- src/routes/reports/+page.svelte | 7 +- src/routes/reports/ReportHistoryDialog.svelte | 3 +- src/routes/rules-new/+page.svelte | 7 +- src/routes/rules/+page.svelte | 7 +- 31 files changed, 939 insertions(+), 79 deletions(-) create mode 100644 src/lib/i18n/cwuiLabels.ts create mode 100644 src/lib/navigation/backTo.ts create mode 100644 src/lib/theme/appTheme.svelte.ts diff --git a/messages/en.json b/messages/en.json index 81eb68fd..8034040b 100644 --- a/messages/en.json +++ b/messages/en.json @@ -835,6 +835,11 @@ "display_add_note_body": "Add a note for the telemetry entry at {createdAt} with temperature {temperature}°C.", "display_add_note_title": "Add Note for {createdAt}", "display_add_note_today_title": "Add Note for Today's Reading", + "display_air_quality": "Air Quality", + "display_air_quality_metrics": "CO₂, air temperature & humidity", + "display_light_ppfd": "Light (PPFD)", + "display_vpd": "Vapor Pressure Deficit (VPD)", + "display_awaiting_sensor_data": "Awaiting sensor data", "display_today_reading_label": "Select a reading from today", "display_loading_today_readings": "Loading today's readings…", "display_no_readings_today": "No readings have arrived today yet.", @@ -1051,5 +1056,151 @@ "sensor_people_count": "People Count", "sensor_car_count": "Car Count", "sensor_value_on": "On", - "sensor_value_off": "Off" + "sensor_value_off": "Off", + "cwui_table_search": "Search...", + "cwui_table_loading": "Loading...", + "cwui_table_loading_more": "Loading more rows...", + "cwui_table_no_sort": "No sort", + "cwui_table_sort_asc": "{header} (A-Z)", + "cwui_table_sort_desc": "{header} (Z-A)", + "cwui_table_page_size": "{n} rows", + "cwui_table_page_size_batch": "{n} rows/batch", + "cwui_table_toolbar_menu": "Open table options", + "cwui_table_columns_settings": "Columns Settings", + "cwui_table_refresh": "Refresh", + "cwui_table_actions": "Actions", + "cwui_table_error_prefix": "Error: {message}", + "cwui_table_retry": "Retry", + "cwui_table_empty": "No data available", + "cwui_table_generic_error": "An error occurred", + "cwui_table_load_more_error": "Unable to load more rows", + "cwui_table_group_count": "{count} items", + "cwui_table_range": "{from}–{to} of {total}", + "cwui_table_range_loaded": "{from}–{to} loaded", + "cwui_table_load_interrupted": "Load interrupted", + "cwui_table_scroll_to_load_more": "Scroll to load more", + "cwui_table_all_rows_loaded": "All {total} rows loaded", + "cwui_table_all_loaded_visible": "All loaded rows visible", + "cwui_table_previous_page": "Previous page", + "cwui_table_next_page": "Next page", + "cwui_table_page_of": "Page {page} of {totalPages}", + "cwui_table_column_settings_copy": "Choose which columns are visible for this grid.", + "cwui_table_visible_columns": "Visible columns", + "cwui_table_select_at_least_one": "Select at least one column before saving.", + "cwui_table_close": "Close", + "cwui_table_reset_default": "Reset to Default", + "cwui_table_save": "Save", + "cwui_chart_range_1h": "1H", + "cwui_chart_range_24h": "24H", + "cwui_chart_range_7d": "7D", + "cwui_chart_range_group_aria": "Time range", + "cwui_chart_toggle_theme_aria": "Toggle theme", + "cwui_chart_sensors_on_of": "Sensors · {on} of {total} on", + "cwui_chart_anomaly": "anomaly", + "cwui_chart_data_gap": "data gap", + "cwui_chart_no_signal": "no signal", + "cwui_chart_zoom_hint": "Hold Ctrl + scroll to zoom", + "cwui_chart_last_sensor": "At least one sensor must stay visible", + "cwui_chart_hide_series": "Hide {label}", + "cwui_chart_show_series": "Show {label}", + "cwui_donut_aria": "Donut chart", + "cwui_donut_total": "Total", + "cwui_donut_of_total": "of {total}", + "cwui_donut_segment": "{label}: {value}", + "cwui_heatmap_title": "Temperature Heatmap", + "cwui_heatmap_no_data": "No data", + "cwui_ppfd_eyebrow": "Photosynthetic Photon Flux Density", + "cwui_ppfd_heading": "PPFD range gauge", + "cwui_ppfd_heading_plant": "{plant} PPFD", + "cwui_ppfd_current": "Current PPFD", + "cwui_ppfd_current_marker": "Current", + "cwui_ppfd_dli_reading": "DLI Today", + "cwui_ppfd_dli_stat": "DLI today", + "cwui_ppfd_target_range": "Target range", + "cwui_ppfd_status": "Status", + "cwui_ppfd_too_low": "Too low", + "cwui_ppfd_too_high": "Too high", + "cwui_ppfd_optimal": "Optimal", + "cwui_ppfd_inside_band": "Inside target band", + "cwui_ppfd_delta_below": "{amount} {unit} below target", + "cwui_ppfd_delta_above": "{amount} {unit} above target", + "cwui_ppfd_updated": "Updated {when}", + "cwui_vpd_caption": "Vapor pressure deficit heatmap by temperature and relative humidity", + "cwui_vpd_zone_wet": "Wet", + "cwui_vpd_zone_humid": "Humid", + "cwui_vpd_zone_balanced": "Balanced", + "cwui_vpd_zone_optimal": "Optimal", + "cwui_vpd_zone_firm": "Firm", + "cwui_vpd_zone_dry": "Dry", + "cwui_vpd_zone_stress": "Stress", + "cwui_vpd_cell": "{t} degrees Celsius, {h} percent relative humidity, {vpd} {unit}, {zone} zone", + "cwui_vpd_cell_in_target": ", inside target band", + "cwui_vpd_cell_current": ", current room climate cell", + "cwui_offline_title": "You're Offline", + "cwui_offline_message": "It looks like you've lost your internet connection. CropWatch needs an active connection to sync your device data.", + "cwui_offline_retry": "Try Again", + "cwui_offline_footnote": "Don't worry — we'll automatically reconnect when your connection is restored.", + "cwui_offline_help_title": "While you wait, you can try:", + "cwui_offline_help_wifi": "Checking if your WiFi is connected", + "cwui_offline_help_router": "Moving closer to your router", + "cwui_offline_help_mobile": "Switching to mobile data if available", + "cwui_offline_reconnected_title": "You're back online!", + "cwui_offline_reconnected_subtitle": "Connection restored successfully.", + "cwui_cal_aria": "Calendar scroll", + "cwui_cal_today": "Today", + "cwui_cal_data_available": "Data available", + "cwui_cal_no_data": "No data", + "cwui_cal_row_aria": "{key}, {status}", + "cwui_cal_actions_aria": "Actions for {key}", + "cwui_cal_content_placeholder": "No content", + "cwui_cal_no_data_for_date": "No data for this date.", + "cwui_cal_empty_title": "No dated rows to show.", + "cwui_cal_empty_copy": "Nothing to display yet.", + "cwui_dtr_placeholder": "Select date…", + "cwui_dtr_dialog": "Date picker", + "cwui_dtr_previous": "Previous", + "cwui_dtr_next": "Next", + "cwui_dtr_time": "Time", + "cwui_dtr_start_time": "Start time", + "cwui_dtr_end_time": "End time", + "cwui_dtr_cancel": "Cancel", + "cwui_dtr_set": "Set", + "cwui_copy_copy": "Copy to clipboard", + "cwui_copy_copied": "Copied", + "cwui_copy_feedback": "Copied!", + "cwui_dli_title": "DLI Today", + "cwui_dli_status_very_low": "Very low", + "cwui_dli_status_slightly_low": "Slightly low", + "cwui_dli_status_low": "Low", + "cwui_dli_status_good": "Good", + "cwui_dli_status_high": "High", + "cwui_dli_status_very_high": "Very high", + "cwui_dli_status_prefix": "Status:", + "cwui_dli_history_title": "Daily history", + "cwui_dli_history_list": "Daily DLI history", + "cwui_dli_history_count": "{days} days", + "cwui_dli_target_crop": "Target for {crop}: {range}", + "cwui_dli_target": "Target: {range}", + "cwui_dli_value_aria": "{value} {unit}, {target}, {status}", + "cwui_dli_bar_aria": "Scale 0 to {scaleMax}, current {value} {unit}, target {range}, {status}", + "cwui_wind_eyebrow": "Wind", + "cwui_wind_heading": "{location} wind", + "cwui_wind_heading_fallback": "Wind compass", + "cwui_wind_pill": "Bft {force} · {label}", + "cwui_wind_reading": "Wind {word}", + "cwui_wind_updated": "Updated {formatted}", + "cwui_wind_from": "from", + "cwui_wind_to": "to", + "cwui_wind_stat_direction": "Direction", + "cwui_wind_stat_heading": "Heading {word}", + "cwui_wind_stat_speed": "Speed", + "cwui_wind_stat_beaufort": "Beaufort", + "cwui_wind_stat_beaufort_value": "F{force} · {label}", + "cwui_wind_stat_conditions": "Conditions", + "cwui_wind_cardinals": "N,NNE,NE,ENE,E,ESE,SE,SSE,S,SSW,SW,WSW,W,WNW,NW,NNW", + "cwui_wind_cardinal_letters": "N,E,S,W", + "cwui_wind_beaufort_names": "Calm,Light air,Light breeze,Gentle breeze,Moderate,Fresh,Strong,Near gale,Gale,Strong gale,Storm,Violent storm,Hurricane", + "cwui_wind_sr_wind": "Wind {word} {direction} {cardinal}", + "cwui_wind_sr_speed": "Speed {speed}", + "cwui_wind_sr_beaufort": "Beaufort {force}, {label}" } diff --git a/messages/ja.json b/messages/ja.json index e744862b..f1a86dd4 100644 --- a/messages/ja.json +++ b/messages/ja.json @@ -850,6 +850,11 @@ "display_add_note_body": "{createdAt} のテレメトリエントリに、温度 {temperature}°C のメモを追加します。", "display_add_note_title": "{createdAt} のメモを追加", "display_add_note_today_title": "本日の測定値にメモを追加", + "display_air_quality": "空気質", + "display_air_quality_metrics": "CO₂・気温・湿度", + "display_light_ppfd": "光量 (PPFD)", + "display_vpd": "飽差 (VPD)", + "display_awaiting_sensor_data": "センサーデータ待機中", "display_today_reading_label": "本日の測定値を選択", "display_loading_today_readings": "本日の測定値を読み込み中…", "display_no_readings_today": "本日の測定値はまだありません。", @@ -1051,5 +1056,151 @@ "sensor_people_count": "人数", "sensor_car_count": "車両数", "sensor_value_on": "オン", - "sensor_value_off": "オフ" + "sensor_value_off": "オフ", + "cwui_table_search": "検索...", + "cwui_table_loading": "読み込み中...", + "cwui_table_loading_more": "さらに読み込み中...", + "cwui_table_no_sort": "並べ替えなし", + "cwui_table_sort_asc": "{header}(昇順)", + "cwui_table_sort_desc": "{header}(降順)", + "cwui_table_page_size": "{n} 行", + "cwui_table_page_size_batch": "{n} 行/バッチ", + "cwui_table_toolbar_menu": "テーブルオプションを開く", + "cwui_table_columns_settings": "列の設定", + "cwui_table_refresh": "更新", + "cwui_table_actions": "操作", + "cwui_table_error_prefix": "エラー: {message}", + "cwui_table_retry": "再試行", + "cwui_table_empty": "データがありません", + "cwui_table_generic_error": "エラーが発生しました", + "cwui_table_load_more_error": "追加の行を読み込めません", + "cwui_table_group_count": "{count} 件", + "cwui_table_range": "{from}–{to} / {total}", + "cwui_table_range_loaded": "{from}–{to} 読み込み済み", + "cwui_table_load_interrupted": "読み込みが中断されました", + "cwui_table_scroll_to_load_more": "スクロールしてさらに読み込む", + "cwui_table_all_rows_loaded": "全 {total} 行を読み込みました", + "cwui_table_all_loaded_visible": "読み込んだ行をすべて表示中", + "cwui_table_previous_page": "前のページ", + "cwui_table_next_page": "次のページ", + "cwui_table_page_of": "{page} / {totalPages} ページ", + "cwui_table_column_settings_copy": "このグリッドに表示する列を選択してください。", + "cwui_table_visible_columns": "表示する列", + "cwui_table_select_at_least_one": "保存する前に少なくとも 1 つの列を選択してください。", + "cwui_table_close": "閉じる", + "cwui_table_reset_default": "デフォルトに戻す", + "cwui_table_save": "保存", + "cwui_chart_range_1h": "1時間", + "cwui_chart_range_24h": "24時間", + "cwui_chart_range_7d": "7日", + "cwui_chart_range_group_aria": "期間", + "cwui_chart_toggle_theme_aria": "テーマを切り替え", + "cwui_chart_sensors_on_of": "センサー · {total} 個中 {on} 個表示", + "cwui_chart_anomaly": "異常", + "cwui_chart_data_gap": "データ欠損", + "cwui_chart_no_signal": "信号なし", + "cwui_chart_zoom_hint": "Ctrl + スクロールでズーム", + "cwui_chart_last_sensor": "少なくとも 1 つのセンサーを表示する必要があります", + "cwui_chart_hide_series": "{label} を非表示", + "cwui_chart_show_series": "{label} を表示", + "cwui_donut_aria": "ドーナツチャート", + "cwui_donut_total": "合計", + "cwui_donut_of_total": "/ {total}", + "cwui_donut_segment": "{label}: {value}", + "cwui_heatmap_title": "温度ヒートマップ", + "cwui_heatmap_no_data": "データなし", + "cwui_ppfd_eyebrow": "光合成光量子束密度", + "cwui_ppfd_heading": "PPFD レンジゲージ", + "cwui_ppfd_heading_plant": "{plant} PPFD", + "cwui_ppfd_current": "現在の PPFD", + "cwui_ppfd_current_marker": "現在", + "cwui_ppfd_dli_reading": "本日の DLI", + "cwui_ppfd_dli_stat": "本日の DLI", + "cwui_ppfd_target_range": "目標範囲", + "cwui_ppfd_status": "ステータス", + "cwui_ppfd_too_low": "低すぎ", + "cwui_ppfd_too_high": "高すぎ", + "cwui_ppfd_optimal": "最適", + "cwui_ppfd_inside_band": "目標範囲内", + "cwui_ppfd_delta_below": "目標まで {amount} {unit} 不足", + "cwui_ppfd_delta_above": "目標を {amount} {unit} 超過", + "cwui_ppfd_updated": "更新 {when}", + "cwui_vpd_caption": "気温と相対湿度による飽差ヒートマップ", + "cwui_vpd_zone_wet": "過湿", + "cwui_vpd_zone_humid": "湿潤", + "cwui_vpd_zone_balanced": "バランス", + "cwui_vpd_zone_optimal": "最適", + "cwui_vpd_zone_firm": "やや乾燥", + "cwui_vpd_zone_dry": "乾燥", + "cwui_vpd_zone_stress": "ストレス", + "cwui_vpd_cell": "{t}°C、相対湿度 {h}%、{vpd} {unit}、{zone} ゾーン", + "cwui_vpd_cell_in_target": "、目標範囲内", + "cwui_vpd_cell_current": "、現在の室内環境セル", + "cwui_offline_title": "オフラインです", + "cwui_offline_message": "インターネット接続が切断されたようです。CropWatch はデバイスデータを同期するためにアクティブな接続が必要です。", + "cwui_offline_retry": "再試行", + "cwui_offline_footnote": "接続が回復すると自動的に再接続しますのでご安心ください。", + "cwui_offline_help_title": "お待ちの間にお試しください:", + "cwui_offline_help_wifi": "Wi-Fi が接続されているか確認する", + "cwui_offline_help_router": "ルーターに近づく", + "cwui_offline_help_mobile": "可能であればモバイルデータに切り替える", + "cwui_offline_reconnected_title": "オンラインに戻りました!", + "cwui_offline_reconnected_subtitle": "接続が正常に回復しました。", + "cwui_cal_aria": "カレンダースクロール", + "cwui_cal_today": "今日", + "cwui_cal_data_available": "データあり", + "cwui_cal_no_data": "データなし", + "cwui_cal_row_aria": "{key}、{status}", + "cwui_cal_actions_aria": "{key} の操作", + "cwui_cal_content_placeholder": "コンテンツなし", + "cwui_cal_no_data_for_date": "この日付のデータはありません。", + "cwui_cal_empty_title": "表示する日付の行がありません。", + "cwui_cal_empty_copy": "まだ表示するものがありません。", + "cwui_dtr_placeholder": "日付を選択…", + "cwui_dtr_dialog": "日付ピッカー", + "cwui_dtr_previous": "前へ", + "cwui_dtr_next": "次へ", + "cwui_dtr_time": "時刻", + "cwui_dtr_start_time": "開始時刻", + "cwui_dtr_end_time": "終了時刻", + "cwui_dtr_cancel": "キャンセル", + "cwui_dtr_set": "設定", + "cwui_copy_copy": "クリップボードにコピー", + "cwui_copy_copied": "コピーしました", + "cwui_copy_feedback": "コピーしました!", + "cwui_dli_title": "本日の DLI", + "cwui_dli_status_very_low": "非常に低い", + "cwui_dli_status_slightly_low": "やや低い", + "cwui_dli_status_low": "低い", + "cwui_dli_status_good": "良好", + "cwui_dli_status_high": "高い", + "cwui_dli_status_very_high": "非常に高い", + "cwui_dli_status_prefix": "ステータス:", + "cwui_dli_history_title": "日別履歴", + "cwui_dli_history_list": "日別 DLI 履歴", + "cwui_dli_history_count": "{days} 日", + "cwui_dli_target_crop": "{crop} の目標: {range}", + "cwui_dli_target": "目標: {range}", + "cwui_dli_value_aria": "{value} {unit}、{target}、{status}", + "cwui_dli_bar_aria": "0〜{scaleMax} のスケール、現在 {value} {unit}、目標 {range}、{status}", + "cwui_wind_eyebrow": "風", + "cwui_wind_heading": "{location} の風", + "cwui_wind_heading_fallback": "風配図", + "cwui_wind_pill": "Bft {force} · {label}", + "cwui_wind_reading": "風 {word}", + "cwui_wind_updated": "更新 {formatted}", + "cwui_wind_from": "から", + "cwui_wind_to": "へ", + "cwui_wind_stat_direction": "風向", + "cwui_wind_stat_heading": "方位 {word}", + "cwui_wind_stat_speed": "風速", + "cwui_wind_stat_beaufort": "ビューフォート", + "cwui_wind_stat_beaufort_value": "F{force} · {label}", + "cwui_wind_stat_conditions": "状況", + "cwui_wind_cardinals": "N,NNE,NE,ENE,E,ESE,SE,SSE,S,SSW,SW,WSW,W,WNW,NW,NNW", + "cwui_wind_cardinal_letters": "N,E,S,W", + "cwui_wind_beaufort_names": "静穏,至軽風,軽風,軟風,和風,疾風,雄風,強風,疾強風,大強風,全強風,暴風,颶風", + "cwui_wind_sr_wind": "風 {word} {direction} {cardinal}", + "cwui_wind_sr_speed": "風速 {speed}", + "cwui_wind_sr_beaufort": "ビューフォート {force}、{label}" } diff --git a/package.json b/package.json index 16be65d2..e0bcd889 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ }, "packageManager": "pnpm@10.18.2+sha512.9fb969fa749b3ade6035e0f109f0b8a60b5d08a1a87fdf72e337da90dcc93336e2280ca4e44f2358a649b83c17959e9993e777c2080879f3801e6f0d999ad3dd", "dependencies": { - "@cropwatchdevelopment/cwui": "0.1.96", + "@cropwatchdevelopment/cwui": "0.1.101", "@supabase/supabase-js": "^2.98.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c62ffd37..e6ca5f98 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@cropwatchdevelopment/cwui': - specifier: 0.1.96 - version: 0.1.96(svelte@5.53.0) + specifier: 0.1.101 + version: 0.1.101(svelte@5.53.0) '@supabase/supabase-js': specifier: ^2.98.0 version: 2.98.0 @@ -117,8 +117,8 @@ importers: packages: - '@cropwatchdevelopment/cwui@0.1.96': - resolution: {integrity: sha512-2eJFQgam7Chu0zEpz7IheWxDHF438nP0SME/KbVYM90JyRdvQMbJasPe71moUInFc5hVN7seM1mmsAtpUL8f5w==, tarball: https://npm.pkg.github.com/download/@cropwatchdevelopment/cwui/0.1.96/27e1135a9b498b39ca9716d575459b173751e88f} + '@cropwatchdevelopment/cwui@0.1.101': + resolution: {integrity: sha512-Yo9c729uXQO3w9fQ3VUEKdVck4OPayUn/AXEoYGFFIoKsr7kj7FZnI5L08CnOEGsi7WfZc64L2FZ22Hn/qilVg==, tarball: https://npm.pkg.github.com/download/@cropwatchdevelopment/cwui/0.1.101/6e8fc2964422f44fc18de9f790062f8f38934663} peerDependencies: svelte: ^5.0.0 @@ -2147,7 +2147,7 @@ packages: snapshots: - '@cropwatchdevelopment/cwui@0.1.96(svelte@5.53.0)': + '@cropwatchdevelopment/cwui@0.1.101(svelte@5.53.0)': dependencies: svelte: 5.53.0 diff --git a/src/lib/components/dashboard/DashboardCards.svelte b/src/lib/components/dashboard/DashboardCards.svelte index a04c8815..e8023e8a 100644 --- a/src/lib/components/dashboard/DashboardCards.svelte +++ b/src/lib/components/dashboard/DashboardCards.svelte @@ -212,6 +212,19 @@ }); }); + const GoToDetails = (row: DashboardRow) => { + if (!row.location?.location_id) { + console.warn('Device has no location, cannot go to details', { devEui: row.dev_eui }); + return; + } + // Carry the originating page (and active group filter) so the device + // page's back button can return to the filtered dashboard. + const params = new URLSearchParams({ backTo: '/' }); + if (filters.locationGroup) params.set('filter', filters.locationGroup); + loading = true; + goto(`/locations/${row.location.location_id}/devices/${row.dev_eui}?${params.toString()}`); + }; + let removeForegroundListener: (() => void) | null = null; onMount(() => { @@ -243,6 +256,15 @@ { + const locationId = group.location?.location_id; + if (locationId == null) return; + // Carry the originating page (+ active group filter) so the location + // page's back button returns to the filtered dashboard. + const params = new URLSearchParams({ backTo: '/' }); + if (filters.locationGroup) params.set('filter', filters.locationGroup); + goto(`/locations/${locationId}?${params.toString()}`); + }} > {#each group.devices as row (row.dev_eui)} {@const primary = primaryProps(row)} @@ -295,14 +317,17 @@ + {#if row.device_type.data_table_v2 === 'cw_air_data'} + {/if} - goto(`/locations/${row.location?.location_id ?? ''}/devices/${row.dev_eui}`)} - >Details GoToDetails(row)} > + Details + {/if} diff --git a/src/lib/components/dashboard/DashboardTable.svelte b/src/lib/components/dashboard/DashboardTable.svelte index dbf887cb..49628cbd 100644 --- a/src/lib/components/dashboard/DashboardTable.svelte +++ b/src/lib/components/dashboard/DashboardTable.svelte @@ -1,8 +1,8 @@ - { if (row.location?.location_id != null) { - goto(resolve(`/locations/${row.location.location_id}/devices/${row.dev_eui}`)); + // Carry the originating page (and active group filter) so the device + // page's back button can return to the filtered dashboard. + const params = new URLSearchParams({ backTo: '/' }); + if (filters.locationGroup) params.set('filter', filters.locationGroup); + goto(`/locations/${row.location.location_id}/devices/${row.dev_eui}?${params.toString()}`); } }} /> diff --git a/src/lib/components/displays/AirDisplay/AirDisplay.svelte b/src/lib/components/displays/AirDisplay/AirDisplay.svelte index 24dbc227..f40b4408 100644 --- a/src/lib/components/displays/AirDisplay/AirDisplay.svelte +++ b/src/lib/components/displays/AirDisplay/AirDisplay.svelte @@ -5,6 +5,7 @@ CwDuration, CwHeatmap, CwStatCard, + CwWindCompass, type CwColumnDef, type CwHeatmapDataPoint, type CwStatCardData, @@ -13,6 +14,7 @@ } from '@cropwatchdevelopment/cwui'; import type { DeviceDisplayProps } from '$lib/interfaces/deviceDisplay'; import { m } from '$lib/paraglide/messages.js'; + import { cwDataTableLabels, cwHeatmapLabels, cwWindCompassLabels } from '$lib/i18n/cwuiLabels'; import './AirDisplay.css'; import NotesCreateDialog from './dialogs/notes-create-dialog.svelte'; import type { AirRow } from './interfaces/AirRow.interface'; @@ -43,6 +45,19 @@ historicalData.every((row) => row.co2 !== undefined && row.co2 !== null && row.co2 !== 0) ); + // Weather-station wind: render the compass only when the latest reading + // carries both speed and direction (stored as text, so coerce to number). + let windDirection = $derived( + latestData?.wind_direction != null ? Number(latestData.wind_direction) : null + ); + let windSpeed = $derived(latestData?.wind_speed != null ? Number(latestData.wind_speed) : null); + let hasWind = $derived( + windDirection !== null && + Number.isFinite(windDirection) && + windSpeed !== null && + Number.isFinite(windSpeed) + ); + function toAirRows(raw: Record[]): AirRow[] { return raw.map((row, index) => ({ id: String(row.id ?? row.data_id ?? `${row.created_at ?? 'row'}-${index}`), @@ -312,20 +327,33 @@ {#if !loading && rows.length > 0} - - - +
+ + + + + {#if hasWind} + + {/if} +
{#key tableKey} {/if} + + diff --git a/src/lib/components/displays/RelayDisplay/RelayDisplay.svelte b/src/lib/components/displays/RelayDisplay/RelayDisplay.svelte index 3872e05d..b6d4c90f 100644 --- a/src/lib/components/displays/RelayDisplay/RelayDisplay.svelte +++ b/src/lib/components/displays/RelayDisplay/RelayDisplay.svelte @@ -19,6 +19,7 @@ type CwTableResult, CwExpandPanel } from '@cropwatchdevelopment/cwui'; + import { cwDataTableLabels } from '$lib/i18n/cwuiLabels'; import { MAX_RELAY_PULSE_DURATION_SECONDS, MIN_RELAY_PULSE_DURATION_SECONDS @@ -462,7 +463,7 @@ {#if !loading && rows.length > 0} - + {#snippet cell( row: RelayTelemetryRow, col: CwColumnDef, diff --git a/src/lib/components/displays/SoilDisplay/SoilDisplay.svelte b/src/lib/components/displays/SoilDisplay/SoilDisplay.svelte index 0fa9c425..f5bf3ed7 100644 --- a/src/lib/components/displays/SoilDisplay/SoilDisplay.svelte +++ b/src/lib/components/displays/SoilDisplay/SoilDisplay.svelte @@ -1,8 +1,9 @@ - goto(resolve('/'))}> + goto(backHref(page.url, resolve('/')))}> ← {m.action_back_to_dashboard()} - - goto(resolve('/'))}> + goto(backHref(page.url, resolve('/')))}> ← {m.action_back_to_dashboard()} - + {row.dev_eui} {:else} diff --git a/src/routes/locations/[location_id]/devices/[dev_eui]/+page.svelte b/src/routes/locations/[location_id]/devices/[dev_eui]/+page.svelte index 25a8d165..a53e735d 100644 --- a/src/routes/locations/[location_id]/devices/[dev_eui]/+page.svelte +++ b/src/routes/locations/[location_id]/devices/[dev_eui]/+page.svelte @@ -12,6 +12,8 @@ import { type RelayNumber, type RelayTargetState } from '$lib/devices/relay-types'; import { m } from '$lib/paraglide/messages.js'; import { CwCard, CwResponsiveLineChart, useCwToast } from '@cropwatchdevelopment/cwui'; + import { cwResponsiveLineChartLabels } from '$lib/i18n/cwuiLabels'; + import { appTheme } from '$lib/theme/appTheme.svelte'; import { resolveExportTimeZone } from './csvExport'; import type { PageProps } from './$types'; import { onDestroy } from 'svelte'; @@ -421,8 +423,9 @@ title={data?.device?.name || devEui.toUpperCase()} subtitle={m.display_time_series()} ranges={[]} - themeAuto + theme={appTheme.current} height={480} + labels={cwResponsiveLineChartLabels()} /> {/if} diff --git a/src/routes/locations/[location_id]/devices/[dev_eui]/DeviceDashboardHeader.svelte b/src/routes/locations/[location_id]/devices/[dev_eui]/DeviceDashboardHeader.svelte index 09a5f6dd..8e744bc2 100644 --- a/src/routes/locations/[location_id]/devices/[dev_eui]/DeviceDashboardHeader.svelte +++ b/src/routes/locations/[location_id]/devices/[dev_eui]/DeviceDashboardHeader.svelte @@ -1,6 +1,8 @@ - goto(resolve('/locations/[location_id]', { location_id: locationId }))} -> - ← {m.devices_back_to_location()} + + ← {m.action_back()} void loadNotesData()} bind:value={selectedMonth} + labels={cwDateTimeRangePickerLabels()} /> {#snippet content(item)} {#if item} diff --git a/src/routes/locations/[location_id]/devices/[dev_eui]/dialogs/csvExportDialog.svelte b/src/routes/locations/[location_id]/devices/[dev_eui]/dialogs/csvExportDialog.svelte index b9be9879..68cb67ac 100644 --- a/src/routes/locations/[location_id]/devices/[dev_eui]/dialogs/csvExportDialog.svelte +++ b/src/routes/locations/[location_id]/devices/[dev_eui]/dialogs/csvExportDialog.svelte @@ -9,6 +9,7 @@ type CwRangeDateValue, useCwToast } from '@cropwatchdevelopment/cwui'; + import { cwDateTimeRangePickerLabels } from '$lib/i18n/cwuiLabels'; import { downloadCsv, formatCsvRangeLabel, @@ -205,6 +206,7 @@ bind:value={csvRange} placeholder={m.devices_export_select_range()} maxDate={new Date()} + labels={cwDateTimeRangePickerLabels()} />

diff --git a/src/routes/locations/[location_id]/devices/create/+page.svelte b/src/routes/locations/[location_id]/devices/create/+page.svelte index 96f973a7..ef149ea7 100644 --- a/src/routes/locations/[location_id]/devices/create/+page.svelte +++ b/src/routes/locations/[location_id]/devices/create/+page.svelte @@ -12,6 +12,7 @@ type CwSingleDateValue, useCwToast } from '@cropwatchdevelopment/cwui'; + import { cwDateTimeRangePickerLabels } from '$lib/i18n/cwuiLabels'; import { TTI_DEVICE_ID_MAX_LENGTH } from '$lib/devices/tti-device-id'; import { m } from '$lib/paraglide/messages.js'; import type { PageProps } from './$types'; @@ -247,6 +248,7 @@ maxDate={new Date()} placeholder={m.devices_installed_at_placeholder()} bind:value={installedAt} + labels={cwDateTimeRangePickerLabels()} /> diff --git a/src/routes/locations/[location_id]/settings/+page.svelte b/src/routes/locations/[location_id]/settings/+page.svelte index 7225c1eb..a3a29dd5 100644 --- a/src/routes/locations/[location_id]/settings/+page.svelte +++ b/src/routes/locations/[location_id]/settings/+page.svelte @@ -17,6 +17,7 @@ goto(`/locations/${data.locationId}`)} diff --git a/src/routes/locations/[location_id]/settings/LocationEditPermissions.svelte b/src/routes/locations/[location_id]/settings/LocationEditPermissions.svelte index 224f6128..97a11584 100644 --- a/src/routes/locations/[location_id]/settings/LocationEditPermissions.svelte +++ b/src/routes/locations/[location_id]/settings/LocationEditPermissions.svelte @@ -9,6 +9,7 @@ type CwTableResult, type CwTableQuery } from '@cropwatchdevelopment/cwui'; + import { cwDataTableLabels } from '$lib/i18n/cwuiLabels'; import type { LocationOwnerDto } from '$lib/api/api.dtos'; import { getPermissionLevelLabel, getPermissionLevelOptions } from '$lib/i18n/options'; import { m } from '$lib/paraglide/messages.js'; @@ -64,7 +65,7 @@ {#key permissionsKey} - import { goto } from '$app/navigation'; + import { page } from '$app/state'; + import { backHref } from '$lib/navigation/backTo'; import { resolve } from '$app/paths'; import Icon from '$lib/components/Icon.svelte'; import { AppPage } from '$lib/components/layout'; @@ -11,6 +13,7 @@ type CwTableQuery, type CwTableResult } from '@cropwatchdevelopment/cwui'; + import { cwDataTableLabels } from '$lib/i18n/cwuiLabels'; import { m } from '$lib/paraglide/messages.js'; import ReportHistoryDialog from './ReportHistoryDialog.svelte'; import DeleteReportDialog from './DeleteReportDialog.svelte'; @@ -98,13 +101,13 @@ - goto(resolve('/'))}> + goto(backHref(page.url, resolve('/')))}> ← {m.action_back_to_dashboard()} {#key tableKey} - {#if open} - import { goto } from '$app/navigation'; + import { page } from '$app/state'; + import { backHref } from '$lib/navigation/backTo'; import { resolve } from '$app/paths'; import { readApiErrorMessage } from '$lib/api/api-error'; import { ApiService } from '$lib/api/api.service'; @@ -17,6 +19,7 @@ type CwTableQuery, type CwTableResult } from '@cropwatchdevelopment/cwui'; + import { cwDataTableLabels } from '$lib/i18n/cwuiLabels'; import { m } from '$lib/paraglide/messages.js'; import ADD_ICON from '$lib/images/icons/add.svg'; import EDIT_ICON from '$lib/images/icons/edit.svg'; @@ -163,13 +166,13 @@ - goto(resolve('/'))}> + goto(backHref(page.url, resolve('/')))}> ← {m.action_back_to_dashboard()} {#key tableKey} - + {#snippet cell( row: RuleTemplateRow, col: CwColumnDef, diff --git a/src/routes/rules/+page.svelte b/src/routes/rules/+page.svelte index 09de2846..c4aee119 100644 --- a/src/routes/rules/+page.svelte +++ b/src/routes/rules/+page.svelte @@ -9,7 +9,10 @@ type CwTableQuery, type CwTableResult } from '@cropwatchdevelopment/cwui'; + import { cwDataTableLabels } from '$lib/i18n/cwuiLabels'; import { goto } from '$app/navigation'; + import { page } from '$app/state'; + import { backHref } from '$lib/navigation/backTo'; import { resolve } from '$app/paths'; import type { RulesDto } from '$lib/interfaces/rule.interface'; import EDIT_ICON from '$lib/images/icons/edit.svg'; @@ -116,13 +119,13 @@ - goto(resolve('/'))}> + goto(backHref(page.url, resolve('/')))}> ← {m.action_back_to_dashboard()} {#key tableKey} -