Add incremental loading to manager and client lists

This commit is contained in:
Ruslan Bakiev
2026-04-06 11:49:38 +07:00
parent 7c5d0967a0
commit d119a76ae6
7 changed files with 348 additions and 71 deletions

View File

@@ -56,33 +56,40 @@ const referralLinksByReferrer = computed(() => {
const filteredBalances = computed(() => {
const query = search.value.trim().toLowerCase();
return balances.value.filter((item) => {
const links = referralLinksByReferrer.value.get(item.userId);
return balances.value
.filter((item) => {
const links = referralLinksByReferrer.value.get(item.userId);
if (!links?.length) {
return false;
}
if (!links?.length) {
return false;
}
if (!query) {
return true;
}
if (!query) {
return true;
}
return [
item.fullName,
item.email,
item.companyName || '',
String(item.balance),
...links.flatMap((link) => [
link.refereeName,
link.refereeEmail,
link.refereeCompanyName || '',
String(link.bonusPercent),
]),
]
.join(' ')
.toLowerCase()
.includes(query);
});
return [
item.fullName,
item.email,
item.companyName || '',
String(item.balance),
...links.flatMap((link) => [
link.refereeName,
link.refereeEmail,
link.refereeCompanyName || '',
String(link.bonusPercent),
]),
]
.join(' ')
.toLowerCase()
.includes(query);
})
.slice()
.sort((left, right) => {
const leftLatest = referralLinksByReferrer.value.get(left.userId)?.[0]?.createdAt ?? '';
const rightLatest = referralLinksByReferrer.value.get(right.userId)?.[0]?.createdAt ?? '';
return rightLatest.localeCompare(leftLatest);
});
});
const filteredWithdrawals = computed(() => {
@@ -107,6 +114,30 @@ const filteredWithdrawals = computed(() => {
});
});
const {
canLoadMore: canLoadMoreBalances,
loadMore: loadMoreBalances,
loadMoreSentinel: loadMoreBalancesSentinel,
remainingCount: remainingBalancesCount,
visibleItems: visibleBalances,
} = useIncrementalList(filteredBalances, {
pageSize: 24,
enabled: computed(() => activeTab.value === 'balances'),
resetKeys: [search, activeTab],
});
const {
canLoadMore: canLoadMoreWithdrawals,
loadMore: loadMoreWithdrawals,
loadMoreSentinel: loadMoreWithdrawalsSentinel,
remainingCount: remainingWithdrawalsCount,
visibleItems: visibleWithdrawals,
} = useIncrementalList(filteredWithdrawals, {
pageSize: 24,
enabled: computed(() => activeTab.value === 'withdrawals'),
resetKeys: [search, activeTab],
});
function userInitials(fullName: string) {
const parts = fullName
.trim()
@@ -150,17 +181,29 @@ function formatAmount(value: number) {
<div v-else-if="filteredBalances.length === 0" class="manager-empty-state">
Бонусных связок пока нет.
</div>
<div v-else class="grid gap-4 sm:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-6">
<UsersGridCard
v-for="item in filteredBalances"
:key="item.userId"
:to="`/bonus-system/${item.userId}`"
:full-name="item.fullName"
:avatar-src="messengerConnectionAvatarSrc(usersById.get(item.userId)?.telegramConnection)"
:initials="userInitials(item.fullName)"
meta-label="Доступный бонус"
:meta-value="formatAmount(item.balance)"
/>
<div v-else class="space-y-4">
<div class="grid gap-4 sm:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-6">
<UsersGridCard
v-for="item in visibleBalances"
:key="item.userId"
:to="`/bonus-system/${item.userId}`"
:full-name="item.fullName"
:avatar-src="messengerConnectionAvatarSrc(usersById.get(item.userId)?.telegramConnection)"
:initials="userInitials(item.fullName)"
meta-label="Доступный бонус"
:meta-value="formatAmount(item.balance)"
/>
</div>
<div
v-if="canLoadMoreBalances"
ref="loadMoreBalancesSentinel"
class="flex justify-center"
>
<button class="btn btn-outline border-[#d7e9de] bg-white" @click="loadMoreBalances">
Показать ещё {{ Math.min(remainingBalancesCount, 24) }}
</button>
</div>
</div>
</template>
@@ -173,7 +216,7 @@ function formatAmount(value: number) {
</div>
<div v-else class="space-y-4">
<article
v-for="withdrawal in filteredWithdrawals"
v-for="withdrawal in visibleWithdrawals"
:key="withdrawal.id"
class="surface-card rounded-3xl px-5 py-5"
>
@@ -190,6 +233,16 @@ function formatAmount(value: number) {
</NuxtLink>
</div>
</article>
<div
v-if="canLoadMoreWithdrawals"
ref="loadMoreWithdrawalsSentinel"
class="flex justify-center"
>
<button class="btn btn-outline border-[#d7e9de] bg-white" @click="loadMoreWithdrawals">
Показать ещё {{ Math.min(remainingWithdrawalsCount, 24) }}
</button>
</div>
</div>
</template>
</section>

View File

@@ -27,14 +27,32 @@ const createReferralMutation = useMutation(CreateReferralDocument);
const clientOptions = computed<ManagerUserItem[]>(() => (
(usersQuery.result.value?.managerUsers ?? [])
.filter((user) => user.role === 'CLIENT')
.slice()
.sort((left, right) => left.fullName.localeCompare(right.fullName, 'ru'))
));
const referrerOptions = computed<ManagerUserItem[]>(() => (
clientOptions.value.filter((user) => user.id !== refereeUserId.value)
));
const refereeOptions = computed<ManagerUserItem[]>(() => (
clientOptions.value.filter((user) => user.id !== referrerUserId.value)
));
const referralLinks = computed<ManagerReferralLinkItem[]>(() => (
linksQuery.result.value?.managerReferralLinks ?? []
));
watch(referrerUserId, (value) => {
if (value && value === refereeUserId.value) {
refereeUserId.value = '';
}
});
watch(refereeUserId, (value) => {
if (value && value === referrerUserId.value) {
referrerUserId.value = '';
}
});
function userOptionLabel(user: ManagerUserItem) {
return [user.fullName, user.companyName || user.email]
.filter(Boolean)
@@ -96,7 +114,7 @@ async function createReferral() {
<span class="label-text">Клиент, который получает бонус</span>
<select v-model="referrerUserId" class="select manager-field w-full">
<option value="">Выберите клиента</option>
<option v-for="user in clientOptions" :key="user.id" :value="user.id">
<option v-for="user in referrerOptions" :key="user.id" :value="user.id">
{{ userOptionLabel(user) }}
</option>
</select>
@@ -106,7 +124,7 @@ async function createReferral() {
<span class="label-text">Клиент, с чьих заказов начисляется бонус</span>
<select v-model="refereeUserId" class="select manager-field w-full">
<option value="">Выберите клиента</option>
<option v-for="user in clientOptions" :key="user.id" :value="user.id">
<option v-for="user in refereeOptions" :key="user.id" :value="user.id">
{{ userOptionLabel(user) }}
</option>
</select>