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:
parent
ddfaf56722
commit
2d2b8784ec
@ -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();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user