i18 cleanup

This commit is contained in:
2026-02-25 18:31:50 +03:00
parent 99f19ac305
commit 5d42a6b930
48 changed files with 1431 additions and 808 deletions

View File

@@ -159,7 +159,7 @@
// Update selection state
updateSelectionState();
} catch (err) {
error = err.message || "Failed to load dashboards";
error = err.message || $t.dashboard?.load_failed ;
console.error("[DashboardHub][Coherence:Failed]", err);
} finally {
isLoading = false;
@@ -318,7 +318,11 @@
}
} catch (err) {
console.error("[DashboardHub][Coherence:Failed] Validation failed:", err);
alert("Failed to start validation: " + (err.message || "Unknown error"));
alert(
($t.dashboard?.validation_start_failed ) +
": " +
(err.message || $t.dashboard?.unknown_error ),
);
} finally {
validatingIds.delete(dashboard.id);
validatingIds = new Set(validatingIds);
@@ -399,7 +403,9 @@
if (isSubmittingMigrate) return;
if (selectedIds.size === 0) return;
if (!targetEnvId) {
alert("Please select a target environment");
alert(
$t.dashboard?.target_env_required ,
);
return;
}
@@ -432,7 +438,7 @@
}
} catch (err) {
console.error("[DashboardHub][Coherence:Failed]", err);
alert("Failed to create migration task");
alert($t.dashboard?.migration_task_failed );
} finally {
isSubmittingMigrate = false;
}
@@ -469,7 +475,7 @@
}
} catch (err) {
console.error("[DashboardHub][Coherence:Failed]", err);
alert("Failed to create backup task");
alert($t.dashboard?.backup_task_failed );
} finally {
isSubmittingBackup = false;
}
@@ -568,7 +574,7 @@
<!-- Header -->
<div class="flex items-center justify-between">
<h1 class="text-2xl font-bold text-gray-900">
{$t.nav?.dashboard || "Dashboards"}
{$t.nav?.dashboards }
</h1>
<div class="flex items-center space-x-4">
<select
@@ -584,7 +590,7 @@
class="inline-flex items-center justify-center rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-primary-hover"
on:click={loadDashboards}
>
{$t.common?.refresh || "Refresh"}
{$t.common?.refresh }
</button>
</div>
</div>
@@ -599,7 +605,7 @@
class="px-4 py-2 bg-destructive text-white rounded hover:bg-destructive-hover transition-colors"
on:click={loadDashboards}
>
{$t.common?.retry || "Retry"}
{$t.common?.retry }
</button>
</div>
{/if}
@@ -641,7 +647,7 @@
>
<path d="M3 3h18v18H3V3zm16 16V5H5v14h14z" />
</svg>
<p>{$t.dashboards?.empty || "No dashboards found"}</p>
<p>{$t.dashboard?.empty }</p>
</div>
{:else}
<!-- Toolbar -->
@@ -652,18 +658,24 @@
on:click={handleSelectAll}
disabled={total === 0}
>
{isAllSelected ? "Deselect All" : "Select All"}
{isAllSelected
? $t.dashboard?.deselect_all
: $t.dashboard?.select_all }
</button>
<button
class="px-3 py-1 text-sm border border-gray-300 rounded hover:bg-gray-100 transition-colors"
on:click={handleSelectVisible}
disabled={dashboards.length === 0}
>
{isAllVisibleSelected ? "Deselect Visible" : "Select Visible"}
{isAllVisibleSelected
? $t.dashboard?.deselect_visible
: $t.dashboard?.select_visible }
</button>
{#if selectedIds.size > 0}
<span class="text-sm text-gray-600">
{selectedIds.size} selected
{(
$t.dashboard?.selected_count
).replace("{count}", String(selectedIds.size))}
</span>
{/if}
</div>
@@ -671,7 +683,7 @@
<input
type="text"
class="px-4 py-2 border border-gray-300 rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Search dashboards..."
placeholder={$t.dashboard?.search }
on:input={handleSearch}
value={searchQuery}
/>
@@ -686,14 +698,14 @@
>
<div class="col-span-1"></div>
<div class="col-span-3 font-medium text-gray-900">
{$t.dashboards?.title || "Title"}
{$t.dashboard?.title }
</div>
<div class="col-span-2">
{$t.dashboards?.git_status || "Git Status"}
{$t.dashboard?.git_status }
</div>
<div class="col-span-3">{$t.dashboards?.last_task || "Last Task"}</div>
<div class="col-span-3">{$t.dashboard?.last_task }</div>
<div class="col-span-3 flex items-center">
{$t.dashboards?.actions || "Actions"}
{$t.dashboard?.actions }
</div>
</div>
@@ -716,7 +728,7 @@
<button
class="text-left text-blue-700 hover:text-blue-900 hover:underline transition-colors"
on:click={() => navigateToDashboardDetail(dashboard.id)}
title="Open dashboard overview"
title={$t.dashboard?.open_overview }
>
{dashboard.title}
</button>
@@ -729,9 +741,9 @@
class="status-badge {getStatusBadgeClass(dashboard.gitStatus)}"
>
{#if dashboard.gitStatus.toLowerCase() === "ok"}
{$t.dashboards?.status_synced || "Synced"}
{$t.dashboard?.status_synced }
{:else if dashboard.gitStatus.toLowerCase() === "diff"}
! {$t.dashboards?.status_diff || "Diff"}
! {$t.dashboard?.status_diff }
{/if}
</span>
{:else}
@@ -750,18 +762,18 @@
handleTaskStatusClick(dashboard)}
role="button"
tabindex="0"
aria-label={$t.dashboards?.view_task || "View task"}
aria-label={$t.dashboard?.view_task }
>
{@html getTaskStatusIcon(dashboard.lastTask.status)}
<span class="ml-1">
{#if dashboard.lastTask.status.toLowerCase() === "running"}
{$t.dashboards?.task_running || "Running..."}
{$t.dashboard?.task_running }
{:else if dashboard.lastTask.status.toLowerCase() === "success"}
{$t.dashboards?.task_done || "Done"}
{$t.dashboard?.task_done }
{:else if dashboard.lastTask.status.toLowerCase() === "error"}
{$t.dashboards?.task_failed || "Failed"}
{$t.dashboard?.task_failed }
{:else if dashboard.lastTask.status.toLowerCase() === "waiting_input"}
{$t.dashboards?.task_waiting || "Waiting"}
{$t.dashboard?.task_waiting }
{/if}
</span>
</div>
@@ -776,7 +788,7 @@
<button
class="p-2 rounded border border-gray-200 hover:bg-gray-50 hover:border-gray-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
on:click={() => handleAction(dashboard, "migrate")}
title={$t.dashboards?.action_migrate || "Migrate"}
title={$t.dashboard?.action_migrate }
>
<svg
width="14"
@@ -793,7 +805,7 @@
class="p-2 rounded border border-gray-200 hover:bg-gray-50 hover:border-gray-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
on:click={() => handleValidate(dashboard)}
disabled={validatingIds.has(dashboard.id)}
title={$t.dashboards?.action_validate || "Validate"}
title={$t.dashboard?.action_validate }
>
{#if validatingIds.has(dashboard.id)}
<svg
@@ -830,7 +842,7 @@
<button
class="p-2 rounded border border-gray-200 hover:bg-gray-50 hover:border-gray-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
on:click={() => handleAction(dashboard, "backup")}
title={$t.dashboards?.action_backup || "Backup"}
title={$t.dashboard?.action_backup }
>
<svg
width="14"
@@ -857,10 +869,10 @@
class="flex items-center justify-between px-4 py-3 bg-gray-50 border-t border-gray-200"
>
<div class="text-sm text-gray-600">
Showing {(currentPage - 1) * pageSize + 1}-{Math.min(
currentPage * pageSize,
total,
)} of {total}
{($t.dashboard?.showing )
.replace("{start}", String((currentPage - 1) * pageSize + 1))
.replace("{end}", String(Math.min(currentPage * pageSize, total)))
.replace("{total}", String(total))}
</div>
<div class="flex items-center gap-1">
<button
@@ -868,14 +880,14 @@
on:click={() => handlePageChange(1)}
disabled={currentPage === 1}
>
First
{$t.common?.first }
</button>
<button
class="px-3 py-1 border border-gray-300 rounded hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed min-w-[32px]"
on:click={() => handlePageChange(currentPage - 1)}
disabled={currentPage === 1}
>
Previous
{$t.dashboard?.previous }
</button>
{#each getPaginationRange(currentPage, totalPages) as pageNum}
{#if pageNum === "..."}
@@ -894,14 +906,14 @@
on:click={() => handlePageChange(currentPage + 1)}
disabled={currentPage === totalPages}
>
Next
{$t.dashboard?.next }
</button>
<button
class="px-3 py-1 border border-gray-300 rounded hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed min-w-[32px]"
on:click={() => handlePageChange(totalPages)}
disabled={currentPage === totalPages}
>
Last
{$t.common?.last }
</button>
</div>
<div>
@@ -910,11 +922,36 @@
value={pageSize}
on:change={handlePageSizeChange}
>
<option value={5}>5 per page</option>
<option value={10}>10 per page</option>
<option value={25}>25 per page</option>
<option value={50}>50 per page</option>
<option value={100}>100 per page</option>
<option value={5}>
{($t.dashboard?.per_page_option ).replace(
"{count}",
"5",
)}
</option>
<option value={10}>
{($t.dashboard?.per_page_option ).replace(
"{count}",
"10",
)}
</option>
<option value={25}>
{($t.dashboard?.per_page_option ).replace(
"{count}",
"25",
)}
</option>
<option value={50}>
{($t.dashboard?.per_page_option ).replace(
"{count}",
"50",
)}
</option>
<option value={100}>
{($t.dashboard?.per_page_option ).replace(
"{count}",
"100",
)}
</option>
</select>
</div>
</div>
@@ -928,7 +965,9 @@
<div class="flex items-center justify-between max-w-7xl mx-auto">
<div class="flex items-center gap-4">
<span class="font-medium">
{selectedIds.size} selected
{(
$t.dashboard?.selected_count
).replace("{count}", String(selectedIds.size))}
</span>
</div>
<div class="flex gap-3">
@@ -944,19 +983,19 @@
isEditingMappings = false;
}}
>
Migrate
{$t.dashboard?.bulk_migrate }
</button>
<button
class="px-3 py-1 text-sm bg-primary text-white border-primary rounded hover:bg-primary-hover transition-colors"
on:click={() => (showBackupModal = true)}
>
Backup
{$t.dashboard?.bulk_backup }
</button>
<button
class="px-3 py-1 text-sm border border-gray-300 rounded hover:bg-gray-100 transition-colors"
on:click={() => selectedIds.clear()}
>
Cancel
{$t.common?.cancel }
</button>
</div>
</div>
@@ -985,12 +1024,14 @@
class="px-6 py-4 border-b border-gray-200 flex items-center justify-between relative"
>
<h2 id="migrate-modal-title" class="text-xl font-bold">
Migrate {selectedIds.size} Dashboards
{(
$t.dashboard?.migrate_modal_title
).replace("{count}", String(selectedIds.size))}
</h2>
<button
on:click={() => (showMigrateModal = false)}
class="absolute top-4 right-4 p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-full transition-all"
aria-label="Close modal"
aria-label={$t.common?.close_modal }
>
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -1015,14 +1056,14 @@
<label
for="source-env"
class="block text-sm font-medium text-gray-500 mb-1"
>Source Environment</label
>{$t.migration?.source_env }</label
>
<div
id="source-env"
class="px-4 py-2 bg-gray-50 border border-gray-200 rounded-lg text-gray-700 font-medium"
>
{environments.find((e) => e.id === selectedEnv)?.name ||
selectedEnv} (read-only)
selectedEnv} {$t.dashboard?.read_only }
</div>
</div>
@@ -1031,7 +1072,7 @@
<label
for="target-env"
class="block text-sm font-medium text-gray-700 mb-2"
>Target Environment</label
>{$t.migration?.target_env }</label
>
<select
id="target-env"
@@ -1039,7 +1080,10 @@
bind:value={targetEnvId}
on:change={handleTargetEnvChange}
>
<option value="">Select target environment...</option>
<option value="">
{$t.dashboard?.target_env_placeholder ||
"Select target environment..."}
</option>
{#each environments.filter((e) => e.id !== selectedEnv) as env}
<option value={env.id}>{env.name}</option>
{/each}
@@ -1052,7 +1096,7 @@
<label
for="use-db-mappings"
class="block text-sm font-medium text-gray-700"
>Database Mappings</label
>{$t.migration?.database_mappings }</label
>
<label class="relative inline-flex items-center cursor-pointer">
<input
@@ -1065,7 +1109,9 @@
class="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-blue-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-blue-600"
></div>
<span class="ml-2 text-xs text-gray-500"
>{useDbMappings ? "On" : "Off"}</span
>{useDbMappings
? $t.common?.on
: $t.common?.off }</span
>
</label>
</div>
@@ -1077,7 +1123,9 @@
class="text-sm text-blue-600 hover:underline"
on:click={() => (isEditingMappings = !isEditingMappings)}
>
{isEditingMappings ? "View Summary" : "Edit Mappings"}
{isEditingMappings
? $t.dashboard?.view_summary
: $t.dashboard?.edit_mappings }
</button>
{/if}
</div>
@@ -1105,13 +1153,13 @@
<thead class="bg-gray-50 border-b border-gray-200">
<tr>
<th class="px-4 py-2 font-semibold text-gray-700"
>Source Database</th
>{$t.dashboard?.source_database }</th
>
<th class="px-4 py-2 font-semibold text-gray-700"
>Target Database</th
>{$t.dashboard?.target_database }</th
>
<th class="px-4 py-2 font-semibold text-gray-700"
>Match %</th
>{$t.dashboard?.match_percent }</th
>
</tr>
</thead>
@@ -1130,7 +1178,9 @@
dbMappings[mapping.source_db_uuid],
)?.database_name || mapping.target_db}
{:else}
<span class="text-red-500">Not mapped</span>
<span class="text-red-500"
>{$t.dashboard?.not_mapped }</span
>
{/if}
</td>
<td class="px-4 py-2">
@@ -1152,8 +1202,10 @@
class="px-4 py-4 text-center text-gray-500 italic"
>
{targetEnvId
? "No databases found to map"
: "Select target environment to see mappings"}
? $t.dashboard?.no_databases_to_map ||
"No databases found to map"
: $t.dashboard?.select_target_for_mappings ||
"Select target environment to see mappings"}
</td>
</tr>
{/if}
@@ -1163,8 +1215,8 @@
{/if}
{:else}
<p class="text-xs text-gray-400 italic">
Database mapping is disabled. Dashboards will be imported with
original database references.
{$t.dashboard?.mapping_disabled_hint ||
"Database mapping is disabled. Dashboards will be imported with original database references."}
</p>
{/if}
</div>
@@ -1179,12 +1231,12 @@
/>
<div>
<span class="text-sm font-medium text-gray-900"
>Исправить связи кросс-фильтрации</span
>{$t.dashboard?.fix_cross_filters_title ||
"Fix cross-filter bindings"}</span
>
<p class="text-xs text-gray-500 mt-0.5">
Автоматически перепривязать ID чартов и датасетов в
кросс-фильтрах к ID целевого окружения. Рекомендуется при
миграции дашбордов с кросс-фильтрами.
{$t.dashboard?.fix_cross_filters_hint ||
"Automatically remap chart and dataset IDs in cross-filters to target environment IDs. Recommended when migrating dashboards with cross-filters."}
</p>
</div>
</label>
@@ -1193,7 +1245,7 @@
<!-- Selected Dashboards List -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2"
>Selected Dashboards</label
>{$t.dashboard?.selected_dashboards }</label
>
<div
class="max-h-40 overflow-y-auto border border-gray-200 rounded-lg bg-gray-50 p-2"
@@ -1216,7 +1268,8 @@
<button
class="px-3 py-1 text-sm border border-gray-300 rounded hover:bg-gray-100 transition-colors"
on:click={() => (showMigrateModal = false)}
disabled={isSubmittingMigrate}>Cancel</button
disabled={isSubmittingMigrate}
>{$t.common?.cancel }</button
>
<button
class="px-3 py-1 text-sm bg-primary text-white border-primary rounded hover:bg-primary-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
@@ -1225,7 +1278,9 @@
selectedIds.size === 0 ||
isSubmittingMigrate}
>
{isSubmittingMigrate ? "Starting..." : "Start Migration"}
{isSubmittingMigrate
? $t.dashboard?.starting
: $t.migration?.start }
</button>
</div>
</div>
@@ -1253,12 +1308,14 @@
class="px-6 py-4 border-b border-gray-200 flex items-center justify-between relative"
>
<h2 id="backup-modal-title" class="text-xl font-bold">
Backup {selectedIds.size} Dashboards
{(
$t.dashboard?.backup_modal_title
).replace("{count}", String(selectedIds.size))}
</h2>
<button
on:click={() => (showBackupModal = false)}
class="absolute top-4 right-4 p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-full transition-all"
aria-label="Close modal"
aria-label={$t.common?.close_modal }
>
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -1283,21 +1340,21 @@
<label
for="backup-env"
class="block text-sm font-medium text-gray-500 mb-1"
>Environment</label
>{$t.dashboard?.environment }</label
>
<div
id="backup-env"
class="px-4 py-2 bg-gray-50 border border-gray-200 rounded-lg text-gray-700 font-medium"
>
{environments.find((e) => e.id === selectedEnv)?.name ||
selectedEnv} (read-only)
selectedEnv} {$t.dashboard?.read_only }
</div>
</div>
<!-- Selected Dashboards List -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2"
>Selected Dashboards</label
>{$t.dashboard?.selected_dashboards }</label
>
<div
class="max-h-40 overflow-y-auto border border-gray-200 rounded-lg bg-gray-50 p-2"
@@ -1320,7 +1377,7 @@
<label
id="backup-schedule-label"
class="block text-sm font-medium text-gray-700 mb-2"
>Schedule</label
>{$t.dashboard?.schedule }</label
>
<div class="space-y-3" aria-labelledby="backup-schedule-label">
<label class="flex items-center">
@@ -1331,7 +1388,9 @@
bind:group={backupSchedule}
class="mr-2"
/>
<span class="text-sm">One-time backup</span>
<span class="text-sm"
>{$t.dashboard?.one_time_backup }</span
>
</label>
<label class="flex items-center">
<input
@@ -1341,24 +1400,27 @@
bind:group={backupSchedule}
class="mr-2"
/>
<span class="text-sm">Schedule backup</span>
<span class="text-sm"
>{$t.dashboard?.schedule_backup }</span
>
</label>
{#if backupSchedule !== ""}
<div class="ml-6">
<label
for="cron-expression"
class="block text-xs text-gray-500 mb-1"
>Cron expression</label
>{$t.dashboard?.cron_expression }</label
>
<input
id="cron-expression"
type="text"
class="search-input w-full text-sm"
placeholder="0 2 * * * (daily at 2 AM)"
placeholder={$t.dashboard?.cron_placeholder ||
"0 2 * * * (daily at 2 AM)"}
bind:value={backupSchedule}
/>
<button class="text-xs text-blue-600 hover:underline mt-1"
>Help with cron syntax</button
>{$t.dashboard?.cron_help }</button
>
</div>
{/if}
@@ -1370,14 +1432,17 @@
<button
class="px-3 py-1 text-sm border border-gray-300 rounded hover:bg-gray-100 transition-colors"
on:click={() => (showBackupModal = false)}
disabled={isSubmittingBackup}>Cancel</button
disabled={isSubmittingBackup}
>{$t.common?.cancel }</button
>
<button
class="px-3 py-1 text-sm bg-primary text-white border-primary rounded hover:bg-primary-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
on:click={handleBulkBackup}
disabled={selectedIds.size === 0 || isSubmittingBackup}
>
{isSubmittingBackup ? "Starting..." : "Start Backup"}
{isSubmittingBackup
? $t.dashboard?.starting
: $t.dashboard?.start_backup }
</button>
</div>
</div>

View File

@@ -28,7 +28,7 @@
async function loadDashboardDetail() {
if (!dashboardId || !envId) {
error = "Missing dashboard ID or environment ID";
error = $t.dashboard?.missing_context ;
isLoading = false;
return;
}
@@ -38,7 +38,7 @@
try {
dashboard = await api.getDashboardDetail(envId, dashboardId);
} catch (err) {
error = err.message || "Failed to load dashboard details";
error = err.message || $t.dashboard?.load_detail_failed ;
console.error("[DashboardDetail][Coherence:Failed]", err);
} finally {
isLoading = false;
@@ -69,20 +69,20 @@
on:click={goBack}
>
<Icon name="chevronLeft" size={16} />
{$t.common?.back || "Back to Dashboards"}
{$t.common?.back }
</button>
<h1 class="mt-2 text-2xl font-bold text-slate-900">
{dashboard?.title || "Dashboard Overview"}
{dashboard?.title || ($t.dashboard?.overview )}
</h1>
<p class="mt-1 text-sm text-slate-500">
ID: {dashboardId}{#if dashboard?.slug}{dashboard.slug}{/if}
{$t.common?.id }: {dashboardId}{#if dashboard?.slug}{dashboard.slug}{/if}
</p>
</div>
<button
class="inline-flex items-center justify-center rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-primary-hover"
on:click={loadDashboardDetail}
>
{$t.common?.refresh || "Refresh"}
{$t.common?.refresh }
</button>
</div>
@@ -90,7 +90,7 @@
<div class="flex items-center justify-between rounded-lg border border-red-300 bg-red-50 px-4 py-3 text-red-700">
<span>{error}</span>
<button class="rounded bg-red-600 px-3 py-1.5 text-white hover:bg-red-700" on:click={loadDashboardDetail}>
{$t.common?.retry || "Retry"}
{$t.common?.retry }
</button>
</div>
{/if}
@@ -105,39 +105,39 @@
{:else if dashboard}
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
<div class="rounded-xl border border-slate-200 bg-white p-4">
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">Last modified</p>
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">{$t.dashboard?.last_modified }</p>
<p class="mt-2 text-lg font-semibold text-slate-900">{formatDate(dashboard.last_modified)}</p>
</div>
<div class="rounded-xl border border-slate-200 bg-white p-4">
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">Charts</p>
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">{$t.dashboard?.charts }</p>
<p class="mt-2 text-lg font-semibold text-slate-900">{dashboard.chart_count || 0}</p>
</div>
<div class="rounded-xl border border-slate-200 bg-white p-4">
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">Datasets</p>
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">{$t.nav?.datasets }</p>
<p class="mt-2 text-lg font-semibold text-slate-900">{dashboard.dataset_count || 0}</p>
</div>
</div>
{#if dashboard.description}
<div class="rounded-xl border border-slate-200 bg-white p-4">
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">Overview</h2>
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">{$t.dashboard?.overview }</h2>
<p class="mt-2 text-sm text-slate-700">{dashboard.description}</p>
</div>
{/if}
<div class="rounded-xl border border-slate-200 bg-white overflow-hidden">
<div class="border-b border-slate-200 px-4 py-3">
<h2 class="text-lg font-semibold text-slate-900">Charts</h2>
<h2 class="text-lg font-semibold text-slate-900">{$t.dashboard?.charts }</h2>
</div>
{#if dashboard.charts && dashboard.charts.length > 0}
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-slate-200 text-sm">
<thead class="bg-slate-50">
<tr>
<th class="px-4 py-2 text-left font-semibold text-slate-600">Chart</th>
<th class="px-4 py-2 text-left font-semibold text-slate-600">Dataset</th>
<th class="px-4 py-2 text-left font-semibold text-slate-600">Overview</th>
<th class="px-4 py-2 text-left font-semibold text-slate-600">Last modified</th>
<th class="px-4 py-2 text-left font-semibold text-slate-600">{$t.settings?.type_chart }</th>
<th class="px-4 py-2 text-left font-semibold text-slate-600">{$t.nav?.datasets }</th>
<th class="px-4 py-2 text-left font-semibold text-slate-600">{$t.dashboard?.overview }</th>
<th class="px-4 py-2 text-left font-semibold text-slate-600">{$t.dashboard?.last_modified }</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
@@ -152,9 +152,9 @@
<button
class="inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-blue-700 hover:bg-blue-50 hover:text-blue-800"
on:click={() => openDataset(chart.dataset_id)}
title={`Open dataset ${chart.dataset_id}`}
title={`${$t.datasets?.table_name } ${chart.dataset_id}`}
>
Dataset {chart.dataset_id}
{$t.nav?.datasets } {chart.dataset_id}
<Icon name="chevronRight" size={12} className="text-blue-500" />
</button>
{:else}
@@ -169,13 +169,13 @@
</table>
</div>
{:else}
<div class="px-4 py-8 text-sm text-slate-500">No charts found for this dashboard.</div>
<div class="px-4 py-8 text-sm text-slate-500">{$t.dashboard?.no_charts }</div>
{/if}
</div>
<div class="rounded-xl border border-slate-200 bg-white overflow-hidden">
<div class="border-b border-slate-200 px-4 py-3">
<h2 class="text-lg font-semibold text-slate-900">Datasets</h2>
<h2 class="text-lg font-semibold text-slate-900">{$t.nav?.datasets }</h2>
</div>
{#if dashboard.datasets && dashboard.datasets.length > 0}
<div class="divide-y divide-slate-100">
@@ -199,7 +199,7 @@
{/each}
</div>
{:else}
<div class="px-4 py-8 text-sm text-slate-500">No datasets found for this dashboard.</div>
<div class="px-4 py-8 text-sm text-slate-500">{$t.dashboard?.no_datasets }</div>
{/if}
</div>
{/if}