Skip to content

Commit

Permalink
Merge pull request #173 from dump-hr/interview-notes
Browse files Browse the repository at this point in the history
Implemented interview notes
  • Loading branch information
bdeak4 committed Oct 18, 2023
2 parents d8826e6 + 8efc043 commit 8d1370a
Show file tree
Hide file tree
Showing 17 changed files with 247 additions and 59 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
Warnings:
- You are about to drop the column `notes` on the `InterviewSlot` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "Intern" ADD COLUMN "notes" TEXT NOT NULL DEFAULT '';

-- AlterTable
ALTER TABLE "InterviewSlot" DROP COLUMN "notes";
2 changes: 1 addition & 1 deletion apps/api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ model Intern {
email String @unique
image String?
data Json
notes String @default("")
interviewStatus InterviewStatus @default(NoRight)
interviewSlot InterviewSlot?
internDisciplines InternDiscipline[]
Expand Down Expand Up @@ -112,7 +113,6 @@ model InterviewSlot {
score Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
notes String?
interviewers InterviewMemberParticipation[]
}

Expand Down
15 changes: 15 additions & 0 deletions apps/api/src/intern/intern.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
BoardActionRequest,
CreateNoteRequest,
InternActionRequest,
InternDecisionRequest,
SetInterviewRequest,
Expand Down Expand Up @@ -160,4 +161,18 @@ export class InternController {

return await this.internService.setDecision(internId, data);
}

@Post('note/:internId')
@UseGuards(JwtAuthGuard)
async setNote(
@Param('internId') internId: string,
@Body() data: CreateNoteRequest,
) {
await this.loggerService.createAdminLog(
AdminLogAction.Create,
`Dodana nota nad ${internId}`,
);

return await this.internService.createNote(internId, data);
}
}
29 changes: 28 additions & 1 deletion apps/api/src/intern/intern.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
import {
BoardAction,
CreateNoteRequest,
InternAction,
InternDecisionRequest,
SetInterviewRequest,
Expand Down Expand Up @@ -48,7 +49,15 @@ export class InternService {
priority: 'asc',
},
},
interviewSlot: true,
interviewSlot: {
include: {
interviewers: {
include: {
interviewer: true,
},
},
},
},
logs: {
orderBy: {
date: 'desc',
Expand Down Expand Up @@ -471,6 +480,24 @@ dump.hr`,
);
}

async createNote(internId: string, data: CreateNoteRequest) {
const { notes: currentNotes } = await this.prisma.intern.findUnique({
where: { id: internId },
select: { notes: true },
});

return await this.prisma.intern.update({
where: {
id: internId,
},
data: {
notes: {
set: currentNotes + `${data.note}\n`,
},
},
});
}

async count() {
return await this.prisma.intern.count();
}
Expand Down
5 changes: 1 addition & 4 deletions apps/api/src/interview-slot/dto/createInterviewSlot.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IsArray, IsDateString, IsString } from 'class-validator';
import { IsArray, IsDateString } from 'class-validator';

export class CreateInterviewSlotDto {
@IsDateString()
Expand All @@ -9,7 +9,4 @@ export class CreateInterviewSlotDto {

@IsArray()
interviewers: string[];

@IsString()
notes?: string;
}
1 change: 0 additions & 1 deletion apps/api/src/interview-slot/interview-slot.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ export class InterviewSlotService {
start: slotStart,
end: slotEnd,
answers: {},
notes: interviewSlotDto.notes,
},
});

Expand Down
26 changes: 26 additions & 0 deletions apps/web/src/api/useCreateNote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { CreateNoteRequest } from '@internship-app/types';
import toast from 'react-hot-toast';
import { useMutation, useQueryClient } from 'react-query';

import { api } from '.';

const createNote = async (req: CreateNoteRequest) => {
return await api.post<CreateNoteRequest, never>(
`/intern/note/${req.internId}`,
req,
);
};

export const useCreateNote = () => {
const queryClient = useQueryClient();

return useMutation(createNote, {
onSuccess: (_data, variables) => {
toast.success('Nota uspješno dodana!');
queryClient.invalidateQueries(['intern', variables.internId]);
},
onError: (error: string) => {
toast.error(`Greška pri izvođenju akcije: ${error}`);
},
});
};
9 changes: 0 additions & 9 deletions apps/web/src/components/CalendarSidebar/CalendarSidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import FilterAltIcon from '@mui/icons-material/FilterAlt';

import { useFetchAllInterviewers } from '../../api/useFetchAllInterviewers';
import { MultilineInput } from '../common/MultilineInput/MultilineInput';
import { CustomSelectInput } from '../common/SelectInput/CustomSelectInput';
import styles from './index.module.css';

Expand All @@ -12,15 +11,11 @@ interface Props {
setSelectedInterviewerFilter: React.Dispatch<
React.SetStateAction<string[] | null | undefined>
>;
setAdditionalNotesValue: React.Dispatch<
React.SetStateAction<string | undefined>
>;
}

export const CalendarSidebar: React.FC<Props> = ({
setInterviewers,
setSelectedInterviewerFilter,
setAdditionalNotesValue,
}: Props) => {
const { data: interviewers } = useFetchAllInterviewers();

Expand Down Expand Up @@ -52,10 +47,6 @@ export const CalendarSidebar: React.FC<Props> = ({
setInterviewers(selectedInterviewers);
}}
/>
<div>
<div className={styles.notesLabel}>Notes:</div>
<MultilineInput onValueChange={setAdditionalNotesValue} />
</div>
</div>
);
};
4 changes: 0 additions & 4 deletions apps/web/src/components/CalendarSidebar/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@
margin-left: 50px;
}

.notesLabel {
margin-bottom: 15px;
}

.filterSection {
display: flex;
align-items: center;
Expand Down
89 changes: 61 additions & 28 deletions apps/web/src/components/InternInfo/InternInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { Intern, Question } from '@internship-app/types';
import {
Intern,
InternDiscipline,
Question,
TestStatus,
} from '@internship-app/types';
import { DataGrid, GridColDef } from '@mui/x-data-grid';
import moment from 'moment';

Expand All @@ -8,6 +13,7 @@ import {
} from '../../constants/internConstants';
import { Path } from '../../constants/paths';
import styles from './index.module.css';
import InternNotes from './InternNotes';

interface InternInfoProps {
intern: Intern;
Expand All @@ -27,43 +33,70 @@ const InternInfo = ({ intern }: InternInfoProps) => {
date: moment(il.date).format('DD.MM. HH:mm'),
}));

const interview = intern.interviewSlot;
const interviewInfo =
'Intervju: ' +
[
interview && moment(interview.start).format('DD.MM. HH:mm'),
intern.interviewStatus,
interview?.interviewers.map((int) => int.interviewer.name).join(', '),
]
.filter((a) => a)
.join(' | ');

const testInfo = (ind: InternDiscipline) =>
`${disciplineLabel[ind.discipline]} test: ` +
[
ind.testSlot && moment(ind.testSlot?.start).format('DD.MM. HH:mm'),
ind.testSlot && `${ind.testScore}/${ind.testSlot?.maxPoints}`,
ind.testStatus,
]
.filter((a) => a)
.join(' | ');

return (
<div className={styles.page}>
<h1 className={styles.internFullName}>
{intern.firstName} {intern.lastName}
</h1>

<div className={styles.emailContainer}>
<div>{intern.email}</div>
<div className={styles.header}>
<div className={styles.mainInfoContainer}>
<div>{intern.email}</div>
<div>
Registracija: {moment(intern.createdAt).format('DD.MM. HH:mm')}
</div>

<div>
{intern.internDisciplines
.map((ind) => disciplineLabel[ind.discipline])
.join(', ')}
</div>

<div>
<div>{interviewInfo}</div>
{intern.internDisciplines
.map((ind) => disciplineLabel[ind.discipline])
.join(', ')}
.filter((ind) => ind.testStatus)
.map((ind) => (
<div key={ind.discipline}>
<a
href={
ind.testStatus == TestStatus.Done
? Path.TestReview.replace(
':testSlotId',
ind.testSlotId ?? '',
)
.replace(':group', 'intern')
.replace(':groupId', intern.id)
: undefined
}
>
{testInfo(ind)}
</a>
</div>
))}
</div>

<div>
Intervju: {moment(intern.interviewSlot?.start).format('DD.MM. HH:mm')}{' '}
{intern.interviewStatus}
</div>
{intern.internDisciplines
.filter((ind) => ind.testStatus)
.map((ind) => (
<div>
<a
href={Path.TestReview.replace(
':testSlotId',
ind.testSlotId ?? '',
)
.replace(':group', 'intern')
.replace(':groupId', intern.id)}
>
{disciplineLabel[ind.discipline]} test:{' '}
{moment(ind.testSlot?.start).format('DD.MM. HH:mm')},{' '}
{ind.testScore}/{ind.testSlot?.maxPoints} {ind.testStatus}
</a>
</div>
))}
<InternNotes intern={intern} />
</div>

<div className={styles.container}>
Expand Down
57 changes: 57 additions & 0 deletions apps/web/src/components/InternInfo/InternNotes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Intern } from '@internship-app/types';
import { Button, TextField } from '@mui/material';
import { useState } from 'react';

import { useCreateNote } from '../../api/useCreateNote';
import styles from './index.module.css';

type InternNotesProps = {
intern: Intern;
};

const InternNotes: React.FC<InternNotesProps> = ({ intern }) => {
const [newNote, setNewNote] = useState('');
const createNote = useCreateNote();

const handleCreateNote = () => {
createNote.mutate(
{
internId: intern.id,
note: newNote,
},
{
onSuccess: () => setNewNote(''),
},
);
};

return (
<div className={styles.notesWrapper}>
{intern.notes && (
<div className={styles.notes}>
<b>Notes: </b>{' '}
{intern.notes.split('\n').map((i) => (
<p>{i}</p>
))}
</div>
)}
<div className={styles.noteInput}>
<TextField
value={newNote}
size="small"
onChange={(e) => setNewNote(e.target.value)}
label="Add note"
/>
<Button
variant="contained"
onClick={handleCreateNote}
disabled={!newNote}
>
Dodaj
</Button>
</div>
</div>
);
};

export default InternNotes;
Loading

0 comments on commit 8d1370a

Please sign in to comment.