Skip to content

Commit

Permalink
feat: pagination for admin users (#236)
Browse files Browse the repository at this point in the history
* feat: improved laoding state on admin users

* feat: comment out sleep

* feat: added pagination to /api/v1/users route

* feat: added pagination to /api/v1/users route

* feat: added pagination for admin users
  • Loading branch information
wajeht authored Oct 12, 2022
1 parent 271dba0 commit a96b8ba
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 46 deletions.
38 changes: 32 additions & 6 deletions src/apps/api/v1/users/users.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,25 +47,51 @@ export async function postUser(req, res) {
export async function getUsers(req, res) {
const { email } = req.query;

const cachedUsers = JSON.parse(await redis.get(`user-id-${req.user.user_id}-users`));
const { perPage, currentPage, cache } = req.query;

const pagination = {
perPage: perPage ?? null,
currentPage: currentPage ?? null,
};

let users;

if (cachedUsers === null) {
if (cache === false) {
if (email) {
users = await UsersQueries.getAllUsers({ email, pagination });
} else {
users = await UsersQueries.getAllUsers({ pagination });
}

return res.status(StatusCodes.OK).json({
status: 'success',
request_url: req.originalUrl,
message: 'The resource was returned successfully!',
cache,
data: users.data,
pagination: users.pagination,
});
}

users = JSON.parse(await redis.get(`user-id-${req.user.user_id}-users`));

if (users === null) {
if (email) {
users = await UsersQueries.getAllUsers(email);
users = await UsersQueries.getAllUsers({ email, pagination });
} else {
users = await UsersQueries.getAllUsers();
users = await UsersQueries.getAllUsers({ pagination });
}

await redis.set(`user-id-${req.user.user_id}-users`, JSON.stringify(users));
}

res.status(StatusCodes.OK).json({
return res.status(StatusCodes.OK).json({
status: 'success',
request_url: req.originalUrl,
message: 'The resource was returned successfully!',
data: cachedUsers,
cache,
data: users.data,
pagination: users.pagination,
});
}

Expand Down
8 changes: 6 additions & 2 deletions src/apps/api/v1/users/users.queries.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@ import dayjs from 'dayjs';
* Get all users from the database.
* @returns An array of objects
*/
export function getAllUsers(email) {
export function getAllUsers({ email, pagination = { perPage: null, currentPage: null } }) {
const query = db
.select('*')
.from('users as u')
.leftJoin('user_details as ud', 'u.id', 'ud.user_id')
.orderBy('u.id', 'desc');
.orderBy('u.id', 'desc')
.paginate({
...pagination,
isLengthAware: true,
});

if (email) {
query.where({ 'users.email': email });
Expand Down
5 changes: 4 additions & 1 deletion src/apps/api/v1/users/users.router.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ users.get('/check-authentication', UsersController.getCheckAuthentication);
* GET /api/v1/users
* @tags users
* @summary get a list of users
* @param {cache} cache.query.required - the cache - application/x-www-form-urlencoded
* @param {number} perPage.query.required - the perPage id - application/x-www-form-urlencoded
* @param {number} currentPage.query.required - the currentPage id - application/x-www-form-urlencoded
*/
users.get('/', catchAsyncErrors(UsersController.getUsers));
users.get('/', validator(UsersValidation.getUsers), catchAsyncErrors(UsersController.getUsers));

/**
* POST /api/v1/users
Expand Down
35 changes: 34 additions & 1 deletion src/apps/api/v1/users/users.validation.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { check, checkSchema, param, body } from 'express-validator';
import { query, check, checkSchema, param, body } from 'express-validator';
import { blue, custom, green, red, yellow } from '../../../../utils/rainbow-log.js';
import * as UserQueries from './users.queries.js';
import { isEqual } from 'lodash-es';
Expand Down Expand Up @@ -52,6 +52,39 @@ export const postUser = [
.withMessage('The value must include a number character!'),
];

export const getUsers = [
query('cache')
.optional()
.trim()
.notEmpty()
.withMessage('cache must not be empty!')
.bail()
.isBoolean()
.withMessage('cache must be a boolean format')
.bail()
.toBoolean(),
query('perPage')
.optional()
.trim()
.notEmpty()
.withMessage('perPage must not be empty!')
.bail()
.isInt()
.withMessage('perPage must be an ID!')
.bail()
.toInt(),
query('currentPage')
.optional()
.trim()
.notEmpty()
.withMessage('current-page must not be empty!')
.bail()
.isInt()
.withMessage('current-page must be an ID!')
.bail()
.toInt(),
];

/* Validating the user input. */
export const getUser = [
param('id')
Expand Down
82 changes: 82 additions & 0 deletions src/apps/ui/components/shared/Paginator.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<script setup>
const props = defineProps({
pagination: {
total: 0,
lastPage: 0,
perPage: 0,
currentPage: 0,
from: 0,
to: 0,
},
});
const emit = defineEmits(['next', 'previous', 'to']);
function handleNext() {
emit('next');
}
function handlePrevious() {
emit('previous');
}
function handleToPage(currentPage) {
emit('to', currentPage);
}
</script>

<template>
<nav class="py-2">
<ul class="pagination justify-content-center mb-0 pb-0">
<li
class="page-item"
:class="{ disabled: pagination.currentPage === 1 || pagination.currentPage === 0 }"
>
<a class="page-link" @click="handlePrevious()">Previous</a>
</li>

<li
v-for="(p, index) in pagination.lastPage"
class="page-item"
:class="{ active: p === pagination.currentPage }"
>
<a class="page-link" @click="handleToPage(p)">{{ p }}</a>
</li>

<li class="page-item" :class="{ disabled: pagination.currentPage === pagination.lastPage }">
<a class="page-link" @click="handleNext()">Next</a>
</li>
</ul>
</nav>
</template>

<style scoped>
.pagination > li > a {
background-color: white;
color: #212529;
cursor: pointer;
}
.pagination > li > a:focus,
.pagination > li > a:hover,
.pagination > li > span:focus,
.pagination > li > span:hover {
color: #5a5a5a;
background-color: #eee;
border-color: #ddd;
cursor: pointer;
}
.pagination > .active > a {
color: white;
background-color: #212529 !important;
border: solid 1px #212529 !important;
cursor: pointer;
}
.pagination > .active > a:hover {
background-color: #212529 !important;
border: solid 1px #ffffff;
cursor: pointer;
}
</style>
80 changes: 48 additions & 32 deletions src/apps/ui/pages/admin/AdminUsers.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,21 @@ import { ref, onMounted, reactive, watch } from 'vue';
import useAppStore from '../../store/app.store.js';
import api from '../../../../utils/fetch-with-style.js';
import dayjs from 'dayjs';
import Paginator from '../../components/shared/Paginator.vue';
// import { sleep } from '../../../../utils/helpers.js';
// import InsideLoading from '../../components/shared/InsideLoading.vue';
const appStore = useAppStore();
const alert = reactive({ type: '', msg: '' });
const users = ref([]);
const checkedUsers = ref([]);
const loading = ref(false);
const checkAll = ref(false);
onMounted(async () => {
loading.value = true;
// await sleep(1000);
await fetchUsers();
const alert = reactive({ type: '', msg: '' });
const pagination = reactive({});
loading.value = false;
onMounted(async () => {
await fetchUsers({});
});
watch(checkAll, (value) => {
Expand All @@ -32,9 +30,25 @@ watch(checkAll, (value) => {
}
});
async function fetchUsers() {
async function addUser() {
// ...
}
async function deleteUser() {
// ...
}
async function modifyUser() {
// ...
}
async function fetchUsers({ perPage = 25, currentPage = 1 }) {
try {
const res = await api.get(`/api/v1/users`);
loading.value = true;
const res = await api.get(
`/api/v1/users?cache=false&perPage=${perPage}&currentPage=${currentPage}`,
);
const json = await res.json();
if (!res.ok) {
Expand All @@ -46,6 +60,9 @@ async function fetchUsers() {
}
users.value = json.data;
Object.assign(pagination, json.pagination);
loading.value = false;
} catch (e) {
appStore.loading = false;
alert.type = 'danger';
Expand Down Expand Up @@ -140,8 +157,11 @@ async function fetchUsers() {

<!-- table body -->
<!-- **************************** LOADING STATE STARTS **************************** -->
<tbody v-if="loading" class="placeholder-glow">
<tr v-for="(i, index) in 20" :key="`loading-key-${index}`">
<tbody
v-if="loading"
class="placeholder-glow animate__animated animate__fadeIn animate__faster"
>
<tr v-for="(i, index) in pagination.perPage" :key="`loading-key-${index}`">
<!-- checkbox -->
<th scope="row">
<span class="placeholder col-6"></span>
Expand All @@ -152,17 +172,20 @@ async function fetchUsers() {

<!-- user -->
<td>
<div class="d-flex gap-1">
<div class="d-flex gap-1" style="min-height: 90px !important">
<!-- pic -->
<span class="placeholder col-6"></span>
<span class="placeholder col-6 rounded"></span>

<!-- role -->
<div class="d-flex flex-column gap-1">
<div class="d-flex flex-column gap-1 justify-content-between">
<!-- top -->
<span class="placeholder col-6"></span>

<!-- bottom -->
<span class="d-flex flex-column fw-light">
<div
class="d-flex flex-column fw-light gap-1"
style="min-width: 200px !important"
>
<!-- username -->
<span class="placeholder col-6"></span>

Expand All @@ -171,7 +194,7 @@ async function fetchUsers() {

<!-- birthday -->
<span class="placeholder col-6"></span>
</span>
</div>
</div>
</div>
</td>
Expand All @@ -182,8 +205,8 @@ async function fetchUsers() {
<!-- status -->
<td>
<div class="d-flex flex-column gap-1">
<span class="placeholder bg-success col-6"></span>
<span class="placeholder bg-success col-6"></span>
<span class="placeholder bg-success col-6 rounded"></span>
<span class="placeholder bg-success col-6 rounded"></span>
</div>
</td>

Expand All @@ -199,7 +222,7 @@ async function fetchUsers() {
<!-- **************************** LOADING STATE ENDS **************************** -->

<!-- table body -->
<tbody v-if="!loading">
<tbody v-if="!loading" class="animate__animated animate__fadeIn animate__faster">
<tr v-for="u in users" :key="`user-key-${u.id}`">
<!-- checkbox -->
<th scope="row">
Expand Down Expand Up @@ -312,19 +335,12 @@ async function fetchUsers() {

<!-- footer -->
<div class="card-footer text-muted">
<nav aria-label="Page navigation example" class="py-2">
<ul class="pagination justify-content-center mb-0 pb-0">
<li class="page-item disabled">
<a class="page-link">Previous</a>
</li>
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item"><a class="page-link" href="#">2</a></li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item">
<a class="page-link" href="#">Next</a>
</li>
</ul>
</nav>
<Paginator
:pagination="pagination"
@previous="fetchUsers({ currentPage: pagination.currentPage - 1 })"
@to="(page) => fetchUsers({ currentPage: page })"
@next="fetchUsers({ currentPage: pagination.currentPage + 1 })"
/>
</div>
</div>
</div>
Expand Down
11 changes: 7 additions & 4 deletions src/utils/scratch.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import dayjs from 'dayjs';
const pagination = {
from: 5,
to: 10,
};

const now = new Date().toISOString();

console.log(now);
for (const p in pagination.to) {
console.log(p);
}

0 comments on commit a96b8ba

Please sign in to comment.