Create pagination component and paginate manage page as well

This commit is contained in:
thiloho
2024-10-19 21:01:45 +02:00
parent b1a59e38c1
commit 4e98df5790
6 changed files with 121 additions and 84 deletions

View File

@@ -0,0 +1,82 @@
<script lang="ts">
import { page } from "$app/stores";
import { PAGINATION_MAX_ITEMS } from "$lib/utils";
const { commonFilters = [], resultCount }: { commonFilters?: string[]; resultCount: number } =
$props();
</script>
<div class="pagination">
{#snippet commonFilterInputs()}
{#each commonFilters as filter}
<input type="hidden" name={filter} value={$page.url.searchParams.get(filter)} />
{/each}
{/snippet}
<p>
{$page.url.searchParams.get("page") ?? 1} / {Math.max(
Math.ceil(resultCount / PAGINATION_MAX_ITEMS),
1
)}
</p>
<form method="GET">
<input type="hidden" name="page" value={1} />
{@render commonFilterInputs()}
<button type="submit" disabled={($page.url.searchParams.get("page") ?? "1") === "1"}
>First</button
>
</form>
<form method="GET">
<input
type="hidden"
name="page"
value={Math.max(1, Number.parseInt($page.url.searchParams.get("page") ?? "1") - 1)}
/>
{@render commonFilterInputs()}
<button type="submit" disabled={($page.url.searchParams.get("page") ?? "1") === "1"}
>Previous</button
>
</form>
<form method="GET">
<input
type="hidden"
name="page"
value={Math.min(
Math.max(Math.ceil(resultCount / PAGINATION_MAX_ITEMS), 1),
Number.parseInt($page.url.searchParams.get("page") ?? "1") + 1
)}
/>
{@render commonFilterInputs()}
<button
type="submit"
disabled={($page.url.searchParams.get("page") ?? "1") ===
Math.max(Math.ceil(resultCount / PAGINATION_MAX_ITEMS), 1).toString()}>Next</button
>
</form>
<form method="GET">
<input
type="hidden"
name="page"
value={Math.max(Math.ceil(resultCount / PAGINATION_MAX_ITEMS), 1)}
/>
{@render commonFilterInputs()}
<button
type="submit"
disabled={($page.url.searchParams.get("page") ?? "1") ===
Math.max(Math.ceil(resultCount / PAGINATION_MAX_ITEMS), 1).toString()}>Last</button
>
</form>
</div>
<style>
.pagination {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: var(--space-xs);
justify-content: end;
}
.pagination > form:first-of-type {
margin-inline-start: auto;
}
</style>

View File

@@ -179,6 +179,8 @@ export const enhanceForm = (options?: {
}; };
}; };
export const PAGINATION_MAX_ITEMS = 20;
export const hexToHSL = (hex: string) => { export const hexToHSL = (hex: string) => {
const r = parseInt(hex.slice(1, 3), 16) / 255; const r = parseInt(hex.slice(1, 3), 16) / 255;
const g = parseInt(hex.slice(3, 5), 16) / 255; const g = parseInt(hex.slice(3, 5), 16) / 255;

View File

@@ -1,13 +1,16 @@
import type { Actions, PageServerLoad } from "./$types"; import type { Actions, PageServerLoad } from "./$types";
import { API_BASE_PREFIX } from "$lib/server/utils"; import { API_BASE_PREFIX, apiRequest } from "$lib/server/utils";
import { apiRequest } from "$lib/server/utils";
import type { Website, User } from "$lib/db-schema"; import type { Website, User } from "$lib/db-schema";
import { PAGINATION_MAX_ITEMS } from "$lib/utils";
export const load: PageServerLoad = async ({ fetch, url }) => {
const currentPage = Number.parseInt(url.searchParams.get("page") ?? "1");
const resultOffset = (currentPage - 1) * PAGINATION_MAX_ITEMS;
export const load: PageServerLoad = async ({ fetch }) => {
const usersWithWebsites: (User & { website: Website[] })[] = ( const usersWithWebsites: (User & { website: Website[] })[] = (
await apiRequest( await apiRequest(
fetch, fetch,
`${API_BASE_PREFIX}/user?select=*,website!user_id(*)&order=created_at`, `${API_BASE_PREFIX}/user?select=*,website!user_id(*)&order=created_at&limit=${PAGINATION_MAX_ITEMS}&offset=${resultOffset}`,
"GET", "GET",
{ {
returnData: true returnData: true
@@ -15,9 +18,19 @@ export const load: PageServerLoad = async ({ fetch }) => {
) )
).data; ).data;
const resultUsers = await apiRequest(fetch, `${API_BASE_PREFIX}/user`, "HEAD", {
headers: {
Prefer: "count=exact"
},
returnData: true
});
const resultUsersCount = Number(resultUsers.data.headers.get("content-range")?.split("/").at(-1));
return { return {
usersWithWebsites, usersWithWebsites,
API_BASE_PREFIX API_BASE_PREFIX,
resultUsersCount
}; };
}; };

View File

@@ -7,6 +7,7 @@
import { enhanceForm } from "$lib/utils"; import { enhanceForm } from "$lib/utils";
import { sending } from "$lib/runes.svelte"; import { sending } from "$lib/runes.svelte";
import DateTime from "$lib/components/DateTime.svelte"; import DateTime from "$lib/components/DateTime.svelte";
import Pagination from "$lib/components/Pagination.svelte";
const { data, form }: { data: PageServerData; form: ActionData } = $props(); const { data, form }: { data: PageServerData; form: ActionData } = $props();
</script> </script>
@@ -18,9 +19,15 @@
{/if} {/if}
<section id="all-users"> <section id="all-users">
<hgroup>
<h2> <h2>
<a href="#all-users">All users</a> <a href="#all-users">All users</a>
</h2> </h2>
<p>
<strong>{data.resultUsersCount.toLocaleString("en", { useGrouping: true })}</strong>
<small>result(s)</small>
</p>
</hgroup>
<div class="scroll-container"> <div class="scroll-container">
<table> <table>
<thead> <thead>
@@ -117,6 +124,7 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<Pagination resultCount={data.resultUsersCount} />
</section> </section>
<style> <style>

View File

@@ -2,13 +2,14 @@ import type { PageServerLoad, Actions } from "./$types";
import { API_BASE_PREFIX, apiRequest } from "$lib/server/utils"; import { API_BASE_PREFIX, apiRequest } from "$lib/server/utils";
import type { ChangeLog, User, Collab } from "$lib/db-schema"; import type { ChangeLog, User, Collab } from "$lib/db-schema";
import DiffMatchPatch from "diff-match-patch"; import DiffMatchPatch from "diff-match-patch";
import { PAGINATION_MAX_ITEMS } from "$lib/utils";
export const load: PageServerLoad = async ({ parent, fetch, params, url }) => { export const load: PageServerLoad = async ({ parent, fetch, params, url }) => {
const userFilter = url.searchParams.get("user"); const userFilter = url.searchParams.get("user");
const resourceFilter = url.searchParams.get("resource"); const resourceFilter = url.searchParams.get("resource");
const operationFilter = url.searchParams.get("operation"); const operationFilter = url.searchParams.get("operation");
const currentPage = Number.parseInt(url.searchParams.get("page") ?? "1"); const currentPage = Number.parseInt(url.searchParams.get("page") ?? "1");
const resultOffset = (currentPage - 1) * 20; const resultOffset = (currentPage - 1) * PAGINATION_MAX_ITEMS;
const searchParams = new URLSearchParams(); const searchParams = new URLSearchParams();
@@ -26,7 +27,7 @@ export const load: PageServerLoad = async ({ parent, fetch, params, url }) => {
searchParams.append("operation", `eq.${operationFilter.toUpperCase()}`); searchParams.append("operation", `eq.${operationFilter.toUpperCase()}`);
} }
const constructedFetchUrl = `${baseFetchUrl}&${searchParams.toString()}&limit=20&offset=${resultOffset}`; const constructedFetchUrl = `${baseFetchUrl}&${searchParams.toString()}&limit=${PAGINATION_MAX_ITEMS}&offset=${resultOffset}`;
const changeLog: (ChangeLog & { user: { username: User["username"] } })[] = ( const changeLog: (ChangeLog & { user: { username: User["username"] } })[] = (
await apiRequest(fetch, constructedFetchUrl, "GET", { await apiRequest(fetch, constructedFetchUrl, "GET", {

View File

@@ -11,6 +11,7 @@
import { enhance } from "$app/forms"; import { enhance } from "$app/forms";
import { sending } from "$lib/runes.svelte"; import { sending } from "$lib/runes.svelte";
import LoadingSpinner from "$lib/components/LoadingSpinner.svelte"; import LoadingSpinner from "$lib/components/LoadingSpinner.svelte";
import Pagination from "$lib/components/Pagination.svelte";
const { data, form }: { data: PageServerData; form: ActionData } = $props(); const { data, form }: { data: PageServerData; form: ActionData } = $props();
@@ -162,79 +163,9 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="pagination"> <Pagination
{#snippet commonFilterInputs()} commonFilters={["user", "resource", "operation"]}
<input type="hidden" name="user" value={$page.url.searchParams.get("user")} /> resultCount={data.resultChangeLogCount}
<input type="hidden" name="resource" value={$page.url.searchParams.get("resource")} />
<input type="hidden" name="operation" value={$page.url.searchParams.get("operation")} />
{/snippet}
<p>
{$page.url.searchParams.get("page") ?? 1} / {Math.max(
Math.ceil(data.resultChangeLogCount / 20),
1
)}
</p>
<form method="GET">
<input type="hidden" name="page" value={1} />
{@render commonFilterInputs()}
<button type="submit" disabled={($page.url.searchParams.get("page") ?? "1") === "1"}
>First</button
>
</form>
<form method="GET">
<input
type="hidden"
name="page"
value={Math.max(1, Number.parseInt($page.url.searchParams.get("page") ?? "1") - 1)}
/> />
{@render commonFilterInputs()}
<button type="submit" disabled={($page.url.searchParams.get("page") ?? "1") === "1"}
>Previous</button
>
</form>
<form method="GET">
<input
type="hidden"
name="page"
value={Math.min(
Math.max(Math.ceil(data.resultChangeLogCount / 20), 1),
Number.parseInt($page.url.searchParams.get("page") ?? "1") + 1
)}
/>
{@render commonFilterInputs()}
<button
type="submit"
disabled={($page.url.searchParams.get("page") ?? "1") ===
Math.max(Math.ceil(data.resultChangeLogCount / 20), 1).toString()}>Next</button
>
</form>
<form method="GET">
<input
type="hidden"
name="page"
value={Math.max(Math.ceil(data.resultChangeLogCount / 20), 1)}
/>
{@render commonFilterInputs()}
<button
type="submit"
disabled={($page.url.searchParams.get("page") ?? "1") ===
Math.max(Math.ceil(data.resultChangeLogCount / 20), 1).toString()}>Last</button
>
</form>
</div>
</section> </section>
</WebsiteEditor> </WebsiteEditor>
<style>
.pagination {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: var(--space-xs);
justify-content: end;
}
.pagination > form:first-of-type {
margin-inline-start: auto;
}
</style>