fix: keep deal edits and right-align club leaderboard
Empty product lists were wiping the manual amount on update, and follow-ups were only created for new deals. Leaderboard rows were laid out left-to-right on an RTL page. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -225,14 +225,15 @@ export class DealsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data: Prisma.DealUncheckedUpdateInput = {};
|
const data: Prisma.DealUncheckedUpdateInput = {};
|
||||||
|
const pricedFromLines = (dto.lineItems?.length ?? 0) > 0;
|
||||||
if (dto.title !== undefined) data.title = dto.title.trim();
|
if (dto.title !== undefined) data.title = dto.title.trim();
|
||||||
if ('amount' in dto && dto.lineItems === undefined) {
|
if ('amount' in dto && !pricedFromLines) {
|
||||||
data.amount =
|
data.amount =
|
||||||
dto.amount == null || Number.isNaN(Number(dto.amount))
|
dto.amount == null || Number.isNaN(Number(dto.amount))
|
||||||
? null
|
? null
|
||||||
: new Prisma.Decimal(dto.amount);
|
: new Prisma.Decimal(dto.amount);
|
||||||
}
|
}
|
||||||
if (dto.currency !== undefined && dto.lineItems === undefined) {
|
if (dto.currency !== undefined && !pricedFromLines) {
|
||||||
data.currency = dto.currency.trim() || 'IRR';
|
data.currency = dto.currency.trim() || 'IRR';
|
||||||
}
|
}
|
||||||
if ('source' in dto) data.source = emptyToNull(dto.source);
|
if ('source' in dto) data.source = emptyToNull(dto.source);
|
||||||
@@ -270,7 +271,7 @@ export class DealsService {
|
|||||||
assigneeConnect = assigneeIds.map((uid) => ({ id: uid }));
|
assigneeConnect = assigneeIds.map((uid) => ({ id: uid }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dto.lineItems !== undefined) {
|
if (pricedFromLines && dto.lineItems) {
|
||||||
const total = sumLineItems(dto.lineItems);
|
const total = sumLineItems(dto.lineItems);
|
||||||
data.amount =
|
data.amount =
|
||||||
total == null ? null : new Prisma.Decimal(total);
|
total == null ? null : new Prisma.Decimal(total);
|
||||||
|
|||||||
@@ -97,26 +97,26 @@ const meOutside = computed(() => {
|
|||||||
|
|
||||||
<ol class="rows" :class="{ loading }">
|
<ol class="rows" :class="{ loading }">
|
||||||
<li v-for="e in rest" :key="e.userId" :class="{ me: e.isMe }">
|
<li v-for="e in rest" :key="e.userId" :class="{ me: e.isMe }">
|
||||||
|
<span class="rank">{{ toFaDigits(e.rank) }}</span>
|
||||||
|
<UserAvatar :user-id="e.userId" :has-avatar="e.hasAvatar" :name="e.fullName" :size="28" />
|
||||||
|
<b class="who">{{ e.isMe ? 'شما' : e.fullName }}</b>
|
||||||
|
<span class="score">{{ faNum(e.score) }}</span>
|
||||||
<span class="chg" :class="chgClass(e.change)">
|
<span class="chg" :class="chgClass(e.change)">
|
||||||
<template v-if="e.change">{{ e.change > 0 ? '▲' : '▼' }} {{ toFaDigits(Math.abs(e.change)) }}</template>
|
<template v-if="e.change">{{ e.change > 0 ? '▲' : '▼' }} {{ toFaDigits(Math.abs(e.change)) }}</template>
|
||||||
</span>
|
</span>
|
||||||
<span class="score">{{ faNum(e.score) }}</span>
|
|
||||||
<b class="who">{{ e.isMe ? 'شما' : e.fullName }}</b>
|
|
||||||
<UserAvatar :user-id="e.userId" :has-avatar="e.hasAvatar" :name="e.fullName" :size="28" />
|
|
||||||
<span class="rank">{{ toFaDigits(e.rank) }}</span>
|
|
||||||
</li>
|
</li>
|
||||||
<template v-if="meOutside">
|
<template v-if="meOutside">
|
||||||
<li class="sep" aria-hidden="true">⋯</li>
|
<li class="sep" aria-hidden="true">⋯</li>
|
||||||
<li class="me">
|
<li class="me">
|
||||||
|
<span class="rank">{{ toFaDigits(meOutside.rank) }}</span>
|
||||||
|
<UserAvatar :user-id="meOutside.userId" :has-avatar="meOutside.hasAvatar" :name="meOutside.fullName" :size="28" />
|
||||||
|
<b class="who">شما</b>
|
||||||
|
<span class="score">{{ faNum(meOutside.score) }}</span>
|
||||||
<span class="chg" :class="chgClass(meOutside.change)">
|
<span class="chg" :class="chgClass(meOutside.change)">
|
||||||
<template v-if="meOutside.change">
|
<template v-if="meOutside.change">
|
||||||
{{ meOutside.change > 0 ? '▲' : '▼' }} {{ toFaDigits(Math.abs(meOutside.change)) }}
|
{{ meOutside.change > 0 ? '▲' : '▼' }} {{ toFaDigits(Math.abs(meOutside.change)) }}
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
<span class="score">{{ faNum(meOutside.score) }}</span>
|
|
||||||
<b class="who">شما</b>
|
|
||||||
<UserAvatar :user-id="meOutside.userId" :has-avatar="meOutside.hasAvatar" :name="meOutside.fullName" :size="28" />
|
|
||||||
<span class="rank">{{ toFaDigits(meOutside.rank) }}</span>
|
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
<li v-if="board && !board.items.length" class="empty">هنوز کسی در این بازه امتیاز نگرفته است.</li>
|
<li v-if="board && !board.items.length" class="empty">هنوز کسی در این بازه امتیاز نگرفته است.</li>
|
||||||
@@ -315,7 +315,7 @@ const meOutside = computed(() => {
|
|||||||
.who {
|
.who {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
text-align: end;
|
text-align: start;
|
||||||
font-size: 0.84rem;
|
font-size: 0.84rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #0f172a;
|
color: #0f172a;
|
||||||
|
|||||||
@@ -116,6 +116,8 @@ const sourceRef = ref('');
|
|||||||
const status = ref('');
|
const status = ref('');
|
||||||
const customFields = ref<Record<string, unknown>>({});
|
const customFields = ref<Record<string, unknown>>({});
|
||||||
const lines = ref<LineDraft[]>([]);
|
const lines = ref<LineDraft[]>([]);
|
||||||
|
/** تعداد اقلام ذخیرهشده؛ برای حذف همهٔ اقلام باید آرایهٔ خالی فرستاده شود */
|
||||||
|
const savedLineCount = ref(0);
|
||||||
|
|
||||||
const nextDueDate = ref('');
|
const nextDueDate = ref('');
|
||||||
const nextDueTime = ref('10:30');
|
const nextDueTime = ref('10:30');
|
||||||
@@ -364,7 +366,11 @@ async function load() {
|
|||||||
quantity: String(item.quantity),
|
quantity: String(item.quantity),
|
||||||
unitPrice: String(item.unitPrice),
|
unitPrice: String(item.unitPrice),
|
||||||
}));
|
}));
|
||||||
|
savedLineCount.value = lines.value.length;
|
||||||
productsOpen.value = lines.value.length > 0;
|
productsOpen.value = lines.value.length > 0;
|
||||||
|
if (data.company && !companies.value.some((c) => c.id === data.company!.id)) {
|
||||||
|
companies.value = [{ id: data.company.id, name: data.company.name } as CompanyDto, ...companies.value];
|
||||||
|
}
|
||||||
if (data.pipelineId !== pipelineId.value) {
|
if (data.pipelineId !== pipelineId.value) {
|
||||||
await loadBoard(data.pipelineId);
|
await loadBoard(data.pipelineId);
|
||||||
stageId.value = data.stageId;
|
stageId.value = data.stageId;
|
||||||
@@ -397,6 +403,7 @@ async function load() {
|
|||||||
|
|
||||||
function payload(): CreateDealRequest {
|
function payload(): CreateDealRequest {
|
||||||
const lineItems = toLineInputs();
|
const lineItems = toLineInputs();
|
||||||
|
const sendLineItems = lineItems.length > 0 || savedLineCount.value > 0;
|
||||||
return {
|
return {
|
||||||
title: title.value,
|
title: title.value,
|
||||||
amount:
|
amount:
|
||||||
@@ -423,12 +430,12 @@ function payload(): CreateDealRequest {
|
|||||||
stageId: stageId.value || undefined,
|
stageId: stageId.value || undefined,
|
||||||
customFields: customFields.value,
|
customFields: customFields.value,
|
||||||
currency: currency.value || 'IRR',
|
currency: currency.value || 'IRR',
|
||||||
lineItems,
|
...(sendLineItems ? { lineItems } : {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createFollowUp(dealId: string) {
|
async function createFollowUp(dealId: string) {
|
||||||
if (!isNew.value || !nextDueDate.value) return;
|
if (!nextDueDate.value) return;
|
||||||
const due = new Date(`${nextDueDate.value}T${nextDueTime.value || '10:30'}:00`);
|
const due = new Date(`${nextDueDate.value}T${nextDueTime.value || '10:30'}:00`);
|
||||||
if (Number.isNaN(due.getTime())) return;
|
if (Number.isNaN(due.getTime())) return;
|
||||||
await api.post('/activities', {
|
await api.post('/activities', {
|
||||||
@@ -488,6 +495,11 @@ async function submit() {
|
|||||||
if (moveStageId) {
|
if (moveStageId) {
|
||||||
await api.patch(`/deals/${id.value}/move`, { stageId: moveStageId });
|
await api.patch(`/deals/${id.value}/move`, { stageId: moveStageId });
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
await createFollowUp(id.value);
|
||||||
|
} catch {
|
||||||
|
/* deal saved; follow-up is optional */
|
||||||
|
}
|
||||||
if (props.embedded) {
|
if (props.embedded) {
|
||||||
emit('saved');
|
emit('saved');
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user