Skip to content

Commit ebd1087

Browse files
committed
feat: add graphs to dashboard
1 parent d9cb183 commit ebd1087

File tree

13 files changed

+437
-48
lines changed

13 files changed

+437
-48
lines changed

apps/api/src/demo/demo.service.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,16 @@ export class DemoService {
107107
researchId++;
108108
}
109109

110+
const date = faker.date.past({ years: 1 });
111+
110112
const subject = await this.subjectsService.create({
113+
createdAt: date,
111114
...subjectIdData,
112115
id: subjectId
113116
});
114117

115118
const session = await this.sessionsService.create({
116-
date: new Date(),
119+
date,
117120
groupId: group.id,
118121
subjectData: subject,
119122
type: 'IN_PERSON'
@@ -132,7 +135,7 @@ export class DemoService {
132135
}
133136
await this.instrumentRecordsService.create({
134137
data: data as Json,
135-
date: faker.date.past({ years: 2 }),
138+
date,
136139
groupId: group.id,
137140
instrumentId: hq.id,
138141
sessionId: session.id,

apps/api/src/sessions/sessions.service.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { InternalServerErrorException, NotFoundException } from '@nestjs/common/
55
import type { Group } from '@opendatacapture/schemas/group';
66
import type { CreateSessionData } from '@opendatacapture/schemas/session';
77
import type { CreateSubjectData } from '@opendatacapture/schemas/subject';
8-
import type { Session, Subject } from '@prisma/client';
8+
import type { Prisma, Session, Subject } from '@prisma/client';
99

1010
import type { EntityOperationOptions } from '@/core/types';
1111
import { GroupsService } from '@/groups/groups.service';
@@ -20,6 +20,12 @@ export class SessionsService {
2020
private readonly subjectsService: SubjectsService
2121
) {}
2222

23+
async count(where: Prisma.SessionWhereInput = {}, { ability }: EntityOperationOptions = {}) {
24+
return this.sessionModel.count({
25+
where: { AND: [accessibleQuery(ability, 'read', 'Session'), where] }
26+
});
27+
}
28+
2329
async create({ date, groupId, subjectData, type }: CreateSessionData): Promise<Session> {
2430
this.loggingService.debug({ message: 'Attempting to create session' });
2531
const subject = await this.resolveSubject(subjectData);

apps/api/src/subjects/subjects.service.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,13 @@ export class SubjectsService {
3131
});
3232
}
3333

34-
async create({ id, ...data }: CreateSubjectDto) {
34+
async create({
35+
id,
36+
...data
37+
}: CreateSubjectDto & {
38+
/** for demo purposes need to set createdAt manually */
39+
createdAt?: Date;
40+
}) {
3541
if (await this.subjectModel.exists({ id })) {
3642
throw new ConflictException('A subject with the provided demographic information already exists');
3743
}

apps/api/src/summary/summary.controller.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export class SummaryController {
1313
@RouteAccess([
1414
{ action: 'read', subject: 'Instrument' },
1515
{ action: 'read', subject: 'InstrumentRecord' },
16+
{ action: 'read', subject: 'Session' },
1617
{ action: 'read', subject: 'Subject' },
1718
{ action: 'read', subject: 'User' }
1819
])

apps/api/src/summary/summary.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Module } from '@nestjs/common';
33
import { GroupsModule } from '@/groups/groups.module';
44
import { InstrumentRecordsModule } from '@/instrument-records/instrument-records.module';
55
import { InstrumentsModule } from '@/instruments/instruments.module';
6+
import { SessionsModule } from '@/sessions/sessions.module';
67
import { SubjectsModule } from '@/subjects/subjects.module';
78
import { UsersModule } from '@/users/users.module';
89

@@ -12,7 +13,7 @@ import { SummaryService } from './summary.service';
1213
@Module({
1314
controllers: [SummaryController],
1415
exports: [SummaryService],
15-
imports: [GroupsModule, InstrumentRecordsModule, InstrumentsModule, SubjectsModule, UsersModule],
16+
imports: [GroupsModule, InstrumentRecordsModule, InstrumentsModule, SessionsModule, SubjectsModule, UsersModule],
1617
providers: [SummaryService]
1718
})
1819
export class SummaryModule {}

apps/api/src/summary/summary.service.ts

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,63 @@ import type { Summary } from '@opendatacapture/schemas/summary';
44
import type { EntityOperationOptions } from '@/core/types';
55
import { InstrumentRecordsService } from '@/instrument-records/instrument-records.service';
66
import { InstrumentsService } from '@/instruments/instruments.service';
7+
import { SessionsService } from '@/sessions/sessions.service';
78
import { SubjectsService } from '@/subjects/subjects.service';
89
import { UsersService } from '@/users/users.service';
910

11+
const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
12+
1013
@Injectable()
1114
export class SummaryService {
1215
constructor(
1316
private readonly instrumentRecordsService: InstrumentRecordsService,
1417
private readonly instrumentsService: InstrumentsService,
18+
private readonly sessionsService: SessionsService,
1519
private readonly usersService: UsersService,
1620
private readonly subjectsService: SubjectsService
1721
) {}
1822

1923
async getSummary(groupId?: string, { ability }: EntityOperationOptions = {}): Promise<Summary> {
2024
const filter = groupId ? { groupIds: { has: groupId } } : ({} as { [key: string]: unknown });
25+
const sessionFilter = groupId ? { groupId } : {};
26+
const recordFilter = groupId ? { groupId } : {};
27+
28+
const counts = {
29+
instruments: await this.instrumentsService.count(),
30+
records: await this.instrumentRecordsService.count(recordFilter, { ability }),
31+
sessions: await this.sessionsService.count(sessionFilter, { ability }),
32+
subjects: await this.subjectsService.count(filter, { ability }),
33+
users: await this.usersService.count(filter, { ability })
34+
};
35+
36+
const now = Date.now();
37+
38+
const trends: Summary['trends'] = {
39+
records: [],
40+
sessions: [],
41+
subjects: []
42+
};
43+
44+
for (let i = 0; i < 5; i++) {
45+
const timestamp = now - THIRTY_DAYS_MS * i;
46+
const lte = new Date(timestamp);
47+
trends.records.push({
48+
timestamp,
49+
value: await this.instrumentRecordsService.count({ date: { lte }, ...recordFilter }, { ability })
50+
});
51+
trends.sessions.push({
52+
timestamp,
53+
value: await this.sessionsService.count({ date: { lte }, ...sessionFilter }, { ability })
54+
});
55+
trends.subjects.push({
56+
timestamp,
57+
value: await this.subjectsService.count({ createdAt: { lte }, ...filter }, { ability })
58+
});
59+
}
60+
2161
return {
22-
counts: {
23-
instruments: await this.instrumentsService.count(),
24-
records: await this.instrumentRecordsService.count({ groupId }, { ability }),
25-
subjects: await this.subjectsService.count(filter, { ability }),
26-
users: await this.usersService.count(filter, { ability })
27-
}
62+
counts,
63+
trends
2864
};
2965
}
3066
}

apps/web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"papaparse": "workspace:papaparse__5.x@*",
4949
"react": "workspace:react__19.x@*",
5050
"react-dom": "workspace:react-dom__19.x@*",
51+
"recharts": "^2.15.2",
5152
"ts-pattern": "workspace:ts-pattern__5.x@*",
5253
"type-fest": "workspace:type-fest__4.x@*",
5354
"xlsx": "^0.18.5",

0 commit comments

Comments
 (0)