Skip to content

Commit

Permalink
Implement sort and search in table
Browse files Browse the repository at this point in the history
  • Loading branch information
FyreByrd committed Oct 14, 2024
1 parent daa12b5 commit 55e4aee
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 49 deletions.
1 change: 1 addition & 0 deletions source/SIL.AppBuilder.Portal/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export { default as DatabaseWrites } from './databaseProxy/index.js';
export { readonlyPrisma as prisma } from './prisma.js';
export { Workflow } from './workflow/index.js';
export * as BuildEngine from './build-engine-api/index.js';
export { type Prisma } from '@prisma/client';
6 changes: 3 additions & 3 deletions source/SIL.AppBuilder.Portal/common/workflow/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ export class Workflow {
.reduce((p, c) => p.concat(c), [])
)
).join()
: null,
: '',
TransitionType: ProductTransitionType.Activity,
InitialState: Workflow.stateName(state),
DestinationState: Workflow.targetStringFromEvent(t),
Expand Down Expand Up @@ -419,7 +419,7 @@ export class Workflow {
},
data: {
WorkflowUserId: user?.WorkflowUserId ?? null,
AllowedUserNames: user?.Name ?? null,
AllowedUserNames: user?.Name ?? '',
Command: command ?? null,
DateTransition: new Date(),
Comment: comment ?? null
Expand All @@ -430,7 +430,7 @@ export class Workflow {
data: {
ProductId: this.productId,
WorkflowUserId: user?.WorkflowUserId ?? null,
AllowedUserNames: user?.Name ?? null,
AllowedUserNames: user?.Name ?? '',
InitialState: initialState,
DestinationState: destinationState,
Command: command ?? null,
Expand Down
12 changes: 12 additions & 0 deletions source/SIL.AppBuilder.Portal/src/lib/icons/SearchIcon.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="h-4 w-4 opacity-70"
>
<path
fill-rule="evenodd"
d="M9.965 11.026a5 5 0 1 1 1.06-1.06l2.755 2.754a.75.75 0 1 1-1.06 1.06l-2.755-2.754ZM10.5 7a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0Z"
clip-rule="evenodd"
/>
</svg>
14 changes: 11 additions & 3 deletions source/SIL.AppBuilder.Portal/src/lib/table.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import * as v from 'valibot';

export const paginateSchema = v.object({
export const tableSchema = v.object({
page: v.number(),
size: v.number()
size: v.number(),
sort: v.nullable(v.array(v.object({
field: v.string(),
direction: v.picklist(['asc', 'desc'])
}))),
search: v.object({
field: v.nullable(v.string()),
text: v.nullable(v.string())
})
});

export type PaginateSchema = typeof paginateSchema;
export type TableSchema = typeof tableSchema;
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { prisma } from 'sil.appbuilder.portal.common';
import { prisma, type Prisma } from 'sil.appbuilder.portal.common';
import type { PageServerLoad, Actions } from './$types';
import { paginateSchema } from '$lib/table';
import { tableSchema } from '$lib/table';
import { superValidate } from 'sveltekit-superforms';
import { valibot } from 'sveltekit-superforms/adapters';
import * as m from '$lib/paraglide/messages';

export const load: PageServerLoad = async (event) => {
const transitions = await prisma.productTransitions.findMany({
Expand All @@ -13,12 +14,10 @@ export const load: PageServerLoad = async (event) => {
select: {
AllowedUserNames: true,
InitialState: true,
DestinationState: true,
DateTransition: true,
Command: true,
Comment: true,
Command: true
},
take: 5
take: 20
});

const count = await prisma.productTransitions.count({
Expand All @@ -34,44 +33,123 @@ export const load: PageServerLoad = async (event) => {
form: await superValidate(
{
page: 0,
size: 5
size: 20
},
valibot(paginateSchema)
valibot(tableSchema)
)
};
};

export const actions: Actions = {
page: async function (event) {
const form = await superValidate(event.request, valibot(tableSchema));

const form = await superValidate(event.request, valibot(paginateSchema));
const scripSearch =
form.data.search.text !== null
? (m.appName() as string).match(new RegExp(form.data.search.text))
: false;

const where: Prisma.ProductTransitionsWhereInput = {
ProductId: event.params.product_id,
TransitionType: 1,
OR:
form.data.search.field === null && form.data.search.text
? [
{
OR: scripSearch
? [
{
AllowedUserNames: {
contains: form.data.search.text,
mode: 'insensitive'
}
},
{
OR: [
{
AllowedUserNames: ''
},
{
AllowedUserNames: null
}
]
}
]
: undefined,
AllowedUserNames: scripSearch
? undefined
: {
contains: form.data.search.text,
mode: 'insensitive'
}
},
{
InitialState: {
contains: form.data.search.text,
mode: 'insensitive'
}
},
{
Command: {
contains: form.data.search.text,
mode: 'insensitive'
}
}
]
: form.data.search.field !== null && form.data.search.text && scripSearch
? [
{
AllowedUserNames: {
contains: form.data.search.text,
mode: 'insensitive'
}
},
{
OR: [
{
AllowedUserNames: ''
},
{
AllowedUserNames: null
}
]
}
]
: undefined,
[form.data.search.field!]:
form.data.search.field &&
form.data.search.text &&
!(form.data.search.field === 'AllowedUserNames' && scripSearch)
? {
contains: form.data.search.text,
mode: 'insensitive'
}
: undefined
};

const transitions = await prisma.productTransitions.findMany({
where: {
ProductId: event.params.product_id,
TransitionType: 1
},
orderBy: form.data.sort
? form.data.sort.map((s) => ({
[s.field]: s.direction
}))
: undefined,
where: where,
select: {
AllowedUserNames: true,
InitialState: true,
DestinationState: true,
DateTransition: true,
Command: true,
Comment: true,
Command: true
},
skip: form.data.size * form.data.page,
take: form.data.size
});

const count = await prisma.productTransitions.count({
where: {
ProductId: event.params.product_id,
TransitionType: 1
}
where: where
});

//console.log({ form, query: { data: transitions, count }});

return { form, ok: form.valid, query: { data: transitions, count }};
return { form, ok: form.valid, query: { data: transitions, count } };
}
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
import { writable } from 'svelte/store';
import type { PageData, ActionData } from './$types';
import { createTable, Subscribe, Render } from 'svelte-headless-table';
import type { DataColumnInitBase, DataColumnInitFnAndId } from 'svelte-headless-table';
import { addSortBy, addPagination } from 'svelte-headless-table/plugins';
import SortDirectionPicker from '$lib/components/SortDirectionPicker.svelte';
import { superForm, type FormResult } from 'sveltekit-superforms';
import { onDestroy } from 'svelte';
import SearchIcon from '$lib/icons/SearchIcon.svelte';
export let data: PageData;
Expand All @@ -16,8 +19,7 @@
dataType: 'json',
resetForm: false,
onChange(event) {
//console.log(event);
if (event.paths.includes('page')) {
if (!(event.paths.includes('size') || event.paths.includes('search.text'))) {
submit();
}
},
Expand All @@ -32,44 +34,53 @@
});
const table = createTable(tableData, {
sort: addSortBy(),
sort: addSortBy({
serverSide: true
}),
page: addPagination({
serverSide: true,
serverItemCount: count
})
});
const columns = table.createColumns([
table.column({
type Item = (typeof data.transitions)[0];
type Value = any;
type Extra = { searchable?: boolean };
const columns: (DataColumnInitBase<Item, typeof table.plugins, Value> &
DataColumnInitFnAndId<Item, string, Value> &
Extra)[] = [
{
id: 'InitialState',
header: 'State',
accessor: (t) => t.InitialState
}),
table.column({
},
{
id: 'AllowedUserNames',
header: 'User',
accessor: (t) => t.AllowedUserNames ?? 'Scriptoria'
}),
table.column({
accessor: (t) => t.AllowedUserNames || 'Scriptoria'
},
{
id: 'Command',
header: 'Command',
accessor: (t) => t.Command ?? ''
}),
table.column({
id: 'Date',
},
{
id: 'DateTransition',
header: 'Date',
searchable: false,
accessor: (t) => t.DateTransition,
cell: (c) => c.value?.toLocaleString() ?? ''
})
]);
}
];
const {
headerRows,
pageRows: rows,
tableAttrs,
tableBodyAttrs,
pluginStates
} = table.createViewModel(columns);
} = table.createViewModel(table.createColumns(columns.map((c) => table.column(c))));
const { pageIndex, pageCount, pageSize, hasNextPage, hasPreviousPage } = pluginStates.page;
Expand All @@ -78,21 +89,57 @@
$: pageSize.set($form.size);
$: pageIndex.set($form.page);
$: collapse = $pageCount > 6;
//$: console.log($sortKeys);
function index(i: number, page: number): number {
if (page <= 3) return i + 2;
else if (page > $pageCount - 5) return $pageCount + i - 5;
else return page + i - 1;
}
const sortUnsub = sortKeys.subscribe((keys) => {
console.log(keys);
form.update((data) => ({
...data,
sort: keys.map((k) => ({ field: k.id, direction: k.order }))
}));
});
onDestroy(() => {
sortUnsub();
});
</script>

<div class="w-full">
<h1>{m.admin_workflowInstances_title()}</h1>
<div class="m-4 relative mt-0">
{#if data.transitions.length > 0}
<form method="POST" action="?/page" use:enhance>
<input class="input input-bordered" type="number" name="size" bind:value={$form.size} />
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
<form
method="POST"
action="?/page"
use:enhance
class="flex flex-row gap-2 flex-wrap"
on:keydown={(event) => {
if (event.key === 'Enter') submit();
}}
>
<!-- TODO: Use full-text search in Prisma once it's stable? -->
<select bind:value={$form.search.field} class="select select-bordered w-full max-w-xs">
<option value={null} selected>Anywhere</option>
{#each columns as col}
{#if col.searchable !== false}
<option value={col.id}>{col.header}</option>
{/if}
{/each}
</select>
<span class="input input-bordered flex items-center gap-2 max-w-xs">
<input type="text" name="search.text" bind:value={$form.search.text} />
<SearchIcon />
</span>
<span class="input input-bordered flex items-center gap-2 max-w-xs">
Show: <!-- TODO: i18n -->
<input type="number" name="size" bind:value={$form.size} />
</span>
{#if $pageCount > 1}
<div class="join">
<label
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@
})}
<!-- TODO: The activity name is sometimes showing up blank -->
{m.tasks_forNames({
allowedNames: product.ActiveTransition?.AllowedUserNames ?? 'Scriptoria',
allowedNames: product.ActiveTransition?.AllowedUserNames || m.appName(),
activityName: product.ActiveTransition?.InitialState ?? ''
})}
{#if product.UserTasks.slice(-1)[0]?.UserId === $page.data.session?.user.userId}
Expand Down

0 comments on commit 55e4aee

Please sign in to comment.