From 402b01790daeecfa93ab6e302d9aafac0b8cd271 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Mon, 14 Oct 2024 12:06:57 -0400 Subject: [PATCH] Implement sort and search in table --- source/SIL.AppBuilder.Portal/common/index.ts | 1 + .../common/workflow/index.ts | 4 +- .../src/lib/icons/SearchIcon.svelte | 12 ++ source/SIL.AppBuilder.Portal/src/lib/table.ts | 14 +- .../[product_id]/transitions/+page.server.ts | 124 ++++++++++++++---- .../[product_id]/transitions/+page.svelte | 85 +++++++++--- .../projects/[id=idNumber]/+page.svelte | 2 +- 7 files changed, 194 insertions(+), 48 deletions(-) create mode 100644 source/SIL.AppBuilder.Portal/src/lib/icons/SearchIcon.svelte diff --git a/source/SIL.AppBuilder.Portal/common/index.ts b/source/SIL.AppBuilder.Portal/common/index.ts index 0ad9a043c..fd43036ad 100644 --- a/source/SIL.AppBuilder.Portal/common/index.ts +++ b/source/SIL.AppBuilder.Portal/common/index.ts @@ -3,3 +3,4 @@ export { scriptoriaQueue } from './bullmq.js'; export { default as DatabaseWrites } from './databaseProxy/index.js'; export { readonlyPrisma as prisma } from './prisma.js'; export { Workflow } from './workflow/index.js'; +export { type Prisma } from '@prisma/client'; diff --git a/source/SIL.AppBuilder.Portal/common/workflow/index.ts b/source/SIL.AppBuilder.Portal/common/workflow/index.ts index 56147ac07..896310fe8 100644 --- a/source/SIL.AppBuilder.Portal/common/workflow/index.ts +++ b/source/SIL.AppBuilder.Portal/common/workflow/index.ts @@ -480,7 +480,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 @@ -491,7 +491,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, diff --git a/source/SIL.AppBuilder.Portal/src/lib/icons/SearchIcon.svelte b/source/SIL.AppBuilder.Portal/src/lib/icons/SearchIcon.svelte new file mode 100644 index 000000000..b8e110d57 --- /dev/null +++ b/source/SIL.AppBuilder.Portal/src/lib/icons/SearchIcon.svelte @@ -0,0 +1,12 @@ + + + diff --git a/source/SIL.AppBuilder.Portal/src/lib/table.ts b/source/SIL.AppBuilder.Portal/src/lib/table.ts index 5239251b1..acbf379ec 100644 --- a/source/SIL.AppBuilder.Portal/src/lib/table.ts +++ b/source/SIL.AppBuilder.Portal/src/lib/table.ts @@ -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; \ No newline at end of file +export type TableSchema = typeof tableSchema; \ No newline at end of file diff --git a/source/SIL.AppBuilder.Portal/src/routes/(authenticated)/admin/workflows/[product_id]/transitions/+page.server.ts b/source/SIL.AppBuilder.Portal/src/routes/(authenticated)/admin/workflows/[product_id]/transitions/+page.server.ts index bde2baf1f..d24e88e99 100644 --- a/source/SIL.AppBuilder.Portal/src/routes/(authenticated)/admin/workflows/[product_id]/transitions/+page.server.ts +++ b/source/SIL.AppBuilder.Portal/src/routes/(authenticated)/admin/workflows/[product_id]/transitions/+page.server.ts @@ -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({ @@ -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({ @@ -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 } }; } -} +}; diff --git a/source/SIL.AppBuilder.Portal/src/routes/(authenticated)/admin/workflows/[product_id]/transitions/+page.svelte b/source/SIL.AppBuilder.Portal/src/routes/(authenticated)/admin/workflows/[product_id]/transitions/+page.svelte index c65c25732..3a60011d9 100644 --- a/source/SIL.AppBuilder.Portal/src/routes/(authenticated)/admin/workflows/[product_id]/transitions/+page.svelte +++ b/source/SIL.AppBuilder.Portal/src/routes/(authenticated)/admin/workflows/[product_id]/transitions/+page.svelte @@ -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; @@ -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(); } }, @@ -32,36 +34,45 @@ }); 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 & + DataColumnInitFnAndId & + 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, @@ -69,7 +80,7 @@ tableAttrs, tableBodyAttrs, pluginStates - } = table.createViewModel(columns); + } = table.createViewModel(table.createColumns(columns.map((c) => table.column(c)))); const { pageIndex, pageCount, pageSize, hasNextPage, hasPreviousPage } = pluginStates.page; @@ -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(); + });

{m.admin_workflowInstances_title()}

{#if data.transitions.length > 0} -
- + + { + if (event.key === 'Enter') submit(); + }} + > + + + + + + + + Show: + + {#if $pageCount > 1}