Skip to content

Commit

Permalink
feat(frontend, backend): connect rxdb (#6)
Browse files Browse the repository at this point in the history
* Make a terrible rxdb implementation

* Fix issues with updating patients

* Style fixes

* Small fixes

* Lay groundwork for GraphQL sync

* Add push replication

* Fix bug with unknown consult entity

* Change uid to id, connect RxDB pull

* Fix issues with push-replication, DOB field

* Fix a few errors

* Fix serialization issues

* Never lose a patient again

* Fix deletedAt

* Fix enums... again

* !!Fix vulnerabilities (includes run of npm update)

* Fix npm CI(?)

* roll back node version for CD

npm/cli#4942

* whoops wrong version

* Fix frontend tests

* Refactor for readability

* Fix issues

* Automatically select first tab where available

* Add required fields

* Ugly hack to reduce traffic

* match project file structure

* fix nitpick

* match project file structure

* Fix error with patient ordering in feed

* move usePatients hook

Co-authored-by: Jackson Chadfield <[email protected]>
  • Loading branch information
kand198 and j-chad authored Sep 14, 2022
1 parent aea9eba commit bf26d2d
Show file tree
Hide file tree
Showing 31 changed files with 12,581 additions and 4,101 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
fetch-depth: 0
- uses: actions/setup-node@v3
with:
node-version: 16
node-version: 16.15.0
- uses: nrwl/nx-set-shas@v2
- run: npm ci

Expand Down
2 changes: 2 additions & 0 deletions apps/backend/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import configuration, {
import { HealthModule } from '@supervision/health';
import { UsersModule } from '@supervision/users';
import { PatientsModule } from '@supervision/patients';
import { DateOfBirthScalar } from './patients/graphql/date-of-birth.scalar';

@Module({
imports: [
Expand All @@ -24,6 +25,7 @@ import { PatientsModule } from '@supervision/patients';
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: true,
resolvers: { DateOfBirth: DateOfBirthScalar },
}),
HealthModule,
UsersModule,
Expand Down
6 changes: 3 additions & 3 deletions apps/backend/src/app/patients/database/patient.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ export enum Gender {

@Entity({ name: 'patient' })
export class PatientEntity extends BaseEntity {
@Column('varchar', { length: 40 })
@Column('varchar', { length: 40, nullable: true })
firstName: string;

@Column('varchar', { length: 40 })
@Column('varchar', { length: 40, nullable: true })
lastName: string;

@Column('date')
@Column('date', { nullable: true })
dateOfBirth: Date;

@Column('enum', {
Expand Down
8 changes: 4 additions & 4 deletions apps/backend/src/app/patients/dto/create-patient.input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import {
@InputType()
export class CreatePatientInput {
// TODO: add field descriptions
@Field(() => String, { nullable: false })
@Field(() => String, { nullable: true })
firstName: string;

@Field(() => String, { nullable: false })
@Field(() => String, { nullable: true })
lastName: string;

@Field(() => Date, { nullable: false })
@Field(() => Date, { nullable: true })
dateOfBirth: Date;

@Field(() => Gender, { nullable: true })
Expand All @@ -25,7 +25,7 @@ export class CreatePatientInput {
@Field(() => String, { nullable: true })
school: string;

@Field(() => String, { nullable: true })
@Field(() => Number, { nullable: true })
yearLevel: number;

@Field(() => Date, { nullable: true })
Expand Down
15 changes: 15 additions & 0 deletions apps/backend/src/app/patients/dto/set-patient-input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { InputType, Field, PartialType } from '@nestjs/graphql';
import { UpdatePatientInput } from './update-patient.input';

@InputType()
export class SetPatientInput extends PartialType(UpdatePatientInput) {
// TODO: add field descriptions
@Field(() => String, { nullable: false })
id: string;

@Field({ nullable: true })
deletedAt: Date;

@Field({ nullable: true })
updatedAt: Date;
}
21 changes: 21 additions & 0 deletions apps/backend/src/app/patients/graphql/date-of-birth.scalar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { GraphQLScalarType, Kind } from 'graphql';

export const DateOfBirthScalar = new GraphQLScalarType({
name: 'DateOfBirth',
description: 'Date custom scalar type',
parseValue(value: string | number | Date) {
return new Date(value); // value from the client
},
serialize(value: string | number | Date) {
if (value instanceof Date) {
return value.toISOString(); // value sent to the client
}
return new Date(value).toISOString();
},
parseLiteral(ast) {
if (ast.kind === Kind.INT) {
return new Date(ast.value); // ast value is always in string format
}
return null;
},
});
7 changes: 4 additions & 3 deletions apps/backend/src/app/patients/graphql/patient.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
import { BaseModel } from '@supervision/shared';
import { ConsultModel } from '@supervision/consults/graphql/consult.model';
import { Ethnicity, Gender } from '@supervision/patients';
import { DateOfBirthScalar } from './date-of-birth.scalar';

registerEnumType(Ethnicity, {
name: 'Ethnicity',
Expand All @@ -13,13 +14,13 @@ registerEnumType(Gender, {

@ObjectType({ description: 'patient' })
export class PatientModel extends BaseModel {
@Field({ nullable: false })
@Field({ nullable: true })
firstName: string;

@Field({ nullable: false })
@Field({ nullable: true })
lastName: string;

@Field({ nullable: false })
@Field(() => DateOfBirthScalar, { nullable: true })
dateOfBirth: Date;

@Field(() => Gender, { nullable: true })
Expand Down
13 changes: 13 additions & 0 deletions apps/backend/src/app/patients/graphql/patient.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { PatientService } from '@supervision/patients/patients.service';
import { CreatePatientInput } from '../dto/create-patient.input';
import { PatientEntity } from '../database';
import { UpdatePatientInput } from '../dto/update-patient.input';
import { SetPatientInput } from '../dto/set-patient-input';

@Resolver(() => PatientModel)
export class PatientResolver implements IReplicationResolver<PatientModel> {
Expand Down Expand Up @@ -38,6 +39,18 @@ export class PatientResolver implements IReplicationResolver<PatientModel> {
);
}

@Mutation(() => PatientModel, { nullable: true })
async setPatients(
@Args({
name: 'setPatientsInput',
type: () => [SetPatientInput],
nullable: true,
})
setPatientsInput: SetPatientInput[]
): Promise<PatientModel | null> {
return await this.patientService.set(setPatientsInput);
}

@Query(() => PatientModel)
async patient(@Args('id') id: string): Promise<PatientModel> {
return await this.patientService.findOne(id);
Expand Down
6 changes: 5 additions & 1 deletion apps/backend/src/app/patients/patients.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { PatientEntity } from '@supervision/patients/database';
import { PatientResolver } from '@supervision/patients/graphql';
import { PatientService } from '@supervision/patients/patients.service';
import { ConsultEntity } from '@supervision/consults';

@Module({
imports: [TypeOrmModule.forFeature([PatientEntity])],
imports: [
TypeOrmModule.forFeature([PatientEntity]),
TypeOrmModule.forFeature([ConsultEntity]),
],
exports: [TypeOrmModule],
providers: [PatientService, PatientResolver],
})
Expand Down
8 changes: 7 additions & 1 deletion apps/backend/src/app/patients/patients.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import { PatientEntity } from '@supervision/patients';
import { Repository, SelectQueryBuilder } from 'typeorm';
import { CreatePatientInput } from './dto/create-patient.input';
import { SetPatientInput } from './dto/set-patient-input';
import { UpdatePatientInput } from './dto/update-patient.input';

@Injectable()
Expand Down Expand Up @@ -31,6 +32,11 @@ export class PatientService {
return await this.patientsRepository.save(patient);
}

async set(patients: SetPatientInput[]): Promise<PatientEntity> {
const newPatients = await this.patientsRepository.save(patients);
return newPatients[newPatients.length - 1];
}

async findOne(id: string): Promise<PatientEntity> {
return await this.patientsRepository.findOneBy({ id: id });
}
Expand Down Expand Up @@ -100,7 +106,7 @@ export class PatientService {
}

return await query
.orderBy('patient.updatedAt', 'DESC')
.orderBy('patient.updatedAt', 'ASC')
.addOrderBy('patient.id')
.take(limit)
.withDeleted()
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/src/app/shared/database/base.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export abstract class BaseEntity {
@CreateDateColumn({ type: 'timestamptz' })
createdAt: Date;

@DeleteDateColumn({ type: 'timestamptz' })
@DeleteDateColumn({ type: 'timestamptz', nullable: true })
deletedAt: Date;

@UpdateDateColumn({ type: 'timestamptz' })
Expand Down
7 changes: 6 additions & 1 deletion apps/backend/src/app/users/users.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ export class UserService {
limit: number
): Promise<UserEntity[]> {
let query: SelectQueryBuilder<UserEntity>;
if (minUpdatedAt === undefined || lastId === undefined) {
if (
minUpdatedAt === undefined ||
lastId === undefined ||
minUpdatedAt?.getTime() === 0 ||
lastId === ''
) {
query = this.usersRepository.createQueryBuilder('user');
} else {
query = this.usersRepository
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ type EthnicitySelectProps = {
onChange: (value: string | null) => void;
};

enum Ethnicities {
export enum Ethnicities {
'NZ European' = 'nz european',
'Other European' = 'other european',
'NZ Maori' = 'nz maori',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ type GenderSelectProps = {
onChange: (value: string | null) => void;
};

enum Gender {
export enum Gender {
'Female' = 'female',
'Male' = 'male',
'Other' = 'other',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,114 +1,68 @@
import React from 'react';
import { ScrollArea, SimpleGrid } from '@mantine/core';
import { useState } from 'react';
import { Center, ScrollArea, SimpleGrid, Text } from '@mantine/core';
import { Button } from '@shared';
import { PatientTabs } from '../patient-tabs';
import { PatientInputs } from '../patient-inputs';
import { PatientRecords } from '../patient-records';

export type IPatient = {
uid: string;
id: string;
firstName?: string;
lastName?: string;
dob?: string;
patientId?: string;
dateOfBirth?: string;
ethnicity?: string;
gender?: string;
school?: string;
year?: number;
yearLevel?: number;
room?: string;
address?: {
street?: string;
suburb?: string;
city?: string;
postCode?: string;
};
streetAddress?: string;
suburb?: string;
city?: string;
postcode?: string;
caregiverFirstName?: string;
caregiverLastName?: string;
phoneNumber?: string;
email?: string;
notes?: string;
adminNotes?: string;
};

export const PatientDetailsPage = () => {
// TODO: Replace with actual data
const [patients, setPatients] = React.useState<IPatient[]>([
{
uid: '8c78e8d5-26e5-4a99-a112-0b8602bf2c1b',
firstName: 'Yulia',
lastName: 'Pechorina',
dob: '2001-02-21',
patientId: '12345',
ethnicity: 'other european',
gender: 'female',
school: 'The University of Auckland',
year: 4,
room: 'N/A',
address: {
street: '1000 Fifth Avenue',
suburb: 'Manhattan',
city: 'New York',
postCode: '10028',
},
caregiverFirstName: 'John',
caregiverLastName: 'Doe',
phoneNumber: '+64 9 12345678',
email: '[email protected]',
notes: 'Nothing to add',
},
{
uid: 'c7695a78-33ae-4f71-9c54-4a3336628965',
firstName: 'Kid',
lastName: 'Cudi',
dob: '1984-01-30',
patientId: '54321',
gender: 'male',
},
]);

const [currentPatient, setCurrentPatient] = React.useState<IPatient>(
patients[0]
);
const [currentPatientUid, setCurrentPatientUid] = useState<
string | undefined
>();

const handlePatientChange = (uid: string) => {
const patient = patients.find((p) => p.uid === uid);
if (patient) {
setCurrentPatient(patient);
}
};

const handleUpdatePatient = (updatedPatient: IPatient) => {
setCurrentPatient(updatedPatient);
const newPatients = patients.map((p) => {
if (p.uid === updatedPatient.uid) {
return updatedPatient;
}
return p;
});
setPatients(newPatients);
setCurrentPatientUid(uid);
};

return (
<>
<PatientTabs patients={patients} onPatientChange={handlePatientChange} />
<ScrollArea className="h-full p-8">
<SimpleGrid
cols={3}
spacing={180}
breakpoints={[
{ maxWidth: 1024, cols: 2, spacing: 100 },
{ maxWidth: 1280, cols: 3, spacing: 100 },
]}
>
<PatientInputs
patient={currentPatient}
onUpdatePatient={handleUpdatePatient}
/>
</SimpleGrid>
<div className="flex mt-5 -mb-5 justify-end w-full">
<Button className="ml-auto">CREATE NEW RECORD</Button>
</div>
<PatientRecords className="pb-5" />
</ScrollArea>
<PatientTabs
currentPatientUid={currentPatientUid}
onPatientChange={handlePatientChange}
/>
{currentPatientUid ? (
<ScrollArea className="h-full p-8">
<SimpleGrid
cols={3}
spacing={180}
breakpoints={[
{ maxWidth: 1024, cols: 2, spacing: 100 },
{ maxWidth: 1280, cols: 3, spacing: 100 },
]}
>
<PatientInputs patientUid={currentPatientUid} />
</SimpleGrid>
<div className="flex mt-5 -mb-5 justify-end w-full">
<Button className="ml-auto">CREATE NEW RECORD</Button>
</div>
<PatientRecords className="pb-5" />
</ScrollArea>
) : (
<Center className="h-full">
<Text>Click on a patient to view their details</Text>
</Center>
)}
</>
);
};
Expand Down
Loading

0 comments on commit bf26d2d

Please sign in to comment.