diff --git a/apps/api/src/instrument-records/instrument-records.controller.ts b/apps/api/src/instrument-records/instrument-records.controller.ts index f15c11aac..a642482b2 100644 --- a/apps/api/src/instrument-records/instrument-records.controller.ts +++ b/apps/api/src/instrument-records/instrument-records.controller.ts @@ -92,4 +92,11 @@ export class InstrumentRecordsController { ) { return this.instrumentRecordsService.updateById(id, data, { ability }); } + + @ApiOperation({ summary: 'Get Instrument Record' }) + @Get(':id') + @RouteAccess({ action: 'read', subject: 'InstrumentRecord' }) + findById(@Param('id', ValidObjectIdPipe) id: string, @CurrentUser('ability') ability: AppAbility) { + return this.instrumentRecordsService.findById(id, { ability }); + } } diff --git a/apps/api/src/instrument-records/instrument-records.service.ts b/apps/api/src/instrument-records/instrument-records.service.ts index 4d0dcbc35..91852f3ee 100644 --- a/apps/api/src/instrument-records/instrument-records.service.ts +++ b/apps/api/src/instrument-records/instrument-records.service.ts @@ -254,6 +254,16 @@ export class InstrumentRecordsService { return records; } + async findById(id: string, { ability }: EntityOperationOptions = {}) { + const record = await this.instrumentRecordModel.findFirst({ + where: { AND: [accessibleQuery(ability, 'read', 'InstrumentRecord')], id } + }); + if (!record) { + throw new NotFoundException(); + } + return record; + } + async linearModel( { groupId, instrumentId }: { groupId?: string; instrumentId: string }, { ability }: EntityOperationOptions = {} diff --git a/apps/web/package.json b/apps/web/package.json index 22417aeea..3229faa76 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -46,6 +46,7 @@ "lucide-react": "^0.507.0", "motion": "catalog:", "papaparse": "workspace:papaparse__5.x@*", + "qrcode": "^1.5.4", "react": "workspace:react__19.x@*", "react-dom": "workspace:react-dom__19.x@*", "recharts": "^2.15.2", @@ -65,6 +66,7 @@ "@tanstack/router-plugin": "^1.127.3", "@testing-library/dom": "^10.4.0", "@testing-library/react": "16.2.0", + "@types/qrcode": "^1.5.6", "@vitejs/plugin-react-swc": "^3.9.0", "happy-dom": "catalog:", "tailwindcss": "catalog:", diff --git a/apps/web/src/components/QRCode.tsx b/apps/web/src/components/QRCode.tsx new file mode 100644 index 000000000..0a7634951 --- /dev/null +++ b/apps/web/src/components/QRCode.tsx @@ -0,0 +1,32 @@ +import { useEffect, useRef } from 'react'; + +import { useTheme } from '@douglasneuroinformatics/libui/hooks'; +import qrcode from 'qrcode'; + +export const QRCode = ({ url }: { url: string }) => { + const ref = useRef(null); + + const [theme] = useTheme(); + + useEffect(() => { + qrcode.toCanvas( + ref.current, + url, + { + color: { + dark: theme === 'dark' ? '#f1f5f9' : '#0f172a', + light: '#0000' + }, + margin: 2, + scale: 6 + }, + (error) => { + if (error) { + console.error(error); + } + } + ); + }, [theme, url]); + + return ; +}; diff --git a/apps/web/src/hooks/useInstrumentRecordQuery.ts b/apps/web/src/hooks/useInstrumentRecordQuery.ts new file mode 100644 index 000000000..94b1a616d --- /dev/null +++ b/apps/web/src/hooks/useInstrumentRecordQuery.ts @@ -0,0 +1,17 @@ +import { $InstrumentRecord } from '@opendatacapture/schemas/instrument-records'; +import { queryOptions, useSuspenseQuery } from '@tanstack/react-query'; +import axios from 'axios'; + +export const instrumentRecordQueryOptions = ({ params }: { params: { id: string } }) => { + return queryOptions({ + queryFn: async () => { + const response = await axios.get(`/v1/instrument-records/${params.id}`); + return $InstrumentRecord.parse(response.data); + }, + queryKey: ['instrument-records', `id-${params.id}`] + }); +}; + +export function useInstrumentRecordQuery({ params }: { params: { id: string } }) { + return useSuspenseQuery(instrumentRecordQueryOptions({ params })); +} diff --git a/apps/web/src/hooks/useInstrumentVisualization.ts b/apps/web/src/hooks/useInstrumentVisualization.ts index 8da1fe142..28ec5f427 100644 --- a/apps/web/src/hooks/useInstrumentVisualization.ts +++ b/apps/web/src/hooks/useInstrumentVisualization.ts @@ -18,6 +18,7 @@ import { useFindSessionQuery } from './useFindSessionQuery'; type InstrumentVisualizationRecord = { [key: string]: unknown; __date__: Date; + __id__: string; __time__: number; }; @@ -76,7 +77,7 @@ export function useInstrumentVisualization({ params }: UseInstrumentVisualizatio instrument.internal.edition }_${new Date().toISOString()}`; - const exportRecords = records.map((record) => omit(record, ['__time__'])); + const exportRecords = records.map((record) => omit(record, ['__time__', '__id__'])); const makeWideRows = () => { const columnNames = Object.keys(exportRecords[0]!); @@ -229,6 +230,7 @@ export function useInstrumentVisualization({ params }: UseInstrumentVisualizatio return { __date__: record.date, + __id__: record.id, __time__: record.date.getTime(), username: username, ...record.computedMeasures, diff --git a/apps/web/src/hooks/useSubjectQuery.ts b/apps/web/src/hooks/useSubjectQuery.ts new file mode 100644 index 000000000..8769393bf --- /dev/null +++ b/apps/web/src/hooks/useSubjectQuery.ts @@ -0,0 +1,21 @@ +import { $Subject } from '@opendatacapture/schemas/subject'; +import { queryOptions, useSuspenseQuery } from '@tanstack/react-query'; +import axios from 'axios'; + +type SubjectQueryParams = { + id: string; +}; + +export const subjectQueryOptions = ({ params }: { params: SubjectQueryParams }) => { + return queryOptions({ + queryFn: async () => { + const response = await axios.get(`/v1/subjects/${params.id}`, { params }); + return $Subject.parseAsync(response.data); + }, + queryKey: ['subjects', `id-${params.id}`] + }); +}; + +export function useSubjectQuery({ params }: { params: SubjectQueryParams }) { + return useSuspenseQuery(subjectQueryOptions({ params })); +} diff --git a/apps/web/src/route-tree.ts b/apps/web/src/route-tree.ts index 54372f031..352561c91 100644 --- a/apps/web/src/route-tree.ts +++ b/apps/web/src/route-tree.ts @@ -31,6 +31,7 @@ import { Route as AppInstrumentsRenderIdRouteImport } from './routes/_app/instru import { Route as AppDatahubSubjectIdTableRouteImport } from './routes/_app/datahub/$subjectId/table' import { Route as AppDatahubSubjectIdGraphRouteImport } from './routes/_app/datahub/$subjectId/graph' import { Route as AppDatahubSubjectIdAssignmentsRouteImport } from './routes/_app/datahub/$subjectId/assignments' +import { Route as AppDatahubSubjectIdRecordIdRouteImport } from './routes/_app/datahub/$subjectId/$recordId' import { Route as AppAdminUsersCreateRouteImport } from './routes/_app/admin/users/create' import { Route as AppAdminGroupsCreateRouteImport } from './routes/_app/admin/groups/create' @@ -148,6 +149,12 @@ const AppDatahubSubjectIdAssignmentsRoute = path: '/assignments', getParentRoute: () => AppDatahubSubjectIdRouteRoute, } as any) +const AppDatahubSubjectIdRecordIdRoute = + AppDatahubSubjectIdRecordIdRouteImport.update({ + id: '/$recordId', + path: '/$recordId', + getParentRoute: () => AppDatahubSubjectIdRouteRoute, + } as any) const AppAdminUsersCreateRoute = AppAdminUsersCreateRouteImport.update({ id: '/admin/users/create', path: '/admin/users/create', @@ -177,6 +184,7 @@ export interface FileRoutesByFullPath { '/upload': typeof AppUploadIndexRoute '/admin/groups/create': typeof AppAdminGroupsCreateRoute '/admin/users/create': typeof AppAdminUsersCreateRoute + '/datahub/$subjectId/$recordId': typeof AppDatahubSubjectIdRecordIdRoute '/datahub/$subjectId/assignments': typeof AppDatahubSubjectIdAssignmentsRoute '/datahub/$subjectId/graph': typeof AppDatahubSubjectIdGraphRoute '/datahub/$subjectId/table': typeof AppDatahubSubjectIdTableRoute @@ -202,6 +210,7 @@ export interface FileRoutesByTo { '/upload': typeof AppUploadIndexRoute '/admin/groups/create': typeof AppAdminGroupsCreateRoute '/admin/users/create': typeof AppAdminUsersCreateRoute + '/datahub/$subjectId/$recordId': typeof AppDatahubSubjectIdRecordIdRoute '/datahub/$subjectId/assignments': typeof AppDatahubSubjectIdAssignmentsRoute '/datahub/$subjectId/graph': typeof AppDatahubSubjectIdGraphRoute '/datahub/$subjectId/table': typeof AppDatahubSubjectIdTableRoute @@ -229,6 +238,7 @@ export interface FileRoutesById { '/_app/upload/': typeof AppUploadIndexRoute '/_app/admin/groups/create': typeof AppAdminGroupsCreateRoute '/_app/admin/users/create': typeof AppAdminUsersCreateRoute + '/_app/datahub/$subjectId/$recordId': typeof AppDatahubSubjectIdRecordIdRoute '/_app/datahub/$subjectId/assignments': typeof AppDatahubSubjectIdAssignmentsRoute '/_app/datahub/$subjectId/graph': typeof AppDatahubSubjectIdGraphRoute '/_app/datahub/$subjectId/table': typeof AppDatahubSubjectIdTableRoute @@ -256,6 +266,7 @@ export interface FileRouteTypes { | '/upload' | '/admin/groups/create' | '/admin/users/create' + | '/datahub/$subjectId/$recordId' | '/datahub/$subjectId/assignments' | '/datahub/$subjectId/graph' | '/datahub/$subjectId/table' @@ -281,6 +292,7 @@ export interface FileRouteTypes { | '/upload' | '/admin/groups/create' | '/admin/users/create' + | '/datahub/$subjectId/$recordId' | '/datahub/$subjectId/assignments' | '/datahub/$subjectId/graph' | '/datahub/$subjectId/table' @@ -307,6 +319,7 @@ export interface FileRouteTypes { | '/_app/upload/' | '/_app/admin/groups/create' | '/_app/admin/users/create' + | '/_app/datahub/$subjectId/$recordId' | '/_app/datahub/$subjectId/assignments' | '/_app/datahub/$subjectId/graph' | '/_app/datahub/$subjectId/table' @@ -477,6 +490,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AppDatahubSubjectIdAssignmentsRouteImport parentRoute: typeof AppDatahubSubjectIdRouteRoute } + '/_app/datahub/$subjectId/$recordId': { + id: '/_app/datahub/$subjectId/$recordId' + path: '/$recordId' + fullPath: '/datahub/$subjectId/$recordId' + preLoaderRoute: typeof AppDatahubSubjectIdRecordIdRouteImport + parentRoute: typeof AppDatahubSubjectIdRouteRoute + } '/_app/admin/users/create': { id: '/_app/admin/users/create' path: '/admin/users/create' @@ -495,6 +515,7 @@ declare module '@tanstack/react-router' { } interface AppDatahubSubjectIdRouteRouteChildren { + AppDatahubSubjectIdRecordIdRoute: typeof AppDatahubSubjectIdRecordIdRoute AppDatahubSubjectIdAssignmentsRoute: typeof AppDatahubSubjectIdAssignmentsRoute AppDatahubSubjectIdGraphRoute: typeof AppDatahubSubjectIdGraphRoute AppDatahubSubjectIdTableRoute: typeof AppDatahubSubjectIdTableRoute @@ -502,6 +523,7 @@ interface AppDatahubSubjectIdRouteRouteChildren { const AppDatahubSubjectIdRouteRouteChildren: AppDatahubSubjectIdRouteRouteChildren = { + AppDatahubSubjectIdRecordIdRoute: AppDatahubSubjectIdRecordIdRoute, AppDatahubSubjectIdAssignmentsRoute: AppDatahubSubjectIdAssignmentsRoute, AppDatahubSubjectIdGraphRoute: AppDatahubSubjectIdGraphRoute, AppDatahubSubjectIdTableRoute: AppDatahubSubjectIdTableRoute, diff --git a/apps/web/src/routes/_app/datahub/$subjectId/$recordId.tsx b/apps/web/src/routes/_app/datahub/$subjectId/$recordId.tsx new file mode 100644 index 000000000..66a9b36de --- /dev/null +++ b/apps/web/src/routes/_app/datahub/$subjectId/$recordId.tsx @@ -0,0 +1,41 @@ +import { InstrumentSummary } from '@opendatacapture/react-core'; +import { createFileRoute } from '@tanstack/react-router'; + +import { useInstrument } from '@/hooks/useInstrument'; +import { instrumentRecordQueryOptions, useInstrumentRecordQuery } from '@/hooks/useInstrumentRecordQuery'; +import { subjectQueryOptions, useSubjectQuery } from '@/hooks/useSubjectQuery'; + +const RouteComponent = () => { + const recordId = Route.useParams({ select: (params) => params.recordId }); + + const { data: instrumentRecord } = useInstrumentRecordQuery({ params: { id: recordId } }); + const { data: subject } = useSubjectQuery({ params: { id: instrumentRecord.subjectId } }); + + const instrument = useInstrument(instrumentRecord.instrumentId); + + if (!instrument) { + return null; + } + + return ( +
+ +
+ ); +}; + +export const Route = createFileRoute('/_app/datahub/$subjectId/$recordId')({ + component: RouteComponent, + loader: async ({ context, params }) => { + const record = await context.queryClient.ensureQueryData( + instrumentRecordQueryOptions({ params: { id: params.recordId } }) + ); + await context.queryClient.ensureQueryData(subjectQueryOptions({ params: { id: record.subjectId } })); + } +}); diff --git a/apps/web/src/routes/_app/datahub/$subjectId/assignments.tsx b/apps/web/src/routes/_app/datahub/$subjectId/assignments.tsx index 1b428be9d..f2443d27f 100644 --- a/apps/web/src/routes/_app/datahub/$subjectId/assignments.tsx +++ b/apps/web/src/routes/_app/datahub/$subjectId/assignments.tsx @@ -21,6 +21,7 @@ import type { UnilingualInstrumentInfo } from '@opendatacapture/schemas/instrume import { createFileRoute } from '@tanstack/react-router'; import { z } from 'zod/v4'; +import { QRCode } from '@/components/QRCode'; import { useAssignmentsQuery } from '@/hooks/useAssignmentsQuery'; import { useCreateAssignment } from '@/hooks/useCreateAssignment'; import { useInstrument } from '@/hooks/useInstrument'; @@ -40,26 +41,25 @@ const AssignmentSlider: React.FC<{ const instrument = useInstrument(assignment?.instrumentId ?? null); return ( - + {instrument?.details.title} {t('datahub.assignments.assignmentSliderDesc')} - {instrument && ( -
- -
- - -
+
); diff --git a/apps/web/src/routes/_app/datahub/index.tsx b/apps/web/src/routes/_app/datahub/index.tsx index d0a828bf2..4dd2f83ed 100644 --- a/apps/web/src/routes/_app/datahub/index.tsx +++ b/apps/web/src/routes/_app/datahub/index.tsx @@ -174,7 +174,7 @@ const RouteComponent = () => { { - void navigate({ to: `./${subject.id}/assignments` }); + void navigate({ to: `./${subject.id}/table` }); }} /> diff --git a/packages/react-core/src/components/InstrumentSummary/InstrumentSummary.tsx b/packages/react-core/src/components/InstrumentSummary/InstrumentSummary.tsx index 5809433a8..acbc85feb 100644 --- a/packages/react-core/src/components/InstrumentSummary/InstrumentSummary.tsx +++ b/packages/react-core/src/components/InstrumentSummary/InstrumentSummary.tsx @@ -14,12 +14,19 @@ import type { SubjectDisplayInfo } from '../../types'; export type InstrumentSummaryProps = { data: any; + displayAllMeasures?: boolean; instrument: AnyUnilingualInstrument; subject?: SubjectDisplayInfo; timeCollected: number; }; -export const InstrumentSummary = ({ data, instrument, subject, timeCollected }: InstrumentSummaryProps) => { +export const InstrumentSummary = ({ + data, + displayAllMeasures, + instrument, + subject, + timeCollected +}: InstrumentSummaryProps) => { const download = useDownload(); const { resolvedLanguage, t } = useTranslation(); @@ -28,6 +35,9 @@ export const InstrumentSummary = ({ data, instrument, subject, timeCollected }: } const computedMeasures = filter(computeInstrumentMeasures(instrument, data), (_, key) => { + if (displayAllMeasures) { + return true; + } const measure = instrument.measures?.[key]; if (measure?.visibility === 'hidden' || measure?.hidden === true) { return false; diff --git a/packages/react-core/src/index.ts b/packages/react-core/src/index.ts index 2f19eb3dc..da795b439 100644 --- a/packages/react-core/src/index.ts +++ b/packages/react-core/src/index.ts @@ -5,6 +5,7 @@ export * from './components/ErrorPage'; export * from './components/InstrumentIcon'; export { InstrumentRenderer, type InstrumentRendererProps } from './components/InstrumentRenderer'; export { ScalarInstrumentRenderer, type ScalarInstrumentRendererProps } from './components/InstrumentRenderer'; +export * from './components/InstrumentSummary'; export * from './components/LoadingPage'; export * from './components/Logo'; export * from './types'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b900550c7..a8b000db9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -696,6 +696,9 @@ importers: papaparse: specifier: workspace:papaparse__5.x@* version: link:../../vendor/papaparse@5.x + qrcode: + specifier: ^1.5.4 + version: 1.5.4 react: specifier: workspace:react__19.x@* version: link:../../vendor/react@19.x @@ -745,6 +748,9 @@ importers: '@testing-library/react': specifier: 16.2.0 version: 16.2.0(@testing-library/dom@10.4.0)(react-dom@vendor+react-dom@19.x)(react@vendor+react@19.x) + '@types/qrcode': + specifier: ^1.5.6 + version: 1.5.6 '@vitejs/plugin-react-swc': specifier: ^3.9.0 version: 3.10.2(@swc/helpers@0.5.17)(vite@6.3.5(@types/node@24.10.1)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)) @@ -5878,6 +5884,10 @@ packages: resolution: { integrity: sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg== } + '@types/qrcode@1.5.6': + resolution: + { integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw== } + '@types/qs@6.14.0': resolution: { integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ== } @@ -6811,6 +6821,11 @@ packages: { integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== } engines: { node: '>=6' } + camelcase@5.3.1: + resolution: + { integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== } + engines: { node: '>=6' } + camelcase@6.3.0: resolution: { integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== } @@ -6957,6 +6972,10 @@ packages: { integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ== } engines: { node: '>= 12' } + cliui@6.0.0: + resolution: + { integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== } + cliui@8.0.1: resolution: { integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== } @@ -7340,6 +7359,11 @@ packages: supports-color: optional: true + decamelize@1.2.0: + resolution: + { integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== } + engines: { node: '>=0.10.0' } + decimal.js-light@2.5.1: resolution: { integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== } @@ -7479,6 +7503,10 @@ packages: { integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg== } engines: { node: '>=0.3.1' } + dijkstrajs@1.0.3: + resolution: + { integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA== } + direction@2.0.1: resolution: { integrity: sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA== } @@ -10822,6 +10850,11 @@ packages: engines: { node: '>=18' } hasBin: true + pngjs@5.0.0: + resolution: + { integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== } + engines: { node: '>=10.13.0' } + possible-typed-array-names@1.1.0: resolution: { integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== } @@ -11100,6 +11133,12 @@ packages: resolution: { integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== } + qrcode@1.5.4: + resolution: + { integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg== } + engines: { node: '>=10.13.0' } + hasBin: true + qs@6.13.0: resolution: { integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== } @@ -11453,6 +11492,10 @@ packages: { integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== } engines: { node: '>=0.10.0' } + require-main-filename@2.0.0: + resolution: + { integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== } + requires-port@1.0.0: resolution: { integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== } @@ -13275,6 +13318,10 @@ packages: { integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== } engines: { node: '>= 0.4' } + which-module@2.0.1: + resolution: + { integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== } + which-pm-runs@1.1.0: resolution: { integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA== } @@ -13373,6 +13420,10 @@ packages: resolution: { integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA== } + y18n@4.0.3: + resolution: + { integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== } + y18n@5.0.8: resolution: { integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== } @@ -13412,11 +13463,21 @@ packages: engines: { node: '>= 14.6' } hasBin: true + yargs-parser@18.1.3: + resolution: + { integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== } + engines: { node: '>=6' } + yargs-parser@21.1.1: resolution: { integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== } engines: { node: '>=12' } + yargs@15.4.1: + resolution: + { integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== } + engines: { node: '>=8' } + yargs@17.7.2: resolution: { integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== } @@ -17500,6 +17561,10 @@ snapshots: dependencies: '@types/express': 5.0.3 + '@types/qrcode@1.5.6': + dependencies: + '@types/node': 22.16.3 + '@types/qs@6.14.0': {} '@types/range-parser@1.2.7': {} @@ -18564,6 +18629,8 @@ snapshots: callsites@3.1.0: {} + camelcase@5.3.1: {} + camelcase@6.3.0: {} camelcase@8.0.0: {} @@ -18667,6 +18734,12 @@ snapshots: cli-width@4.1.0: optional: true + cliui@6.0.0: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -18988,6 +19061,8 @@ snapshots: optionalDependencies: supports-color: 8.1.1 + decamelize@1.2.0: {} + decimal.js-light@2.5.1: {} decode-named-character-reference@1.2.0: @@ -19069,6 +19144,8 @@ snapshots: diff@8.0.2: {} + dijkstrajs@1.0.3: {} + direction@2.0.1: {} dlv@1.1.3: {} @@ -22584,6 +22661,8 @@ snapshots: optionalDependencies: fsevents: 2.3.2 + pngjs@5.0.0: {} + possible-typed-array-names@1.1.0: {} postcss-load-config@3.1.4(postcss@8.5.6): @@ -22750,6 +22829,12 @@ snapshots: pure-rand@6.1.0: {} + qrcode@1.5.4: + dependencies: + dijkstrajs: 1.0.3 + pngjs: 5.0.0 + yargs: 15.4.1 + qs@6.13.0: dependencies: side-channel: 1.1.0 @@ -23207,6 +23292,8 @@ snapshots: require-from-string@2.0.2: {} + require-main-filename@2.0.0: {} + requires-port@1.0.0: optional: true @@ -23495,8 +23582,7 @@ snapshots: transitivePeerDependencies: - supports-color - set-blocking@2.0.0: - optional: true + set-blocking@2.0.0: {} set-cookie-parser@2.7.2: {} @@ -24998,6 +25084,8 @@ snapshots: is-weakmap: 2.0.2 is-weakset: 2.0.4 + which-module@2.0.1: {} + which-pm-runs@1.1.0: {} which-typed-array@1.1.19: @@ -25076,6 +25164,8 @@ snapshots: xxhash-wasm@1.1.0: {} + y18n@4.0.3: {} + y18n@5.0.8: {} yallist@3.1.1: {} @@ -25105,8 +25195,27 @@ snapshots: yaml@2.8.0: {} + yargs-parser@18.1.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + yargs-parser@21.1.1: {} + yargs@15.4.1: + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + yargs@17.7.2: dependencies: cliui: 8.0.1