Fix live review navigation and in-UI highlight behavior

This commit is contained in:
Ruslan Bakiev
2026-02-21 10:08:22 +07:00
parent e2e2901076
commit edea7a0034

View File

@@ -1267,6 +1267,11 @@ const activeReviewDealId = computed(() => {
if (!item || item.entity !== "deal" || !item.entityId) return "";
return item.entityId;
});
const activeReviewMessageId = computed(() => {
const item = activeChangeItem.value;
if (!item || item.entity !== "message" || !item.entityId) return "";
return item.entityId;
});
const activeReviewContactDiff = computed(() => {
const item = activeChangeItem.value;
if (!item || item.entity !== "contact_note" || !item.entityId) return null;
@@ -1310,6 +1315,7 @@ function normalizeChangeText(raw: string | null | undefined) {
function describeChangeEntity(entity: string) {
if (entity === "contact_note") return "Contact summary";
if (entity === "calendar_event") return "Calendar event";
if (entity === "message") return "Message";
if (entity === "deal") return "Deal";
return entity || "Change";
}
@@ -1680,6 +1686,10 @@ function isReviewHighlightedDeal(dealId: string) {
return Boolean(reviewActive.value && activeReviewDealId.value && activeReviewDealId.value === dealId);
}
function isReviewHighlightedMessage(messageId: string) {
return Boolean(reviewActive.value && activeReviewMessageId.value && activeReviewMessageId.value === messageId);
}
function applyReviewStepToUi(push = false) {
const item = activeChangeItem.value;
if (!item) {
@@ -1729,6 +1739,18 @@ function applyReviewStepToUi(push = false) {
return;
}
if (item.entity === "message" && item.entityId) {
peopleLeftMode.value = "contacts";
peopleListMode.value = "contacts";
const message = commItems.value.find((entry) => entry.id === item.entityId);
if (message?.contact) {
openCommunicationThread(message.contact);
}
focusedCalendarEventId.value = "";
syncPathFromUi(push);
return;
}
peopleLeftMode.value = "contacts";
focusedCalendarEventId.value = "";
syncPathFromUi(push);
@@ -1825,6 +1847,11 @@ const calendarCursor = ref(new Date(new Date().getFullYear(), new Date().getMont
const selectedDateKey = ref(dayKey(new Date()));
const sortedEvents = computed(() => [...calendarEvents.value].sort((a, b) => a.start.localeCompare(b.start)));
const focusedCalendarEvent = computed(() => {
const id = focusedCalendarEventId.value.trim();
if (!id) return null;
return sortedEvents.value.find((event) => event.id === id) ?? null;
});
const eventsByDate = computed(() => {
const map = new Map<string, CalendarEvent[]>();
@@ -1876,6 +1903,21 @@ const monthCells = computed(() => {
});
});
function monthCellHasFocusedEvent(events: CalendarEvent[]) {
const id = focusedCalendarEventId.value.trim();
if (!id) return false;
return events.some((event) => event.id === id);
}
function monthCellEvents(events: CalendarEvent[]) {
const id = focusedCalendarEventId.value.trim();
if (!id) return events.slice(0, 2);
const focused = events.find((event) => event.id === id);
if (!focused) return events.slice(0, 2);
const rest = events.filter((event) => event.id !== id).slice(0, 1);
return [focused, ...rest];
}
const weekDays = computed(() => {
const base = new Date(`${selectedDateKey.value}T00:00:00`);
const mondayOffset = (base.getDay() + 6) % 7;
@@ -2623,6 +2665,8 @@ async function togglePinForEntry(entry: any) {
}
const selectedWorkspaceContact = computed(() => {
if (selectedContact.value) return selectedContact.value;
const threadContactId = (selectedCommThread.value?.id ?? "").trim();
if (threadContactId) {
const byId = contacts.value.find((contact) => contact.id === threadContactId);
@@ -2634,8 +2678,6 @@ const selectedWorkspaceContact = computed(() => {
const byName = contacts.value.find((contact) => contact.name === threadContactName);
if (byName) return byName;
}
if (selectedContact.value) return selectedContact.value;
return contacts.value[0] ?? null;
});
@@ -3500,8 +3542,8 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
class="min-h-0 flex-1"
:class="selectedTab === 'communications' && peopleLeftMode === 'contacts' ? 'px-0 pt-0 pb-0' : 'px-3 pt-3 pb-0 md:px-4 md:pt-4 md:pb-0'"
>
<section v-if="selectedTab === 'communications' && peopleLeftMode === 'calendar'" class="flex h-full min-h-0 flex-col gap-3">
<div class="grid grid-cols-[auto_1fr_auto] items-center gap-2">
<section v-if="selectedTab === 'communications' && peopleLeftMode === 'calendar'" class="flex h-full min-h-0 flex-col gap-3">
<div class="grid grid-cols-[auto_1fr_auto] items-center gap-2">
<div class="flex items-center gap-1">
<button class="btn btn-xs" @click="setToday">Today</button>
<button class="btn btn-xs btn-ghost" @click="shiftCalendar(-1)"></button>
@@ -3522,11 +3564,23 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
{{ option.label }}
</option>
</select>
</div>
</div>
</div>
</div>
<div class="min-h-0 flex-1 overflow-y-auto pr-1">
<div v-if="calendarView === 'month'" class="space-y-1">
<article
v-if="focusedCalendarEvent"
class="rounded-xl border border-success/50 bg-success/10 px-3 py-2"
>
<p class="text-xs font-semibold uppercase tracking-wide text-success/80">Review focus event</p>
<p class="text-sm font-medium text-base-content">{{ focusedCalendarEvent.title }}</p>
<p class="text-xs text-base-content/70">
{{ formatDay(focusedCalendarEvent.start) }} · {{ formatTime(focusedCalendarEvent.start) }} - {{ formatTime(focusedCalendarEvent.end) }}
</p>
<p class="mt-1 text-xs text-base-content/80">{{ focusedCalendarEvent.note || "No note" }}</p>
</article>
<div class="min-h-0 flex-1 overflow-y-auto pr-1">
<div v-if="calendarView === 'month'" class="space-y-1">
<div class="grid grid-cols-7 gap-1 text-center text-xs font-semibold text-base-content/60">
<span>Sun</span>
<span>Mon</span>
@@ -3542,18 +3596,19 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
v-for="cell in monthCells"
:key="cell.key"
class="min-h-24 rounded-lg border p-1 text-left"
:class="[
cell.inMonth ? 'border-base-300 bg-base-100' : 'border-base-200 bg-base-200/40 text-base-content/40',
selectedDateKey === cell.key ? 'border-primary bg-primary/5' : '',
]"
@click="pickDate(cell.key)"
>
<p class="mb-1 text-xs font-semibold">{{ cell.day }}</p>
<button
v-for="event in cell.events.slice(0, 2)"
:key="event.id"
class="block w-full truncate rounded px-1 text-left text-[10px] text-base-content/70 transition hover:underline"
:class="isReviewHighlightedEvent(event.id) ? 'bg-success/20 text-success-content ring-1 ring-success/40' : ''"
:class="[
cell.inMonth ? 'border-base-300 bg-base-100' : 'border-base-200 bg-base-200/40 text-base-content/40',
selectedDateKey === cell.key ? 'border-primary bg-primary/5' : '',
monthCellHasFocusedEvent(cell.events) ? 'border-success/60 bg-success/10' : '',
]"
@click="pickDate(cell.key)"
>
<p class="mb-1 text-xs font-semibold">{{ cell.day }}</p>
<button
v-for="event in monthCellEvents(cell.events)"
:key="event.id"
class="block w-full truncate rounded px-1 text-left text-[10px] text-base-content/70 transition hover:underline"
:class="isReviewHighlightedEvent(event.id) ? 'bg-success/20 text-success-content ring-1 ring-success/40' : ''"
@click.stop="openThreadFromCalendarItem(event)"
>
{{ formatTime(event.start) }} {{ event.title }}
@@ -4119,7 +4174,10 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
</div>
<div v-else-if="entry.kind === 'call'" class="flex justify-center">
<div class="call-wave-card w-full max-w-[460px] rounded-2xl border border-base-300 px-4 py-3 text-center">
<div
class="call-wave-card w-full max-w-[460px] rounded-2xl border border-base-300 px-4 py-3 text-center"
:class="isReviewHighlightedMessage(entry.item.id) ? 'border-success/60 bg-success/10 ring-2 ring-success/40' : ''"
>
<p class="mb-2 text-xs text-base-content/65">
{{ formatDay(entry.item.at) }} · {{ formatTime(entry.item.at) }}
<span v-if="entry.item.duration"> · {{ entry.item.duration }}</span>
@@ -4249,13 +4307,19 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
</article>
</div>
<div
v-else
class="flex"
:class="entry.item.direction === 'out' ? 'justify-end' : 'justify-start'"
>
<div class="max-w-[88%] rounded-xl border border-base-300 p-3" :class="entry.item.direction === 'out' ? 'bg-base-200' : 'bg-base-100'">
<p class="text-sm">{{ entry.item.text }}</p>
<div
v-else
class="flex"
:class="entry.item.direction === 'out' ? 'justify-end' : 'justify-start'"
>
<div
class="max-w-[88%] rounded-xl border border-base-300 p-3"
:class="[
entry.item.direction === 'out' ? 'bg-base-200' : 'bg-base-100',
isReviewHighlightedMessage(entry.item.id) ? 'border-success/60 bg-success/10 ring-2 ring-success/40' : '',
]"
>
<p class="text-sm">{{ entry.item.text }}</p>
<p class="mt-1 text-xs text-base-content/60">
<span class="mr-1 inline-flex h-4 w-4 items-center justify-center align-middle">
<svg v-if="channelIcon(entry.item.channel) === 'telegram'" viewBox="0 0 24 24" class="h-3.5 w-3.5 fill-current">