Compare commits

...

2 Commits

Author SHA1 Message Date
Олег Водянов
c23c862d0b
Merge pull request #15 from olegvodyanov/add_pagination
All checks were successful
continuous-integration/drone/push Build is passing
add pagination
2025-04-23 10:28:06 +04:00
oleg.vodyanov91@gmail.com
07d1d7d990 add pagination 2025-04-23 10:27:41 +04:00
3 changed files with 71 additions and 39 deletions

View File

@ -29,3 +29,4 @@ volumes:
trigger: trigger:
branch: branch:
- master - master
- develop

View File

@ -16,11 +16,27 @@ def links_list(request):
""" """
Return JSON list of all links. Return JSON list of all links.
""" """
if request.method == 'GET':
all_links = Link.objects.all().values('id', 'url', 'watched') try:
return JsonResponse(list(all_links), safe=False) page = int(request.GET.get('page', 1))
else: per_page = int(request.GET.get('per_page', 8))
return JsonResponse({'error': 'Method not allowed'}, status=405) except ValueError:
return JsonResponse({'error': 'Invalid page or per_page'}, status=400)
offset = (page - 1) * per_page
limit = offset + per_page
all_links = Link.objects.all().order_by('-id') # latest first
total = all_links.count()
results = list(all_links[offset:limit].values('id', 'url', 'watched'))
return JsonResponse({
'results': results,
'total': total,
'page': page,
'per_page': per_page,
'pages': (total + per_page - 1) // per_page
})
@csrf_exempt @csrf_exempt

View File

@ -3,52 +3,65 @@ const addLinkBtn = document.getElementById('addLinkBtn');
const newLinksContainer = document.getElementById('newLinksContainer'); const newLinksContainer = document.getElementById('newLinksContainer');
const watchedLinksContainer = document.getElementById('watchedLinksContainer'); const watchedLinksContainer = document.getElementById('watchedLinksContainer');
const PER_PAGE = 8;
let linksData = []; let linksData = [];
let currentPage = 1;
// Fetch all links from the server
function loadLinks() { function loadLinks() {
fetch('/api/links/') fetch('/api/links/?page=${currentPage}&per_page=${PER_PAGE}')
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
linksData = data; // store in a global variable linksData = data.results;
renderLinks(); renderPaginatedList(linksData, newLinksContainer, false, data.page, data.pages);
renderPaginatedList(linksData.filter(l => l.watched), watchedLinksContainer, true, data.page, data.pages);
}) })
.catch(err => console.error(err)); .catch(err => console.error(err));
} }
// Render the links into the two tabs function renderPaginatedList(linkList, container, isWatched, page, totalPages) {
function renderLinks() { container.innerHTML = '';
// separate them
const newLinks = linksData.filter(link => !link.watched);
const watchedLinks = linksData.filter(link => link.watched);
const newRow = document.createElement('div'); const row = document.createElement('div');
newRow.className = 'row gy-4'; row.className = 'row gy-4';
// Render "New" links linkList
newLinksContainer.innerHTML = ''; .filter(link => link.watched === isWatched)
newLinks.forEach(link => { .forEach(link => {
const col = document.createElement('div'); const col = document.createElement('div');
col.className = 'col-12 col-sm-6 col-md-4 col-lg-3'; // responsive columns col.className = 'col-12 col-sm-6 col-md-4 col-lg-3';
col.appendChild(createLinkElement(link, false)); col.appendChild(createLinkElement(link, isWatched));
newRow.appendChild(col); row.appendChild(col);
}); });
newLinksContainer.appendChild(newRow); container.appendChild(row);
const watchedRow = document.createElement('div'); // Pagination controls
watchedRow.className = 'row gy-4'; if (totalPages > 1) {
const nav = document.createElement('div');
nav.className = 'd-flex justify-content-center mt-4 gap-2';
// Render "Watched" links const prevBtn = document.createElement('button');
watchedLinksContainer.innerHTML = ''; prevBtn.innerText = '← Previous';
watchedLinks.forEach(link => { prevBtn.className = 'btn btn-outline-primary';
const col = document.createElement('div'); prevBtn.disabled = page <= 1;
col.className = 'col-12 col-sm-6 col-md-4 col-lg-3'; prevBtn.onclick = () => {
col.appendChild(createLinkElement(link, true)); currentPage--;
watchedRow.appendChild(col); loadLinks();
}); };
watchedLinksContainer.appendChild(watchedRow); const nextBtn = document.createElement('button');
nextBtn.innerText = 'Next →';
nextBtn.className = 'btn btn-outline-primary';
nextBtn.disabled = page >= totalPages;
nextBtn.onclick = () => {
currentPage++;
loadLinks();
};
nav.appendChild(prevBtn);
nav.appendChild(nextBtn);
container.appendChild(nav);
}
reinitializeInstagramEmbeds(); reinitializeInstagramEmbeds();
} }
@ -95,7 +108,8 @@ function deleteLink(linkId) {
.then(res => { .then(res => {
if (res.ok) { if (res.ok) {
linksData = linksData.filter(link => link.id !== linkId); linksData = linksData.filter(link => link.id !== linkId);
renderLinks(); currentPage = 1;
renderPaginatedList();
reinitializeInstagramEmbeds(); reinitializeInstagramEmbeds();
} }
}) })
@ -121,7 +135,7 @@ function markLinkWatched(linkId) {
if (idx !== -1) { if (idx !== -1) {
linksData[idx] = updatedLink; linksData[idx] = updatedLink;
} }
renderLinks(); renderPaginatedList();
reinitializeInstagramEmbeds(); reinitializeInstagramEmbeds();
}) })
.catch(err => console.error(err)); .catch(err => console.error(err));
@ -142,7 +156,8 @@ addLinkBtn.addEventListener('click', () => {
// update local data // update local data
linksData.push(addedLink); linksData.push(addedLink);
newLinkInput.value = ''; newLinkInput.value = '';
renderLinks(); currentPage = 1;
renderPaginatedList();
reinitializeInstagramEmbeds(); reinitializeInstagramEmbeds();
}) })
.catch(err => console.error(err)); .catch(err => console.error(err));