Skip to content

Commit b511729

Browse files
authored
Merge pull request #30 from itk-dev/feature/5708-pagination-has-arrived
Add pagination to process overview
2 parents 2ad246e + ac0fc4c commit b511729

File tree

17 files changed

+250
-50
lines changed

17 files changed

+250
-50
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
* [PR-30](https://github.com/itk-dev/rpa-process-overview/pull/30)
11+
Add pagination
1012
* [PR-27](https://github.com/itk-dev/rpa-process-overview/pull/27)
1113
Added pre-defined users in OIDC mock
1214
* [PR-26](https://github.com/itk-dev/rpa-process-overview/pull/26)

fixtures/process_overview.yaml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ App\Entity\ProcessOverview:
4040
data:
4141
default_query:
4242
status: failed
43+
page_size: 7
44+
search:
45+
minimum_search_query_length: 3
4346
4447
process_overview_2:
4548
label: Frit valg 0-15 år
@@ -54,6 +57,10 @@ App\Entity\ProcessOverview:
5457
data: meta.name
5558
- label: Klinik
5659
data: meta.branch
60+
data:
61+
page_size: 3
62+
search:
63+
minimum_search_query_length: 2
5764
5865
process_overview_3:
5966
label: En anden proces (failed)
@@ -69,7 +76,9 @@ App\Entity\ProcessOverview:
6976
data:
7077
default_query:
7178
status: failed
72-
79+
page_size: 3
80+
search:
81+
minimum_search_query_length: 2
7382
process_overview_4:
7483
label: En anden proces (pending)
7584
group: "@process_overview_group_2"
@@ -84,6 +93,9 @@ App\Entity\ProcessOverview:
8493
data:
8594
default_query:
8695
status: pending
96+
page_size: 5
97+
search:
98+
minimum_search_query_length: 3
8799
88100
process_overview_incomplete:
89101
label: Incomplete (missing process ID)

src/Controller/ProcessOverviewController.php

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
use Symfony\Component\HttpFoundation\Response;
1212
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
1313
use Symfony\Component\Routing\Attribute\Route;
14+
use Symfony\Component\Yaml\Yaml;
15+
16+
use function Symfony\Component\Translation\t;
1417

1518
#[Route('/group/{group}/overview', name: 'process_overview_')]
1619
final class ProcessOverviewController extends AbstractController
@@ -22,16 +25,39 @@ public function show(ProcessOverviewGroup $group, ProcessOverview $overview): Re
2225
throw new BadRequestHttpException();
2326
}
2427

28+
$overviewOptions = Yaml::parse($overview->getOptions() ?? '');
29+
2530
return $this->render('process_overview/show.html.twig', [
2631
'overview' => $overview,
27-
'data_url' => $this->generateUrl('process_overview_data', [
28-
'group' => $group->getId(),
29-
'overview' => $overview->getId(),
30-
]),
31-
'search_url' => $this->generateUrl('process_overview_search', [
32-
'group' => $group->getId(),
33-
'overview' => $overview->getId(),
34-
]),
32+
'overview_config' => [
33+
'data_url' => $this->generateUrl('process_overview_data', [
34+
'group' => $group->getId(),
35+
'overview' => $overview->getId(),
36+
]),
37+
'messages' => array_map('strval', [
38+
'Go to previous page' => strval(t('Go to previous page')),
39+
'Go to page' => t('Go to page'),
40+
'Go to next page' => t('Go to next page'),
41+
'Missing data' => t('Missing data'),
42+
'Failed processes' => t('Failed processes'),
43+
'Loading data...' => t('Loading data...'),
44+
'of' => t('of'),
45+
'An error occurred while fetching the data' => t('An error occurred while fetching the data'),
46+
]),
47+
'page_size' => $overviewOptions['data']['page_size'] ?? 5,
48+
],
49+
'search_config' => [
50+
'search_url' => $this->generateUrl('process_overview_search', [
51+
'group' => $group->getId(),
52+
'overview' => $overview->getId(),
53+
]),
54+
'minimum_search_query_length' => $overviewOptions['search']['minimum_search_query_length'] ?? 2,
55+
'messages' => array_map('strval', [
56+
'Citizen search' => t('Citizen search'),
57+
'Citizen information' => t('Citizen information'),
58+
'An error occurred while searching' => t('An error occurred while searching'),
59+
]),
60+
],
3561
]);
3662
}
3763

templates/_partials/group_header.html.twig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<ul class="flex flex-wrap {{ overview is defined ? 'nav-tabs' }}">
77
{% for overview in group.overviews %}
88
{% set is_active = overview == loop.parent.overview|default(null) %}
9-
<li class="cursor-pointer transition-all duration-200 border-b-2 px-4 border-gray-300 py-2 hover:border-gray-600 dark:text-gray-400 text-gray-800 hover:text-gray-600 dark:hover:text-gray-200 {{ is_active ? 'text-black dark:text-white border-rose-400' }}">
9+
<li class="cursor-pointer transition-all duration-200 border-b-2 px-4 border-gray-300 py-2 hover:border-gray-600 dark:text-gray-400 text-gray-800 hover:text-gray-600 dark:hover:text-gray-200 {{ is_active ? 'text-black dark:text-white border-violet-600' }}">
1010
<a class="flex items-center text-sm font-medium {{ is_active ? 'active' }}" {{ is_active ? 'aria-current="page"' }} href="{{ path('process_overview_show', {group: group.id, overview: overview.id}) }}">{{ overview.label }}</a>
1111
</li>
1212
{% endfor %}

templates/process_overview/show.html.twig

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,6 @@
55
{% block content %}
66
{{ include('_partials/group_header.html.twig', {group: overview.group, overview: overview}) }}
77

8-
{% set overview_messages = {
9-
'Missing data': 'Missing data'|trans,
10-
'Failed processes': 'Failed processes'|trans,
11-
'Loading data...': 'Loading data...'|trans,
12-
'An error occurred while fetching the data': 'An error occurred while fetching the data'|trans,
13-
} %}
14-
15-
{% set search_messages = {
16-
'Citizen search': 'Citizen search'|trans,
17-
'Citizen information': 'Citizen information'|trans,
18-
'An error occurred while searching': 'An error occurred while searching'|trans,
19-
Name: 'Name'|trans,
20-
} %}
21-
22-
{% set overview_config = {
23-
messages: overview_messages,
24-
data_url: data_url,
25-
} %}
26-
27-
{# todo search limit from config #}
28-
{% set search_config = {
29-
messages: search_messages,
30-
search_url: search_url,
31-
min_search_limit: 3,
32-
} %}
33-
348
{# https://standalone.brenoliradev.com/embed.html#auto-embed-with-target #}
359
<div id="ProcessOverview" data-rpa-process-overview-config="{{ overview_config|json_encode }}"></div>
3610
<script src="/widgets/ProcessOverview.min.js"></script>
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<script lang="ts">
2+
import LeftArrow from '../../lib/Icons/LeftArrow.svelte';
3+
import RightArrow from '../../lib/Icons/RightArrow.svelte';
4+
import { t } from './config';
5+
import PaginationButton from './PaginationButton.svelte';
6+
7+
let {
8+
total,
9+
size,
10+
page,
11+
changePage
12+
}: {
13+
size: number;
14+
page: number;
15+
total: number;
16+
changePage: Function;
17+
} = $props();
18+
const totalAmountOfButtons: number = total / size;
19+
const totalAmountOfPagesAsIntegerArray = Array.from(
20+
{ length: totalAmountOfButtons },
21+
(_, i) => i + 1
22+
);
23+
24+
function isThisTheLastPage(): boolean {
25+
return page === totalAmountOfButtons;
26+
}
27+
28+
function isThisTheFirstPage(): boolean {
29+
return page === 1;
30+
}
31+
</script>
32+
33+
<div
34+
class="flex items-center overflow-y-scroll justify-between border-t border-neutral-400 dark:border-neutral-700 px-4 py-3"
35+
>
36+
<div class="min-w-[200px] flex items-center text-sm dark:text-gray-300">
37+
<!-- Todo find a way to handle placeholders -->
38+
<span>{size * page - size}-{size * page} {t('of')} {total}</span>
39+
</div>
40+
<div class="flex items-center space-x-2">
41+
<PaginationButton
42+
disabled={isThisTheFirstPage()}
43+
label={t('Go to previous page')}
44+
id={'prev'}
45+
changePage={() => changePage(page - 1)}
46+
>
47+
<LeftArrow />
48+
</PaginationButton>
49+
<div class="flex items-center space-x-1">
50+
{#each totalAmountOfPagesAsIntegerArray as index}
51+
<PaginationButton
52+
selected={page === index}
53+
label={t('Go to page')}
54+
id={String(index)}
55+
changePage={() => changePage(index)}>{index}</PaginationButton
56+
>
57+
{/each}
58+
</div>
59+
60+
<PaginationButton
61+
disabled={isThisTheLastPage()}
62+
label={t('Go to next page')}
63+
id="next"
64+
changePage={() => changePage(page + 1)}
65+
>
66+
<RightArrow />
67+
</PaginationButton>
68+
</div>
69+
</div>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<script lang="ts">
2+
import type { Snippet } from 'svelte';
3+
4+
let {
5+
id,
6+
changePage,
7+
label,
8+
selected = false,
9+
disabled = false,
10+
children
11+
}: {
12+
id: string;
13+
label: string;
14+
changePage: Function;
15+
selected?: boolean;
16+
disabled?: boolean;
17+
children: Snippet;
18+
} = $props();
19+
</script>
20+
21+
<span class="sr-only" id={`go-to-page-${id}`}>{label}</span>
22+
<button
23+
aria-describedby={`go-to-page-${id}`}
24+
onclick={() => changePage()}
25+
{disabled}
26+
class="cursor-pointer flex disabled:text-gray-300 dark:disabled:text-gray-700 hover:disabled:bg-transparent justify-center items-center h-8 w-8 rounded-md text-gray-800 dark:text-gray-300 dark:hover:bg-violet-600 hover:bg-violet-300 text-gray-100 {selected
27+
? 'bg-violet-600 text-white'
28+
: ''}"
29+
>
30+
{@render children()}
31+
</button>

widgets/src/_standalone/ProcessOverview/index.svelte

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,42 @@
55
import Table from '$lib/Table.svelte';
66
import { t, config } from './config';
77
import ErrorBanner from '$lib/ErrorBanner.svelte';
8+
import Pagination from './Pagination.svelte';
9+
10+
const { page_size, data_url } = config;
811
912
let error: boolean = $state(false);
1013
let data: ProgressData | null = $state(null);
11-
let total: Number | null = $state(null);
12-
let fetching = $state(true);
14+
let total: number | null = $state(null);
15+
let fetching: boolean = $state(true);
1316
let errorMessage: string = $state('');
17+
let size: number = $state(parseInt(page_size));
18+
let page: number = $state(getCurrentPage());
19+
20+
function getCurrentPage(): number {
21+
const url = new URL(document.location.href);
22+
const page = Number(url.searchParams.get('page')) || null;
23+
return page ?? 1;
24+
}
25+
26+
function updateUrl(): void {
27+
const pageUrl = new URL(document.location.href);
28+
pageUrl.searchParams.set('page', String(page));
29+
history.replaceState({}, '', pageUrl);
30+
}
31+
32+
function changePage(index: number): void {
33+
page = index;
34+
}
1435
1536
$effect(() => {
37+
updateUrl();
1638
fetching = true;
1739
errorMessage = '';
1840
error = false;
19-
const url = new URL(config.data_url, document.location.href);
41+
const url = new URL(data_url, document.location.href);
42+
url.searchParams.set('page', String(page));
43+
url.searchParams.set('size', String(size));
2044
fetch(url.toString())
2145
.then((response) => response.json())
2246
.then(({ data: recievedData, meta }) => {
@@ -27,8 +51,8 @@
2751
fetching = false;
2852
})
2953
.catch((e) => {
30-
fetching = false;
3154
console.error(e);
55+
fetching = false;
3256
error = true;
3357
errorMessage = t('An error occurred while fetching the data');
3458
});
@@ -58,8 +82,11 @@
5882
>{total ?? '?'}</span
5983
>
6084
</div>
61-
<div class="p-4">
85+
<div class="p-4 min-h-[450px] flex flex-col justify-between">
6286
<Table columns={data.columns} rows={data.rows}></Table>
87+
{#if total !== null}
88+
<Pagination {total} {changePage} {size} {page} />
89+
{/if}
6390
</div>
6491
</div>
6592
{/if}

widgets/src/_standalone/ProcessSearch/index.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
const DEBOUNCE_DELAY: number = 500;
1414
let name: string = $state('');
1515
let data: ProgressData | null = $state(null);
16-
const { search_url, min_search_limit } = config;
16+
const { search_url, minimum_search_query_length } = config;
1717
let timer: ReturnType<typeof setTimeout>;
1818
1919
$effect(() => {
@@ -24,7 +24,7 @@
2424
2525
// Debounce setTimeout
2626
timer = setTimeout(() => {
27-
if (parsedQuery && parsedQuery.length >= min_search_limit) {
27+
if (parsedQuery && parsedQuery.length >= minimum_search_query_length) {
2828
const url = new URL(search_url, document.location.href);
2929
fetch(url.toString())
3030
.then((response) => response.json())
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<script lang="ts">
2+
export let className = 'h-4 w-4';
3+
</script>
4+
5+
<svg
6+
xmlns="http://www.w3.org/2000/svg"
7+
fill="none"
8+
viewBox="0 0 24 24"
9+
stroke-width="1.5"
10+
stroke="currentColor"
11+
class={className}
12+
>
13+
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
14+
</svg>

0 commit comments

Comments
 (0)