feat: separate prior records behind an owner switch and count last-period questions

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-15 10:48:30 +03:30
co-authored by Cursor
parent 3a8a684aa4
commit 6bc5e1bec7
29 changed files with 775 additions and 77 deletions
+74 -4
View File
@@ -2,6 +2,7 @@
import { computed, ref, watch } from 'vue';
import { RouterLink, useRoute, useRouter } from 'vue-router';
import { api } from '@/lib/api';
import { useAuthStore } from '@/stores/auth';
import { formatCompactMoney, formatMoney } from '@/lib/money';
import {
calendarDaysBetween,
@@ -60,6 +61,7 @@ type TimelineRow = {
tone: TimelineTone;
href: string;
at: number;
prior?: boolean;
};
type AttachmentRow = {
@@ -180,6 +182,27 @@ const previousSourceLabel = computed(() => {
return SOURCE_LABELS[src] || src;
});
const auth = useAuthStore();
const priorArchiveOn = computed(() => Boolean(auth.user?.priorArchive));
function isPriorActivity(row: ActivityDto) {
if (row.importSource !== 'didar') return false;
if (row.status === 'open' && row.dueAt && new Date(row.dueAt).getTime() > Date.now()) return false;
return true;
}
function isPriorDeal(row: DealDto) {
if (row.importSource !== 'didar') return false;
if (
row.status === 'open' &&
row.expectedCloseAt &&
new Date(row.expectedCloseAt).getTime() > Date.now()
) {
return false;
}
return true;
}
const lastCallAt = computed(() => {
const calls = activities.value
.filter((a) => a.type === 'call')
@@ -210,6 +233,12 @@ const nextActivity = computed(() => {
});
const openDeals = computed(() => deals.value.filter((d) => d.status === 'open'));
const currentDeals = computed(() =>
priorArchiveOn.value ? deals.value.filter((d) => !isPriorDeal(d)) : deals.value,
);
const priorDeals = computed(() =>
priorArchiveOn.value ? deals.value.filter((d) => isPriorDeal(d)) : [],
);
const lastQuote = computed(
() =>
@@ -308,6 +337,7 @@ const timeline = computed<TimelineRow[]>(() => {
tone: activityTone(a),
href: `/activities/${a.id}`,
at,
prior: isPriorActivity(a),
});
}
for (const n of notes.value) {
@@ -340,7 +370,17 @@ const visibleTimeline = computed(() => {
historyFilter.value === 'all'
? timeline.value
: timeline.value.filter((row) => row.tone === historyFilter.value);
return rows.slice(0, 8);
const current = priorArchiveOn.value ? rows.filter((row) => !row.prior) : rows;
return current.slice(0, 8);
});
const priorTimeline = computed(() => {
if (!priorArchiveOn.value) return [];
const rows =
historyFilter.value === 'all'
? timeline.value
: timeline.value.filter((row) => row.tone === historyFilter.value);
return rows.filter((row) => row.prior).slice(0, 8);
});
function fileKind(name: string, mime: string): AttachmentRow['kind'] {
@@ -442,7 +482,7 @@ async function load() {
api.get<PaginatedResponse<MemoryItemDto>>('/memory', {
params: { personId: id.value, limit: 50 },
}),
api.get<PipelineBoardDto>('/pipelines/board'),
api.get<PipelineBoardDto>('/pipelines/board', { params: { prior: 'all' } }),
]);
person.value = personRes.data;
activities.value = actRes.data.items;
@@ -832,7 +872,24 @@ watch(() => route.query.tab, readTab, { immediate: true });
</RouterLink>
</li>
</ol>
<p v-else class="empty">موردی با این فیلتر نیست.</p>
<p v-else-if="!priorTimeline.length" class="empty">موردی با این فیلتر نیست.</p>
<template v-if="priorTimeline.length">
<h3 class="prior-head">بایگانی اطلاعات قبل</h3>
<ol class="tl">
<li v-for="row in priorTimeline" :key="row.key">
<RouterLink :to="row.href">
<span class="dot" :class="row.tone">
<UiIcon :name="timelineIcon(row.tone)" :size="14" />
</span>
<div>
<time>{{ row.when }}</time>
<strong>{{ row.title }}</strong>
<p v-if="row.body">{{ row.body }}</p>
</div>
</RouterLink>
</li>
</ol>
</template>
<RouterLink class="see-all" :to="{ name: 'activities', query: { filter: 'all' } }">
مشاهده همه فعالیت‌ها
</RouterLink>
@@ -896,7 +953,7 @@ watch(() => route.query.tab, readTab, { immediate: true });
/>
<div v-if="!dealsEmpty">
<RouterLink
v-for="deal in deals"
v-for="deal in currentDeals"
:key="deal.id"
class="row-card"
:to="{ name: 'deal-detail', params: { id: deal.id } }"
@@ -905,6 +962,19 @@ watch(() => route.query.tab, readTab, { immediate: true });
<span>{{ deal.amount != null ? formatCompactMoney(deal.amount, deal.currency) : 'بدون مبلغ' }}</span>
<em>{{ dealStatusLabel[deal.status] ?? deal.status }}</em>
</RouterLink>
<div v-if="priorDeals.length" class="subblock">
<h3>بایگانی اطلاعات قبل</h3>
<RouterLink
v-for="deal in priorDeals"
:key="deal.id"
class="row-card"
:to="{ name: 'deal-detail', params: { id: deal.id } }"
>
<strong>{{ deal.title }}</strong>
<span>{{ deal.amount != null ? formatCompactMoney(deal.amount, deal.currency) : 'بدون مبلغ' }}</span>
<em>{{ dealStatusLabel[deal.status] ?? deal.status }}</em>
</RouterLink>
</div>
<div v-if="invoices.length" class="subblock">
<h3>فاکتورها</h3>
<RouterLink
@@ -584,6 +584,19 @@ onMounted(() => {
حرفه‌ای (چندشاخه، شرط روی پاسخ فرم، تقسیم نوبتی)
</label>
</div>
<div class="mod-group">
<div class="mod-group-head">
<span>بایگانی اطلاعات قبل</span>
</div>
<label class="mod-item">
<input
type="checkbox"
:checked="moduleDraft.includes('prior_data_archive')"
@change="toggleModule('prior_data_archive')"
/>
بایگانی اطلاعات قبل
</label>
</div>
</div>
<div class="chat-settings">