diff --git a/frontend/src/lib/components/dashboard/RepositoryDashboardGrid.svelte b/frontend/src/lib/components/dashboard/RepositoryDashboardGrid.svelte index 20eb5e7f2..713d24592 100644 --- a/frontend/src/lib/components/dashboard/RepositoryDashboardGrid.svelte +++ b/frontend/src/lib/components/dashboard/RepositoryDashboardGrid.svelte @@ -30,6 +30,8 @@ statusMode = "dashboard" as "dashboard" | "repository", envId = null as string | null, repositoriesOnly = false, + /** Called when bulk-delete removes dashboards from the list — parent owns the data. */ + onDashboardsChanged = (_next: DashboardMetadata[]) => {}, } = $props(); // [/SECTION] @@ -43,8 +45,14 @@ let gitDashboardTitle = $state(""); let repositoryStatusByDashboardId = $state>({}); let repositoryStatusRequestId = $state(0); + let repositoryStatusFetching = $state(false); let bulkActionRunning = $state(false); let showBulkDeleteConfirm = $state(false); + // Bulk-commit dialog (replaces native prompt()) + let showBulkCommitDialog = $state(false); + let bulkCommitMessage = $state(""); + // Bulk-action confirmation dialogs (sync/pull/push) + let bulkConfirmAction = $state(null); // [/SECTION] // ── Column definitions ───────────────────────────────────────── @@ -108,9 +116,16 @@ repositoryStatusByDashboardId = {}; return; } - const requestId = ++repositoryStatusRequestId; - // Load statuses for all pre-filtered data (not just current page — DashboardDataGrid owns pagination) + // P2 #7: skip if a fetch is already in progress, or all IDs already have resolved statuses. + if (repositoryStatusFetching) return; const allIds = dashboards.map(d => d.id); + const allResolved = allIds.every(id => { + const tkn = repositoryStatusByDashboardId[id]; + return tkn !== undefined && tkn !== "loading"; + }); + if (allResolved) return; + const requestId = ++repositoryStatusRequestId; + repositoryStatusFetching = true; const missingIds = allIds.filter(id => { const tkn = repositoryStatusByDashboardId[id]; return tkn === undefined || tkn === "loading"; @@ -130,11 +145,12 @@ } catch { entries = missingIds.map((id) => [id, "error"] as const); } - if (requestId !== repositoryStatusRequestId) return; + if (requestId !== repositoryStatusRequestId) { repositoryStatusFetching = false; return; } repositoryStatusByDashboardId = { ...repositoryStatusByDashboardId, ...Object.fromEntries(entries), }; + repositoryStatusFetching = false; } // #endregion loadRepositoryStatuses:Function @@ -166,18 +182,31 @@ // #endregion runBulkGitAction:Function // #region handleBulkAction:Function [TYPE Function] - async function handleBulkSync() { await runBulkGitAction("sync", (id) => gitService.sync(id, null, envId)); } - async function handleBulkCommit() { - const msg = prompt($t.git?.commit_message); - if (!msg?.trim()) return; - await runBulkGitAction("commit", (id) => gitService.commit(id, msg.trim(), [], envId)); + async function handleBulkSync() { bulkConfirmAction = "sync"; } + async function handleBulkPull() { bulkConfirmAction = "pull"; } + async function handleBulkPush() { bulkConfirmAction = "push"; } + async function executeBulkConfirmed() { + const action = bulkConfirmAction; + bulkConfirmAction = null; + if (action === "sync") { await runBulkGitAction("sync", (id) => gitService.sync(id, null, envId)); } + else if (action === "pull") { await runBulkGitAction("pull", (id) => gitService.pull(id, envId)); } + else if (action === "push") { await runBulkGitAction("push", (id) => gitService.push(id, envId)); } + } + function openBulkCommitDialog() { + bulkCommitMessage = ""; + showBulkCommitDialog = true; + } + async function confirmBulkCommit() { + const msg = bulkCommitMessage.trim(); + if (!msg) return; + showBulkCommitDialog = false; + await runBulkGitAction("commit", (id) => gitService.commit(id, msg, [], envId)); } - async function handleBulkPull() { await runBulkGitAction("pull", (id) => gitService.pull(id, envId)); } - async function handleBulkPush() { await runBulkGitAction("push", (id) => gitService.push(id, envId)); } async function onConfirmBulkDelete() { const idsToDelete = [...selectedIds]; await runBulkGitAction("delete", (id) => gitService.deleteRepository(id, envId)); - dashboards = dashboards.filter((d) => !idsToDelete.includes(d.id)); + // P2 #9: do NOT mutate the prop directly — delegate to parent via callback. + onDashboardsChanged(dashboards.filter((d) => !idsToDelete.includes(d.id))); selectedIds = []; } // #endregion handleBulkAction:Function @@ -302,7 +331,7 @@ {$t.git?.bulk_sync} {#if !repositoriesOnly} - {/if} @@ -337,13 +366,47 @@ {}} /> + +{#if showBulkCommitDialog} +
{ if (e.target === e.currentTarget) showBulkCommitDialog = false; }} onkeydown={(e) => { if (e.key === 'Escape') showBulkCommitDialog = false; }} role="dialog" aria-modal="true" aria-label={$t.git?.bulk_commit_dialog?.title || 'Bulk commit'}> +
+

{$t.git?.bulk_commit_dialog?.title || 'Bulk commit'}

+

{$t.git?.bulk_commit_dialog?.message || 'Enter a commit message for the selected dashboards:'}

+ +
+ + +
+
+
+{/if} + + +{#if bulkConfirmAction} +
{ if (e.target === e.currentTarget) bulkConfirmAction = null; }} onkeydown={(e) => { if (e.key === 'Escape') bulkConfirmAction = null; }} role="dialog" aria-modal="true" aria-label={$t.git?.bulk_confirm?.[`${bulkConfirmAction}_title`] || bulkConfirmAction}> +
+

{$t.git?.bulk_confirm?.[`${bulkConfirmAction}_title`] || bulkConfirmAction}

+

{($t.git?.bulk_confirm?.[`${bulkConfirmAction}_message`] || '').replace('{count}', String(selectedIds.length))}

+
+ + +
+
+
+{/if} + diff --git a/frontend/src/lib/components/git/BranchSelector.svelte b/frontend/src/lib/components/git/BranchSelector.svelte index e4faf6461..e8d374b32 100644 --- a/frontend/src/lib/components/git/BranchSelector.svelte +++ b/frontend/src/lib/components/git/BranchSelector.svelte @@ -234,7 +234,7 @@ {/if} - diff --git a/frontend/src/lib/components/git/CommitHistory.svelte b/frontend/src/lib/components/git/CommitHistory.svelte index 985b0785d..e971be36e 100644 --- a/frontend/src/lib/components/git/CommitHistory.svelte +++ b/frontend/src/lib/components/git/CommitHistory.svelte @@ -171,7 +171,7 @@ {#if showRollbackConfirm} -
+
e.stopPropagation()} role="alertdialog" aria-modal="true" aria-label={($t.git?.rollback || 'Rollback') + ' ' + (rollbackConfirmHash?.substring(0, 7) || '')}>

diff --git a/frontend/src/lib/components/git/CommitModal.svelte b/frontend/src/lib/components/git/CommitModal.svelte index 7a1d4e683..7ceca2325 100644 --- a/frontend/src/lib/components/git/CommitModal.svelte +++ b/frontend/src/lib/components/git/CommitModal.svelte @@ -36,7 +36,7 @@ {#if show}
{#if show}