From df8c06d3135910ea870984447c482610cd8b4edd Mon Sep 17 00:00:00 2001 From: Ruslan Bakiev <572431+veikab@users.noreply.github.com> Date: Mon, 23 Feb 2026 15:28:46 +0700 Subject: [PATCH] refactor(review): rollback-only flow and compact change summary --- .../components/workspace/CrmWorkspaceApp.vue | 96 +++++-------------- .../workspace/pilot/CrmPilotSidebar.vue | 40 +++----- .../review/CrmChangeReviewOverlay.vue | 47 ++++----- 3 files changed, 51 insertions(+), 132 deletions(-) diff --git a/frontend/app/components/workspace/CrmWorkspaceApp.vue b/frontend/app/components/workspace/CrmWorkspaceApp.vue index 279df36..02b45c5 100644 --- a/frontend/app/components/workspace/CrmWorkspaceApp.vue +++ b/frontend/app/components/workspace/CrmWorkspaceApp.vue @@ -1624,7 +1624,6 @@ watchEffect(() => { const changeActionBusy = ref(false); const activeChangeSetId = ref(""); const activeChangeStep = ref(0); -const changeSelectionByItemId = ref>({}); const focusedCalendarEventId = ref(""); const uiPathSyncLocked = ref(false); let popstateHandler: (() => void) | null = null; @@ -1672,11 +1671,7 @@ const activeChangeItem = computed(() => { const reviewActive = computed(() => Boolean(activeChangeSetId.value.trim() && activeChangeItems.value.length > 0)); const activeChangeStepNumber = computed(() => activeChangeIndex.value + 1); -const activeChangeApproved = computed(() => { - const item = activeChangeItem.value; - if (!item || item.rolledBack) return true; - return changeSelectionByItemId.value[item.id] !== false; -}); +const rollbackableCount = computed(() => activeChangeItems.value.filter((item) => !item.rolledBack).length); const activeReviewCalendarEventId = computed(() => { const item = activeChangeItem.value; if (!item || item.entity !== "calendar_event" || !item.entityId) return ""; @@ -1706,21 +1701,6 @@ const activeReviewContactDiff = computed(() => { after: normalizeChangeText(item.after), }; }); -const selectedRollbackItemIds = computed(() => - activeChangeItems.value - .filter((item) => !item.rolledBack && changeSelectionByItemId.value[item.id] === false) - .map((item) => item.id), -); -const selectedRollbackCount = computed(() => selectedRollbackItemIds.value.length); - -function setReviewApprovalForAll(approved: boolean) { - const next: Record = {}; - for (const item of activeChangeItems.value) { - next[item.id] = item.rolledBack ? true : approved; - } - changeSelectionByItemId.value = next; -} - function normalizeChangeText(raw: string | null | undefined) { const text = String(raw ?? "").trim(); if (!text) return ""; @@ -1753,32 +1733,6 @@ function describeChangeAction(action: string) { return action || "changed"; } -function isReviewItemApproved(item: PilotChangeItem | null | undefined) { - if (!item || item.rolledBack) return true; - return changeSelectionByItemId.value[item.id] !== false; -} - -function setReviewItemApproval(itemId: string, approved: boolean) { - const target = activeChangeItems.value.find((item) => item.id === itemId); - if (!target || target.rolledBack) return; - changeSelectionByItemId.value = { - ...changeSelectionByItemId.value, - [itemId]: approved, - }; -} - -function onReviewItemApprovalInput(itemId: string, event: Event) { - const input = event.target as HTMLInputElement | null; - setReviewItemApproval(itemId, Boolean(input?.checked)); -} - -function onActiveReviewApprovalInput(event: Event) { - const item = activeChangeItem.value; - if (!item) return; - const input = event.target as HTMLInputElement | null; - setReviewItemApproval(item.id, Boolean(input?.checked)); -} - function calendarCursorToken(date: Date) { const y = date.getFullYear(); const m = String(date.getMonth() + 1).padStart(2, "0"); @@ -1880,19 +1834,6 @@ function syncPathFromUi(push = false) { } } -function ensureChangeSelectionSeeded(message: PilotMessage | null | undefined) { - if (!message?.changeItems?.length) { - changeSelectionByItemId.value = {}; - return; - } - const next: Record = {}; - for (const item of message.changeItems) { - const prev = changeSelectionByItemId.value[item.id]; - next[item.id] = typeof prev === "boolean" ? prev : true; - } - changeSelectionByItemId.value = next; -} - function setPeopleLeftMode(mode: PeopleLeftMode, push = false) { selectedTab.value = "communications"; peopleLeftMode.value = mode; @@ -1906,7 +1847,6 @@ function openChangeReview(changeSetId: string, step = 0, push = true) { activeChangeSetId.value = targetId; const items = activeChangeMessage.value?.changeItems ?? []; activeChangeStep.value = items.length ? Math.max(0, Math.min(step, items.length - 1)) : 0; - ensureChangeSelectionSeeded(activeChangeMessage.value); applyReviewStepToUi(push); } @@ -1922,7 +1862,6 @@ function applyPathToUi(pathname: string, search = "") { } else { activeChangeSetId.value = ""; activeChangeStep.value = 0; - changeSelectionByItemId.value = {}; } const calendarEventMatch = path.match(/^\/calendar\/event\/([^/]+)\/?$/i); @@ -2074,7 +2013,7 @@ async function rollbackLatestChangeSet() { async function rollbackSelectedChangeItems() { const targetChangeSetId = activeChangeMessage.value?.changeSetId?.trim() || activeChangeSetId.value.trim(); - const itemIds = selectedRollbackItemIds.value; + const itemIds = activeChangeItems.value.filter((item) => !item.rolledBack).map((item) => item.id); if (changeActionBusy.value || !targetChangeSetId || itemIds.length === 0) return; changeActionBusy.value = true; @@ -2084,7 +2023,23 @@ async function rollbackSelectedChangeItems() { itemIds, }); await Promise.all([loadPilotMessages(), refreshCrmData(), loadChatConversations()]); - ensureChangeSelectionSeeded(activeChangeMessage.value); + } finally { + changeActionBusy.value = false; + } +} + +async function rollbackChangeItemById(itemId: string) { + const item = activeChangeItems.value.find((entry) => entry.id === itemId); + const targetChangeSetId = activeChangeMessage.value?.changeSetId?.trim() || activeChangeSetId.value.trim(); + if (!item || item.rolledBack || !targetChangeSetId || changeActionBusy.value) return; + + changeActionBusy.value = true; + try { + await gqlFetch<{ rollbackChangeSetItems: { ok: boolean } }>(rollbackChangeSetItemsMutation, { + changeSetId: targetChangeSetId, + itemIds: [itemId], + }); + await Promise.all([loadPilotMessages(), refreshCrmData(), loadChatConversations()]); } finally { changeActionBusy.value = false; } @@ -2206,7 +2161,6 @@ function applyReviewStepToUi(push = false) { function finishReview(push = true) { activeChangeSetId.value = ""; activeChangeStep.value = 0; - changeSelectionByItemId.value = {}; syncPathFromUi(push); } @@ -2214,7 +2168,6 @@ watch( () => activeChangeMessage.value?.changeSetId, () => { if (!activeChangeSetId.value.trim()) return; - ensureChangeSelectionSeeded(activeChangeMessage.value); const maxIndex = Math.max(0, (activeChangeItems.value.length || 1) - 1); if (activeChangeStep.value > maxIndex) activeChangeStep.value = maxIndex; applyReviewStepToUi(false); @@ -5506,22 +5459,17 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected") :active-change-step-number="activeChangeStepNumber" :active-change-items="activeChangeItems" :active-change-item="activeChangeItem" - :active-change-approved="activeChangeApproved" :active-change-index="activeChangeIndex" - :selected-rollback-count="selectedRollbackCount" + :rollbackable-count="rollbackableCount" :change-action-busy="changeActionBusy" :describe-change-entity="describeChangeEntity" :describe-change-action="describeChangeAction" - :is-review-item-approved="isReviewItemApproved" @close="finishReview(true)" - @active-approval-change="onActiveReviewApprovalInput" - @item-approval-change="onReviewItemApprovalInput($event.itemId, $event.event)" @open-item-target="openChangeItemTarget" + @rollback-item="rollbackChangeItemById" + @rollback-all="rollbackSelectedChangeItems" @prev-step="goToPreviousChangeStep" @next-step="goToNextChangeStep" - @approve-all="setReviewApprovalForAll(true)" - @mark-all-rollback="setReviewApprovalForAll(false)" - @rollback-selected="rollbackSelectedChangeItems" @done="finishReview(true)" /> diff --git a/frontend/app/components/workspace/pilot/CrmPilotSidebar.vue b/frontend/app/components/workspace/pilot/CrmPilotSidebar.vue index bb949b5..cc109ca 100644 --- a/frontend/app/components/workspace/pilot/CrmPilotSidebar.vue +++ b/frontend/app/components/workspace/pilot/CrmPilotSidebar.vue @@ -128,33 +128,19 @@ defineProps<{

{{ message.changeSummary || "Technical change summary" }}

-
- - - - - - - - - - - - - - - - - - - - - - - - - -
MetricValue
Total changes{{ message.changeItems?.length || 0 }}
Created{{ summarizeChangeActions(message.changeItems).created }}
Updated{{ summarizeChangeActions(message.changeItems).updated }}
Archived{{ summarizeChangeActions(message.changeItems).deleted }}
+
+ + total {{ message.changeItems?.length || 0 }} + + + created {{ summarizeChangeActions(message.changeItems).created }} + + + updated {{ summarizeChangeActions(message.changeItems).updated }} + + + archived {{ summarizeChangeActions(message.changeItems).deleted }} +
string; describeChangeAction: (action: string) => string; - isReviewItemApproved: (item: ChangeItem | null | undefined) => boolean; }>(); const emit = defineEmits<{ (e: "close"): void; - (e: "active-approval-change", event: Event): void; - (e: "item-approval-change", payload: { itemId: string; event: Event }): void; (e: "open-item-target", item: ChangeItem): void; + (e: "rollback-item", itemId: string): void; + (e: "rollback-all"): void; (e: "prev-step"): void; (e: "next-step"): void; - (e: "approve-all"): void; - (e: "mark-all-rollback"): void; - (e: "rollback-selected"): void; (e: "done"): void; }>(); @@ -58,23 +53,13 @@ const emit = defineEmits<{ {{ props.describeChangeEntity(props.activeChangeItem.entity) }} {{ props.describeChangeAction(props.activeChangeItem.action) }}

-
- + Rollback + + Rolled back
@@ -116,19 +103,17 @@ const emit = defineEmits<{

- Rollback marked: {{ props.selectedRollbackCount }} + Rollback available: {{ props.rollbackableCount }}

- -