Add pagination to leads list

Store tracks page/pageSize (default 20), exposes totalPages getter and
setPage/setPageSize actions. Filter actions reset to page 1. List view
shows page-size select, range indicator, and prev/next controls.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
ask 2026-05-19 15:01:53 +05:00
parent ddfaf56722
commit 2d2b8784ec
2 changed files with 71 additions and 1 deletions

View File

@ -12,6 +12,8 @@ interface State {
total: number; total: number;
loading: boolean; loading: boolean;
error: string | null; error: string | null;
page: number;
pageSize: number;
filters: { filters: {
status: LeadStatus | 'all'; status: LeadStatus | 'all';
lead_type: LeadType | 'all'; lead_type: LeadType | 'all';
@ -25,8 +27,13 @@ export const useLeadsStore = defineStore('leads', {
total: 0, total: 0,
loading: false, loading: false,
error: null, error: null,
page: 1,
pageSize: 20,
filters: { status: 'all', lead_type: 'all', deal_stage: 'all' }, filters: { status: 'all', lead_type: 'all', deal_stage: 'all' },
}), }),
getters: {
totalPages: (s): number => Math.max(1, Math.ceil(s.total / s.pageSize)),
},
actions: { actions: {
async load() { async load() {
this.loading = true; this.loading = true;
@ -37,26 +44,45 @@ export const useLeadsStore = defineStore('leads', {
lead_type: this.filters.lead_type, lead_type: this.filters.lead_type,
deal_stage: this.filters.deal_stage, deal_stage: this.filters.deal_stage,
sort: 'score_desc', sort: 'score_desc',
limit: 100, limit: this.pageSize,
offset: (this.page - 1) * this.pageSize,
}); });
this.items = res.data; this.items = res.data;
this.total = res.total; this.total = res.total;
const max = Math.max(1, Math.ceil(this.total / this.pageSize));
if (this.page > max) {
this.page = max;
await this.load();
}
} catch (e) { } catch (e) {
this.error = (e as Error).message; this.error = (e as Error).message;
} finally { } finally {
this.loading = false; this.loading = false;
} }
}, },
setPage(page: number) {
const max = Math.max(1, Math.ceil(this.total / this.pageSize));
this.page = Math.min(Math.max(1, page), max);
return this.load();
},
setPageSize(size: number) {
this.pageSize = size;
this.page = 1;
return this.load();
},
setStatus(status: LeadStatus | 'all') { setStatus(status: LeadStatus | 'all') {
this.filters.status = status; this.filters.status = status;
this.page = 1;
return this.load(); return this.load();
}, },
setLeadType(lead_type: LeadType | 'all') { setLeadType(lead_type: LeadType | 'all') {
this.filters.lead_type = lead_type; this.filters.lead_type = lead_type;
this.page = 1;
return this.load(); return this.load();
}, },
setDealStage(deal_stage: DealStage | 'all') { setDealStage(deal_stage: DealStage | 'all') {
this.filters.deal_stage = deal_stage; this.filters.deal_stage = deal_stage;
this.page = 1;
return this.load(); return this.load();
}, },
}, },

View File

@ -126,5 +126,49 @@ async function removeLead(id: string, name: string) {
</tbody> </tbody>
</table> </table>
</div> </div>
<div
v-if="store.items.length > 0"
class="flex items-center justify-between text-sm text-gray-700"
>
<div>
<label class="mr-2">На странице:</label>
<select
:value="store.pageSize"
@change="store.setPageSize(Number(($event.target as HTMLSelectElement).value))"
class="border border-gray-300 rounded-md px-2 py-1 bg-white"
>
<option :value="10">10</option>
<option :value="20">20</option>
<option :value="50">50</option>
<option :value="100">100</option>
</select>
<span class="ml-4 text-gray-500">
{{ (store.page - 1) * store.pageSize + 1 }}–{{
Math.min(store.page * store.pageSize, store.total)
}}
из {{ store.total }}
</span>
</div>
<div class="flex items-center gap-2">
<button
type="button"
class="px-3 py-1 rounded-md border border-gray-300 bg-white disabled:opacity-40"
:disabled="store.page <= 1 || store.loading"
@click="store.setPage(store.page - 1)"
>
←
</button>
<span class="px-2">{{ store.page }} / {{ store.totalPages }}</span>
<button
type="button"
class="px-3 py-1 rounded-md border border-gray-300 bg-white disabled:opacity-40"
:disabled="store.page >= store.totalPages || store.loading"
@click="store.setPage(store.page + 1)"
>
→
</button>
</div>
</div>
</div> </div>
</template> </template>