Compare commits

...

3 Commits

Author SHA1 Message Date
Renovate Bot
e8793380bd Update dependency @tiptap/pm to v3
Some checks failed
Playwright Tests / test (pull_request) Failing after 11s
Build Nginx-based docker image / build-static-assets (push) Failing after 12s
Add copyright notice / copyright_notice (pull_request) Failing after 12s
Check usage of free licenses / build-static-assets (pull_request) Failing after 12s
2025-09-16 22:21:49 +00:00
f6cbc88acd Display simulation progress (#329)
All checks were successful
Check usage of free licenses / build-static-assets (push) Successful in 52s
Build Nginx-based docker image / build-static-assets (push) Successful in 6m15s
Playwright Tests / test (push) Successful in 7m40s
2025-09-16 12:09:17 +00:00
17b9a5e5b4 Display simulation progress
All checks were successful
Add copyright notice / copyright_notice (pull_request) Successful in 3m26s
Check usage of free licenses / build-static-assets (pull_request) Successful in 3m37s
Build Nginx-based docker image / build-static-assets (push) Successful in 40m57s
Playwright Tests / test (pull_request) Successful in 4m16s
2025-09-08 22:47:16 +02:00
7 changed files with 66 additions and 27 deletions

View File

@ -8,7 +8,7 @@ import Arrangement from '@/app/ui/arrangements/arrangement';
import ArrangementsTable from '@/app/ui/arrangements/arrangements-table'; import ArrangementsTable from '@/app/ui/arrangements/arrangements-table';
import { classNames } from '@/app/ui/components/button'; import { classNames } from '@/app/ui/components/button';
import { Toast } from 'primereact/toast'; import { Toast } from 'primereact/toast';
import React, { useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
export default function Page() { export default function Page() {
const toast = useRef<Toast>(null); const toast = useRef<Toast>(null);

View File

@ -13,6 +13,8 @@ export function loadTableSimulations(onLoad?: (tableSimulations: TableArrangemen
name: record.name, name: record.name,
discomfort: record.discomfort, discomfort: record.discomfort,
valid: record.valid, valid: record.valid,
progress: record.progress,
status : record.status
}); });
})); }));
}, (error) => { }, (error) => {

View File

@ -14,6 +14,8 @@ export type TableArrangement = {
guests?: Guest[]; guests?: Guest[];
discomfort?: number; discomfort?: number;
valid?: boolean; valid?: boolean;
progress: number;
status: 'in_progress' | 'completed' | 'not_started';
} }
export type User = { export type User = {

View File

@ -22,10 +22,12 @@ export type Table = {
export class TableSimulation implements Entity { export class TableSimulation implements Entity {
id?: string; id?: string;
tables: Table[]; tables: Table[];
progress: number;
constructor(id?: string, tables?: Table[]) { constructor(id?: string, tables?: Table[], progress?: number) {
this.id = id; this.id = id;
this.tables = tables || []; this.tables = tables || [];
this.progress = progress || 0;
} }
} }
@ -43,7 +45,7 @@ export class TableSimulationSerializer implements Serializable<TableSimulation>
} }
}, },
} }
})); }), data.progress);
} }
toJson(simulation: TableSimulation): string { toJson(simulation: TableSimulation): string {

View File

@ -10,11 +10,21 @@ import { loadTableSimulations } from "@/app/api/tableSimulations";
import { ArchiveBoxXMarkIcon, CheckBadgeIcon } from "@heroicons/react/24/outline"; import { ArchiveBoxXMarkIcon, CheckBadgeIcon } from "@heroicons/react/24/outline";
import { Tooltip } from "primereact/tooltip"; import { Tooltip } from "primereact/tooltip";
import clsx from "clsx"; import clsx from "clsx";
import { ProgressBar } from "primereact/progressbar";
import { useEffect } from "react";
import { TableSimulation, TableSimulationSerializer } from "@/app/lib/tableSimulation";
import { AbstractApi } from "@/app/api/abstract-api";
export default function ArrangementsTable({ onArrangementSelected }: { onArrangementSelected: (arrangementId: string) => void }) { export default function ArrangementsTable({ onArrangementSelected }: { onArrangementSelected: (arrangementId: string) => void }) {
const [arrangements, setArrangements] = useState<Array<TableArrangement>>([]); const [arrangements, setArrangements] = useState<Array<TableArrangement>>([]);
const [arrangementsLoaded, setArrangementsLoaded] = useState(false); const [arrangementsLoaded, setArrangementsLoaded] = useState(false);
useEffect(() => {
refreshSimulations();
const interval = setInterval(refreshSimulations, 10000);
return () => clearInterval(interval);
}, []);
function refreshSimulations() { function refreshSimulations() {
loadTableSimulations((arrangements) => { loadTableSimulations((arrangements) => {
setArrangements(arrangements); setArrangements(arrangements);
@ -26,11 +36,9 @@ export default function ArrangementsTable({ onArrangementSelected }: { onArrange
onArrangementSelected(e.currentTarget.getAttribute('data-arrangement-id') || ''); onArrangementSelected(e.currentTarget.getAttribute('data-arrangement-id') || '');
} }
!arrangementsLoaded && refreshSimulations();
return ( return (
<TableOfContents <TableOfContents
headers={['Name', 'Discomfort', 'Actions', 'Status']} headers={['Name', 'Discomfort', 'Status', 'Actions']}
caption='Simulations' caption='Simulations'
elements={arrangements} elements={arrangements}
rowRender={(arrangement) => ( rowRender={(arrangement) => (
@ -44,17 +52,18 @@ export default function ArrangementsTable({ onArrangementSelected }: { onArrange
<td className="px-6 py-4"> <td className="px-6 py-4">
{arrangement.discomfort} {arrangement.discomfort}
</td> </td>
<td> <td className="px-4">
<button data-arrangement-id={arrangement.id} onClick={arrangementClicked} className={classNames('primary')}>Load</button>
</td>
<td>
<Tooltip target=".tooltip-status" /> <Tooltip target=".tooltip-status" />
{ <>
arrangement.valid ? { arrangement.valid && arrangement.status === 'not_started' && <ProgressBar mode="indeterminate" style={{ height: '6px' }}></ProgressBar> }
<CheckBadgeIcon className='size-6 tooltip-status' data-pr-position="right" data-pr-tooltip="Simulation is valid" /> : { arrangement.valid && arrangement.status !== 'not_started' && <ProgressBar value={(100 * arrangement.progress).toFixed(2) }></ProgressBar> }
<ArchiveBoxXMarkIcon className='size-6 tooltip-status' data-pr-position="right" data-pr-tooltip="Simulation is expired due to attendance or affinity changes" />
} { !arrangement.valid && 'The list of potential guests has changed since this simulation.' }
</>
</td>
<td>
<button data-arrangement-id={arrangement.id} onClick={arrangementClicked} className={classNames('primary')}>Load</button>
</td> </td>
</tr> </tr>
)} )}

View File

@ -9,7 +9,7 @@
"@atlaskit/pragmatic-drag-and-drop": "^1.7.0", "@atlaskit/pragmatic-drag-and-drop": "^1.7.0",
"@heroicons/react": "^2.1.4", "@heroicons/react": "^2.1.4",
"@tailwindcss/forms": "^0.5.7", "@tailwindcss/forms": "^0.5.7",
"@tiptap/pm": "^2.14.0", "@tiptap/pm": "^3.0.0",
"@tiptap/react": "^2.14.0", "@tiptap/react": "^2.14.0",
"@tiptap/starter-kit": "^2.14.0", "@tiptap/starter-kit": "^2.14.0",
"autoprefixer": "10.4.21", "autoprefixer": "10.4.21",

46
pnpm-lock.yaml generated
View File

@ -18,11 +18,11 @@ importers:
specifier: ^0.5.7 specifier: ^0.5.7
version: 0.5.10(tailwindcss@3.4.17) version: 0.5.10(tailwindcss@3.4.17)
'@tiptap/pm': '@tiptap/pm':
specifier: ^2.14.0 specifier: ^3.0.0
version: 2.26.1 version: 3.4.3
'@tiptap/react': '@tiptap/react':
specifier: ^2.14.0 specifier: ^2.14.0
version: 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1)(react-dom@19.0.0-rc-f38c22b244-20240704(react@19.0.0-rc-f38c22b244-20240704))(react@19.0.0-rc-f38c22b244-20240704) version: 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@3.4.3))(@tiptap/pm@3.4.3)(react-dom@19.0.0-rc-f38c22b244-20240704(react@19.0.0-rc-f38c22b244-20240704))(react@19.0.0-rc-f38c22b244-20240704)
'@tiptap/starter-kit': '@tiptap/starter-kit':
specifier: ^2.14.0 specifier: ^2.14.0
version: 2.26.1 version: 2.26.1
@ -497,6 +497,9 @@ packages:
'@tiptap/pm@2.26.1': '@tiptap/pm@2.26.1':
resolution: {integrity: sha512-8aF+mY/vSHbGFqyG663ds84b+vca5Lge3tHdTMTKazxCnhXR9dn2oQJMnZ78YZvdRbkPkMJJHti9h3K7u2UQvw==} resolution: {integrity: sha512-8aF+mY/vSHbGFqyG663ds84b+vca5Lge3tHdTMTKazxCnhXR9dn2oQJMnZ78YZvdRbkPkMJJHti9h3K7u2UQvw==}
'@tiptap/pm@3.4.3':
resolution: {integrity: sha512-WUM9PXwmsC0jZvRl/bgFP0DIMAaPMU0sREefv9leRNiskrG18vl9aZLwG58CPtTybJ/QMS660NnS8Lijhtz69w==}
'@tiptap/react@2.26.1': '@tiptap/react@2.26.1':
resolution: {integrity: sha512-Zxlwzi1iML7aELa+PyysFD2ncVo2mEcjTkhoDok9iTbMGpm1oU8hgR1i6iHrcSNQLfaRiW6M7HNhZZQPKIC9yw==} resolution: {integrity: sha512-Zxlwzi1iML7aELa+PyysFD2ncVo2mEcjTkhoDok9iTbMGpm1oU8hgR1i6iHrcSNQLfaRiW6M7HNhZZQPKIC9yw==}
peerDependencies: peerDependencies:
@ -1611,10 +1614,10 @@ snapshots:
dependencies: dependencies:
'@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1)
'@tiptap/extension-bubble-menu@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1)': '@tiptap/extension-bubble-menu@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@3.4.3))(@tiptap/pm@3.4.3)':
dependencies: dependencies:
'@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1)
'@tiptap/pm': 2.26.1 '@tiptap/pm': 3.4.3
tippy.js: 6.3.7 tippy.js: 6.3.7
'@tiptap/extension-bullet-list@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))': '@tiptap/extension-bullet-list@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))':
@ -1639,10 +1642,10 @@ snapshots:
'@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1)
'@tiptap/pm': 2.26.1 '@tiptap/pm': 2.26.1
'@tiptap/extension-floating-menu@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1)': '@tiptap/extension-floating-menu@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@3.4.3))(@tiptap/pm@3.4.3)':
dependencies: dependencies:
'@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1)
'@tiptap/pm': 2.26.1 '@tiptap/pm': 3.4.3
tippy.js: 6.3.7 tippy.js: 6.3.7
'@tiptap/extension-gapcursor@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1)': '@tiptap/extension-gapcursor@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1)':
@ -1717,12 +1720,33 @@ snapshots:
prosemirror-transform: 1.10.4 prosemirror-transform: 1.10.4
prosemirror-view: 1.40.0 prosemirror-view: 1.40.0
'@tiptap/react@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1)(react-dom@19.0.0-rc-f38c22b244-20240704(react@19.0.0-rc-f38c22b244-20240704))(react@19.0.0-rc-f38c22b244-20240704)': '@tiptap/pm@3.4.3':
dependencies:
prosemirror-changeset: 2.3.1
prosemirror-collab: 1.3.1
prosemirror-commands: 1.7.1
prosemirror-dropcursor: 1.8.2
prosemirror-gapcursor: 1.3.2
prosemirror-history: 1.4.1
prosemirror-inputrules: 1.5.0
prosemirror-keymap: 1.2.3
prosemirror-markdown: 1.13.2
prosemirror-menu: 1.2.5
prosemirror-model: 1.25.2
prosemirror-schema-basic: 1.2.4
prosemirror-schema-list: 1.5.1
prosemirror-state: 1.4.3
prosemirror-tables: 1.7.1
prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.2)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)
prosemirror-transform: 1.10.4
prosemirror-view: 1.40.0
'@tiptap/react@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@3.4.3))(@tiptap/pm@3.4.3)(react-dom@19.0.0-rc-f38c22b244-20240704(react@19.0.0-rc-f38c22b244-20240704))(react@19.0.0-rc-f38c22b244-20240704)':
dependencies: dependencies:
'@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1)
'@tiptap/extension-bubble-menu': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1) '@tiptap/extension-bubble-menu': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@3.4.3))(@tiptap/pm@3.4.3)
'@tiptap/extension-floating-menu': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1) '@tiptap/extension-floating-menu': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@3.4.3))(@tiptap/pm@3.4.3)
'@tiptap/pm': 2.26.1 '@tiptap/pm': 3.4.3
'@types/use-sync-external-store': 0.0.6 '@types/use-sync-external-store': 0.0.6
fast-deep-equal: 3.1.3 fast-deep-equal: 3.1.3
react: 19.0.0-rc-f38c22b244-20240704 react: 19.0.0-rc-f38c22b244-20240704