Skip to content

Commit a13ba28

Browse files
committed
add instrument record summary page
1 parent 69657a3 commit a13ba28

File tree

10 files changed

+133
-2
lines changed

10 files changed

+133
-2
lines changed

apps/api/src/instrument-records/instrument-records.controller.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,11 @@ export class InstrumentRecordsController {
9292
) {
9393
return this.instrumentRecordsService.updateById(id, data, { ability });
9494
}
95+
96+
@ApiOperation({ summary: 'Get Instrument Record' })
97+
@Get(':id')
98+
@RouteAccess({ action: 'read', subject: 'InstrumentRecord' })
99+
findById(@Param('id', ValidObjectIdPipe) id: string, @CurrentUser('ability') ability: AppAbility) {
100+
return this.instrumentRecordsService.findById(id, { ability });
101+
}
95102
}

apps/api/src/instrument-records/instrument-records.service.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,16 @@ export class InstrumentRecordsService {
254254
return records;
255255
}
256256

257+
async findById(id: string, { ability }: EntityOperationOptions = {}) {
258+
const record = await this.instrumentRecordModel.findFirst({
259+
where: { AND: [accessibleQuery(ability, 'read', 'InstrumentRecord')], id }
260+
});
261+
if (!record) {
262+
throw new NotFoundException();
263+
}
264+
return record;
265+
}
266+
257267
async linearModel(
258268
{ groupId, instrumentId }: { groupId?: string; instrumentId: string },
259269
{ ability }: EntityOperationOptions = {}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { $InstrumentRecord } from '@opendatacapture/schemas/instrument-records';
2+
import { queryOptions, useSuspenseQuery } from '@tanstack/react-query';
3+
import axios from 'axios';
4+
5+
export const instrumentRecordQueryOptions = ({ params }: { params: { id: string } }) => {
6+
return queryOptions({
7+
queryFn: async () => {
8+
const response = await axios.get(`/v1/instrument-records/${params.id}`);
9+
return $InstrumentRecord.parse(response.data);
10+
},
11+
queryKey: ['instrument-records', `id-${params.id}`]
12+
});
13+
};
14+
15+
export function useInstrumentRecordQuery({ params }: { params: { id: string } }) {
16+
return useSuspenseQuery(instrumentRecordQueryOptions({ params }));
17+
}

apps/web/src/hooks/useInstrumentVisualization.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { useFindSessionQuery } from './useFindSessionQuery';
1818
type InstrumentVisualizationRecord = {
1919
[key: string]: unknown;
2020
__date__: Date;
21+
__id__: string;
2122
__time__: number;
2223
};
2324

@@ -229,6 +230,7 @@ export function useInstrumentVisualization({ params }: UseInstrumentVisualizatio
229230

230231
return {
231232
__date__: record.date,
233+
__id__: record.id,
232234
__time__: record.date.getTime(),
233235
username: username,
234236
...record.computedMeasures,
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { $Subject } from '@opendatacapture/schemas/subject';
2+
import { queryOptions, useSuspenseQuery } from '@tanstack/react-query';
3+
import axios from 'axios';
4+
5+
type SubjectQueryParams = {
6+
id: string;
7+
};
8+
9+
export const subjectQueryOptions = ({ params }: { params: SubjectQueryParams }) => {
10+
return queryOptions({
11+
queryFn: async () => {
12+
const response = await axios.get(`/v1/subjects/${params.id}`, { params });
13+
return $Subject.parseAsync(response.data);
14+
},
15+
queryKey: ['subjects', `id-${params.id}`]
16+
});
17+
};
18+
19+
export function useSubjectQuery({ params }: { params: SubjectQueryParams }) {
20+
return useSuspenseQuery(subjectQueryOptions({ params }));
21+
}

apps/web/src/route-tree.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { Route as AppInstrumentsRenderIdRouteImport } from './routes/_app/instru
3131
import { Route as AppDatahubSubjectIdTableRouteImport } from './routes/_app/datahub/$subjectId/table'
3232
import { Route as AppDatahubSubjectIdGraphRouteImport } from './routes/_app/datahub/$subjectId/graph'
3333
import { Route as AppDatahubSubjectIdAssignmentsRouteImport } from './routes/_app/datahub/$subjectId/assignments'
34+
import { Route as AppDatahubSubjectIdRecordIdRouteImport } from './routes/_app/datahub/$subjectId/$recordId'
3435
import { Route as AppAdminUsersCreateRouteImport } from './routes/_app/admin/users/create'
3536
import { Route as AppAdminGroupsCreateRouteImport } from './routes/_app/admin/groups/create'
3637

@@ -148,6 +149,12 @@ const AppDatahubSubjectIdAssignmentsRoute =
148149
path: '/assignments',
149150
getParentRoute: () => AppDatahubSubjectIdRouteRoute,
150151
} as any)
152+
const AppDatahubSubjectIdRecordIdRoute =
153+
AppDatahubSubjectIdRecordIdRouteImport.update({
154+
id: '/$recordId',
155+
path: '/$recordId',
156+
getParentRoute: () => AppDatahubSubjectIdRouteRoute,
157+
} as any)
151158
const AppAdminUsersCreateRoute = AppAdminUsersCreateRouteImport.update({
152159
id: '/admin/users/create',
153160
path: '/admin/users/create',
@@ -177,6 +184,7 @@ export interface FileRoutesByFullPath {
177184
'/upload': typeof AppUploadIndexRoute
178185
'/admin/groups/create': typeof AppAdminGroupsCreateRoute
179186
'/admin/users/create': typeof AppAdminUsersCreateRoute
187+
'/datahub/$subjectId/$recordId': typeof AppDatahubSubjectIdRecordIdRoute
180188
'/datahub/$subjectId/assignments': typeof AppDatahubSubjectIdAssignmentsRoute
181189
'/datahub/$subjectId/graph': typeof AppDatahubSubjectIdGraphRoute
182190
'/datahub/$subjectId/table': typeof AppDatahubSubjectIdTableRoute
@@ -202,6 +210,7 @@ export interface FileRoutesByTo {
202210
'/upload': typeof AppUploadIndexRoute
203211
'/admin/groups/create': typeof AppAdminGroupsCreateRoute
204212
'/admin/users/create': typeof AppAdminUsersCreateRoute
213+
'/datahub/$subjectId/$recordId': typeof AppDatahubSubjectIdRecordIdRoute
205214
'/datahub/$subjectId/assignments': typeof AppDatahubSubjectIdAssignmentsRoute
206215
'/datahub/$subjectId/graph': typeof AppDatahubSubjectIdGraphRoute
207216
'/datahub/$subjectId/table': typeof AppDatahubSubjectIdTableRoute
@@ -229,6 +238,7 @@ export interface FileRoutesById {
229238
'/_app/upload/': typeof AppUploadIndexRoute
230239
'/_app/admin/groups/create': typeof AppAdminGroupsCreateRoute
231240
'/_app/admin/users/create': typeof AppAdminUsersCreateRoute
241+
'/_app/datahub/$subjectId/$recordId': typeof AppDatahubSubjectIdRecordIdRoute
232242
'/_app/datahub/$subjectId/assignments': typeof AppDatahubSubjectIdAssignmentsRoute
233243
'/_app/datahub/$subjectId/graph': typeof AppDatahubSubjectIdGraphRoute
234244
'/_app/datahub/$subjectId/table': typeof AppDatahubSubjectIdTableRoute
@@ -256,6 +266,7 @@ export interface FileRouteTypes {
256266
| '/upload'
257267
| '/admin/groups/create'
258268
| '/admin/users/create'
269+
| '/datahub/$subjectId/$recordId'
259270
| '/datahub/$subjectId/assignments'
260271
| '/datahub/$subjectId/graph'
261272
| '/datahub/$subjectId/table'
@@ -281,6 +292,7 @@ export interface FileRouteTypes {
281292
| '/upload'
282293
| '/admin/groups/create'
283294
| '/admin/users/create'
295+
| '/datahub/$subjectId/$recordId'
284296
| '/datahub/$subjectId/assignments'
285297
| '/datahub/$subjectId/graph'
286298
| '/datahub/$subjectId/table'
@@ -307,6 +319,7 @@ export interface FileRouteTypes {
307319
| '/_app/upload/'
308320
| '/_app/admin/groups/create'
309321
| '/_app/admin/users/create'
322+
| '/_app/datahub/$subjectId/$recordId'
310323
| '/_app/datahub/$subjectId/assignments'
311324
| '/_app/datahub/$subjectId/graph'
312325
| '/_app/datahub/$subjectId/table'
@@ -477,6 +490,13 @@ declare module '@tanstack/react-router' {
477490
preLoaderRoute: typeof AppDatahubSubjectIdAssignmentsRouteImport
478491
parentRoute: typeof AppDatahubSubjectIdRouteRoute
479492
}
493+
'/_app/datahub/$subjectId/$recordId': {
494+
id: '/_app/datahub/$subjectId/$recordId'
495+
path: '/$recordId'
496+
fullPath: '/datahub/$subjectId/$recordId'
497+
preLoaderRoute: typeof AppDatahubSubjectIdRecordIdRouteImport
498+
parentRoute: typeof AppDatahubSubjectIdRouteRoute
499+
}
480500
'/_app/admin/users/create': {
481501
id: '/_app/admin/users/create'
482502
path: '/admin/users/create'
@@ -495,13 +515,15 @@ declare module '@tanstack/react-router' {
495515
}
496516

497517
interface AppDatahubSubjectIdRouteRouteChildren {
518+
AppDatahubSubjectIdRecordIdRoute: typeof AppDatahubSubjectIdRecordIdRoute
498519
AppDatahubSubjectIdAssignmentsRoute: typeof AppDatahubSubjectIdAssignmentsRoute
499520
AppDatahubSubjectIdGraphRoute: typeof AppDatahubSubjectIdGraphRoute
500521
AppDatahubSubjectIdTableRoute: typeof AppDatahubSubjectIdTableRoute
501522
}
502523

503524
const AppDatahubSubjectIdRouteRouteChildren: AppDatahubSubjectIdRouteRouteChildren =
504525
{
526+
AppDatahubSubjectIdRecordIdRoute: AppDatahubSubjectIdRecordIdRoute,
505527
AppDatahubSubjectIdAssignmentsRoute: AppDatahubSubjectIdAssignmentsRoute,
506528
AppDatahubSubjectIdGraphRoute: AppDatahubSubjectIdGraphRoute,
507529
AppDatahubSubjectIdTableRoute: AppDatahubSubjectIdTableRoute,
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { InstrumentSummary } from '@opendatacapture/react-core';
2+
import { createFileRoute } from '@tanstack/react-router';
3+
4+
import { useInstrument } from '@/hooks/useInstrument';
5+
import { instrumentRecordQueryOptions, useInstrumentRecordQuery } from '@/hooks/useInstrumentRecordQuery';
6+
import { subjectQueryOptions, useSubjectQuery } from '@/hooks/useSubjectQuery';
7+
8+
const RouteComponent = () => {
9+
const recordId = Route.useParams({ select: (params) => params.recordId });
10+
11+
const { data: instrumentRecord } = useInstrumentRecordQuery({ params: { id: recordId } });
12+
const { data: subject } = useSubjectQuery({ params: { id: instrumentRecord.subjectId } });
13+
14+
const instrument = useInstrument(instrumentRecord.instrumentId);
15+
16+
if (!instrument) {
17+
return null;
18+
}
19+
20+
return (
21+
<div className="container py-8">
22+
<InstrumentSummary
23+
displayAllMeasures
24+
data={instrumentRecord.data}
25+
instrument={instrument}
26+
subject={subject}
27+
timeCollected={instrumentRecord.createdAt.getTime()}
28+
/>
29+
</div>
30+
);
31+
};
32+
33+
export const Route = createFileRoute('/_app/datahub/$subjectId/$recordId')({
34+
component: RouteComponent,
35+
loader: async ({ context, params }) => {
36+
const record = await context.queryClient.ensureQueryData(
37+
instrumentRecordQueryOptions({ params: { id: params.recordId } })
38+
);
39+
await context.queryClient.ensureQueryData(subjectQueryOptions({ params: { id: record.subjectId } }));
40+
}
41+
});

apps/web/src/routes/_app/datahub/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ const RouteComponent = () => {
174174
<MasterDataTable
175175
data={data}
176176
onSelect={(subject) => {
177-
void navigate({ to: `./${subject.id}/assignments` });
177+
void navigate({ to: `./${subject.id}/table` });
178178
}}
179179
/>
180180
</div>

packages/react-core/src/components/InstrumentSummary/InstrumentSummary.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,19 @@ import type { SubjectDisplayInfo } from '../../types';
1414

1515
export type InstrumentSummaryProps = {
1616
data: any;
17+
displayAllMeasures?: boolean;
1718
instrument: AnyUnilingualInstrument;
1819
subject?: SubjectDisplayInfo;
1920
timeCollected: number;
2021
};
2122

22-
export const InstrumentSummary = ({ data, instrument, subject, timeCollected }: InstrumentSummaryProps) => {
23+
export const InstrumentSummary = ({
24+
data,
25+
displayAllMeasures,
26+
instrument,
27+
subject,
28+
timeCollected
29+
}: InstrumentSummaryProps) => {
2330
const download = useDownload();
2431
const { resolvedLanguage, t } = useTranslation();
2532

@@ -28,6 +35,9 @@ export const InstrumentSummary = ({ data, instrument, subject, timeCollected }:
2835
}
2936

3037
const computedMeasures = filter(computeInstrumentMeasures(instrument, data), (_, key) => {
38+
if (displayAllMeasures) {
39+
return true;
40+
}
3141
const measure = instrument.measures?.[key];
3242
if (measure?.visibility === 'hidden' || measure?.hidden === true) {
3343
return false;

packages/react-core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export * from './components/ErrorPage';
55
export * from './components/InstrumentIcon';
66
export { InstrumentRenderer, type InstrumentRendererProps } from './components/InstrumentRenderer';
77
export { ScalarInstrumentRenderer, type ScalarInstrumentRendererProps } from './components/InstrumentRenderer';
8+
export * from './components/InstrumentSummary';
89
export * from './components/LoadingPage';
910
export * from './components/Logo';
1011
export * from './types';

0 commit comments

Comments
 (0)