Skip to content

Commit

Permalink
VKT(Frontend): Implement exam event listing across all examiners for …
Browse files Browse the repository at this point in the history
…OPH clerk user [deploy]
  • Loading branch information
pkoivisto committed Nov 18, 2024
1 parent fe9425b commit 5b76a74
Show file tree
Hide file tree
Showing 11 changed files with 329 additions and 13 deletions.
11 changes: 11 additions & 0 deletions frontend/packages/vkt/public/i18n/fi-FI/clerk.json
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,17 @@
"copyToClipboardFailed": "Kopiointi leikepöydälle epäonnistui"
}
},
"clerkExaminerExamEventListing": {
"header": {
"examDate": "Tutkintopäivä",
"examiner": "Tutkinnon vastaanottaja",
"municipality": "Paikkakunta",
"isPublic": "Nähtävillä julkisesti",
"language": "Tutkinnon kieli"
},
"more": "Lisätiedot",
"title": "Tutkintotilaisuudet"
},
"clerkExaminerListing": {
"buttons": {
"viewDetails": "Katso tiedot"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Divider, SelectChangeEvent } from '@mui/material';
import { H2, PaginatedTable } from 'shared/components';

import { ClerkExaminerExamEventListingHeader } from 'components/clerkExaminer/ClerkExaminerExamEventListingHeader';
import { ClerkExaminerExamEventListingRow } from 'components/clerkExaminer/ClerkExaminerExamEventListingRow';
import { ClerkExaminerExamEventToggleFilters } from 'components/clerkExaminer/ClerkExaminerExamEventToggleFilters';
import { LanguageFilter } from 'components/common/LanguageFilter';
import { useClerkTranslation, useCommonTranslation } from 'configs/i18n';
import { useAppDispatch, useAppSelector } from 'configs/redux';
import { ExamLanguage } from 'enums/app';
import { ClerkExaminerExamEventListingEntry } from 'interfaces/clerkListExaminer';
import { setClerkListExaminerExamEventFilters } from 'redux/reducers/clerkListExaminer';
import {
clerkListExaminerSelector,
selectFilteredClerkExaminerExamEvents,
} from 'redux/selectors/clerkListExaminer';

const getRowDetails = (entry: ClerkExaminerExamEventListingEntry) => {
return <ClerkExaminerExamEventListingRow entry={entry} />;
};

export const ClerkExaminerExamEventListing = () => {
const { t } = useClerkTranslation({
keyPrefix: 'vkt.component.clerkExamEventListing',
});
const translateCommon = useCommonTranslation();
const dispatch = useAppDispatch();

const { examLanguage } = useAppSelector(clerkListExaminerSelector).filters
.examEvents;

const handleLanguageFilterChange = (event: SelectChangeEvent) => {
dispatch(
setClerkListExaminerExamEventFilters({
examLanguage: event.target.value as ExamLanguage,
}),
);
};

const entries = useAppSelector(selectFilteredClerkExaminerExamEvents);

// TODO Table sorting not implemented yet!

return (
<>
<div className="columns">
<div className="clerk-homepage__grid-container__heading">
<H2>{t('title')}</H2>
</div>
</div>
<Divider />
<ClerkExaminerExamEventToggleFilters />
<PaginatedTable
headerContent={
<LanguageFilter
value={examLanguage}
onChange={handleLanguageFilterChange}
/>
}
className="table-layout-auto"
data={entries}
header={<ClerkExaminerExamEventListingHeader />}
getRowDetails={getRowDetails}
initialRowsPerPage={10}
rowsPerPageOptions={[10, 20, 50]}
rowsPerPageLabel={translateCommon('rowsPerPageLabel')}
stickyHeader
/>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { TableCell, TableHead, TableRow } from '@mui/material';

import { useClerkTranslation } from 'configs/i18n';

export const ClerkExaminerExamEventListingHeader = () => {
const { t } = useClerkTranslation({
keyPrefix: 'vkt.component.clerkExaminerExamEventListing.header',
});

return (
<TableHead className="heading-text">
<TableRow>
<TableCell>{t('examiner')}</TableCell>
<TableCell>{t('language')}</TableCell>
<TableCell>{t('municipality')}</TableCell>
<TableCell>{t('examDate')}</TableCell>
<TableCell>{t('isPublic')}</TableCell>
<TableCell />
</TableRow>
</TableHead>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { ChevronRight } from '@mui/icons-material';
import { TableCell, TableRow } from '@mui/material';
import { Link } from 'react-router-dom';
import { CustomButtonLink, Text } from 'shared/components';
import { Color, Variant } from 'shared/enums';

import {
useClerkTranslation,
useCommonTranslation,
useKoodistoMunicipalitiesTranslation,
} from 'configs/i18n';
import { AppRoutes } from 'enums/app';
import { ClerkExaminerExamEventListingEntry } from 'interfaces/clerkListExaminer';
import { DateTimeUtils } from 'utils/dateTime';

export const ClerkExaminerExamEventListingRow = ({
entry,
}: {
entry: ClerkExaminerExamEventListingEntry;
}) => {
const translateMunicipality = useKoodistoMunicipalitiesTranslation();
const translateCommon = useCommonTranslation();
const { t } = useClerkTranslation({
keyPrefix: 'vkt.component.clerkExaminerExamEventListing',
});
const { examiner, examEvent } = entry;

const examEventUrl = AppRoutes.ExaminerExamEventPage.replace(
/:oid/,
examiner.oid,
).replace(/:examEventId$/, `${examEvent.id}`);

return (
<>
<TableRow
className="clerk-exam-event-listing__row"
data-testid={`clerk-exam-events__id-${examEvent.id}-row`}
>
<TableCell>
<Link
className="clerk-exam-event-listing__row__link"
to={examEventUrl}
>
<Text>{`${examiner.firstName} ${examiner.lastName}`}</Text>
</Link>
</TableCell>
<TableCell>
<Text>{translateCommon(`examLanguage.${examEvent.language}`)}</Text>
</TableCell>
<TableCell>
<Text>{translateMunicipality(examEvent.municipality.code)}</Text>
</TableCell>
<TableCell>
<Text>{DateTimeUtils.renderDate(examEvent.date)}</Text>
</TableCell>
<TableCell>
<Text>
{examEvent.isHidden
? translateCommon('no')
: translateCommon('yes')}
</Text>
</TableCell>
<TableCell>
<CustomButtonLink
sx={{ padding: 0 }}
variant={Variant.Text}
color={Color.Secondary}
endIcon={<ChevronRight />}
to={examEventUrl}
>
{t('more')}
</CustomButtonLink>
</TableCell>
</TableRow>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ToggleFilterGroup } from 'shared/components';

import { useClerkTranslation } from 'configs/i18n';
import { useAppDispatch, useAppSelector } from 'configs/redux';
import { ExamEventToggleFilter } from 'enums/app';
import { setClerkListExaminerExamEventFilters } from 'redux/reducers/clerkListExaminer';
import { clerkListExaminerSelector } from 'redux/selectors/clerkListExaminer';
import { ExamEventUtils } from 'utils/examEvent';

export const ClerkExaminerExamEventToggleFilters = () => {
const { t } = useClerkTranslation({
keyPrefix: 'vkt.component.clerkExamEventListing.toggleFilters',
});

const { examiners, filters } = useAppSelector(clerkListExaminerSelector);
const examEvents = examiners.flatMap(({ examEvents }) => examEvents);
const dispatch = useAppDispatch();

const setToggleFilter = (status: ExamEventToggleFilter) => {
dispatch(setClerkListExaminerExamEventFilters({ toggleFilters: status }));
};

const filterData = [
{
status: ExamEventToggleFilter.Upcoming,
label: t(ExamEventToggleFilter.Upcoming),
count: ExamEventUtils.getUpcomingExamEvents(examEvents).length,
testId: `clerk-exam-event-toggle-filters__${ExamEventToggleFilter.Upcoming}-btn`,
},
{
status: ExamEventToggleFilter.Passed,
label: t(ExamEventToggleFilter.Passed),
count: ExamEventUtils.getPassedExamEvents(examEvents).length,
testId: `clerk-exam-event-toggle-filters__${ExamEventToggleFilter.Passed}-btn`,
},
];

return (
<ToggleFilterGroup
filters={filterData}
activeStatus={filters.examEvents.toggleFilters}
onButtonClick={setToggleFilter}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ const ExaminerFilter = () => {
const { t } = useExaminerTranslation({
keyPrefix: 'vkt.component.examinerFilter',
});
const { examLanguage } = useAppSelector(clerkListExaminerSelector).filters;
const { examLanguage } = useAppSelector(clerkListExaminerSelector).filters
.examiners;
const dispatch = useAppDispatch();

return (
Expand Down
19 changes: 17 additions & 2 deletions frontend/packages/vkt/src/interfaces/clerkListExaminer.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
import { APIResponseStatus } from 'shared/enums';
import { WithId } from 'shared/interfaces';

import { ExamLanguage } from 'enums/app';
import { ExamEventToggleFilter, ExamLanguage } from 'enums/app';
import { ExaminerDetails } from 'interfaces/examinerDetails';
import { ExaminerExamEvent } from 'interfaces/examinerExamEvent';

export interface ClerkListExaminerFilters {
examLanguage: ExamLanguage;
}

export interface ClerkListExaminerExamEventFilters {
examLanguage: ExamLanguage;
toggleFilters: ExamEventToggleFilter;
}

export interface ClerkListExaminerState {
status: APIResponseStatus;
examiners: Array<ExaminerDetails>;
filters: ClerkListExaminerFilters;
filters: {
examiners: ClerkListExaminerFilters
examEvents: ClerkListExaminerExamEventFilters;
}
}

export interface ClerkExaminerExamEventListingEntry extends WithId {
examiner: Omit<ExaminerDetails, 'examEvents' | 'contactRequests'>;
examEvent: ExaminerExamEvent;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { FC, useEffect } from 'react';
import { H1 } from 'shared/components';
import { APIResponseStatus } from 'shared/enums';

import { ClerkExamEventListing } from 'components/clerkExamEvent/listing/ClerkExamEventListing';
import { ClerkExaminerExamEventListing } from 'components/clerkExaminer/ClerkExaminerExamEventListing';
import { ClerkExaminerListing } from 'components/clerkExaminer/ClerkExaminerListing';
import { PublicExamEventGridSkeleton } from 'components/skeletons/PublicExamEventGridSkeleton';
import { useClerkTranslation } from 'configs/i18n';
Expand Down Expand Up @@ -45,8 +45,6 @@ export const ClerkGoodAndSatisfactoryLevelPage: FC = () => {
useEffect(() => {
dispatch(resetClerkExamEventOverview());
}, [dispatch]);
// TODO Listing of examiners
// TODO Listing of exam events of good and satisfactory level

return (
<Box className="clerk-homepage">
Expand Down Expand Up @@ -74,7 +72,7 @@ export const ClerkGoodAndSatisfactoryLevelPage: FC = () => {
{examEventsLoading ? (
<PublicExamEventGridSkeleton />
) : (
<ClerkExamEventListing examEvents={[]} />
<ClerkExaminerExamEventListing />
)}
</Paper>
</Grid>{' '}
Expand Down
28 changes: 24 additions & 4 deletions frontend/packages/vkt/src/redux/reducers/clerkListExaminer.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { APIResponseStatus } from 'shared/enums';

import { ExamLanguage } from 'enums/app';
import { ExamEventToggleFilter, ExamLanguage } from 'enums/app';
import {
ClerkListExaminerExamEventFilters,
ClerkListExaminerFilters,
ClerkListExaminerState,
} from 'interfaces/clerkListExaminer';
Expand All @@ -12,7 +13,13 @@ const initialState: ClerkListExaminerState = {
status: APIResponseStatus.NotStarted,
examiners: [],
filters: {
examLanguage: ExamLanguage.ALL,
examiners: {
examLanguage: ExamLanguage.ALL,
},
examEvents: {
examLanguage: ExamLanguage.ALL,
toggleFilters: ExamEventToggleFilter.Upcoming,
},
},
};

Expand All @@ -35,9 +42,21 @@ const clerkListExaminerSlice = createSlice({
},
setClerkListExaminerFilters(
state,
action: PayloadAction<ClerkListExaminerFilters>,
action: PayloadAction<Partial<ClerkListExaminerFilters>>,
) {
state.filters.examiners = {
...state.filters.examiners,
...action.payload,
};
},
setClerkListExaminerExamEventFilters(
state,
action: PayloadAction<Partial<ClerkListExaminerExamEventFilters>>,
) {
state.filters = action.payload;
state.filters.examEvents = {
...state.filters.examEvents,
...action.payload,
};
},
},
});
Expand All @@ -47,5 +66,6 @@ export const {
loadClerkListExaminers,
rejectClerkListExaminers,
setClerkListExaminerFilters,
setClerkListExaminerExamEventFilters,
} = clerkListExaminerSlice.actions;
export const clerkListExaminerReducer = clerkListExaminerSlice.reducer;
Loading

0 comments on commit 5b76a74

Please sign in to comment.