From 5aabc3927801bddf45012519a51278732937b6fb Mon Sep 17 00:00:00 2001 From: fOppenheimer <80682747+fOppenheimer@users.noreply.github.com> Date: Tue, 4 May 2021 13:35:04 +0200 Subject: [PATCH] initial mask for recovery (#46) --- src/assets/SCSS/custom.scss | 4 + src/assets/i18n/de/translation.json | 12 +- src/assets/i18n/en/translation.json | 15 +- src/components/landing-page.component.tsx | 1 + .../record-recovery-cert-data.component.tsx | 551 ++++++++++++++++++ src/components/show-certificate.component.tsx | 3 + src/misc/navigation.tsx | 4 + src/routing.component.tsx | 8 + 8 files changed, 582 insertions(+), 16 deletions(-) create mode 100644 src/components/record-recovery-cert-data.component.tsx diff --git a/src/assets/SCSS/custom.scss b/src/assets/SCSS/custom.scss index d863870..92d5d63 100644 --- a/src/assets/SCSS/custom.scss +++ b/src/assets/SCSS/custom.scss @@ -338,4 +338,8 @@ hr { display: flex; flex-direction: column; height: 100%; + } + + .space-five { + padding: 5px; } \ No newline at end of file diff --git a/src/assets/i18n/de/translation.json b/src/assets/i18n/de/translation.json index 12515b5..8b35e2f 100644 --- a/src/assets/i18n/de/translation.json +++ b/src/assets/i18n/de/translation.json @@ -67,7 +67,8 @@ "totalTestCount": "Gesamtanzahl der Tests", "positiveTestCount": "Anzahl der positiven Tests", "successfull-transferred": "Die Daten wurden erfolgreich übermittelt", - "record-vaccination-cert-dat": "Impfpass erstellen", + "record-vaccination-cert-dat": "Impfzertifikat erstellen", + "record-recovery-cert-dat": "Genesungszertifikat erstellen", "vaccination-cert": "Impfpass erstellen", "identifierType": "Art des Ausweises", "country": "Land", @@ -90,11 +91,6 @@ "def-lot": "Eine unverwechselbare Kombination von Zahlen und / oder Buchstaben, die eine Charge spezifisch identifiziert", "adm": "Verwaltungzentrum", "def-adm": "Name oder Code des Verwaltungszentrums (z. B. Region Halland)", - "PPN": "Ausweisnummer", - "NN": "Nationale Personenkennung", - "CZ": "Staatsbürgerschaftskartennummer", - "HC": "Gesundheitskartennummer", - "NI": "Nationale eindeutige individuelle Kennung", - "MB": "Mitgliedsnummer", - "NH": "National Health Plan Identifier" + "recovery-first-date": "First Positive Test" + } \ No newline at end of file diff --git a/src/assets/i18n/en/translation.json b/src/assets/i18n/en/translation.json index 051c40e..8504b75 100644 --- a/src/assets/i18n/en/translation.json +++ b/src/assets/i18n/en/translation.json @@ -71,6 +71,7 @@ "successfull-transferred": "Data successfully transferred", "record-vaccination-cert-dat": "Record vaccination certification", "record-test-cert-dat": "Record test certification", + "record-recovery-cert-dat": "Record recovery certification", "vaccination-cert": "Record Vaccination Certification", "test-cert": "Record Test Certification", "identifierType": "Identifier Type", @@ -96,18 +97,16 @@ "certificateIssuer": "Certificate Issuer", "adm": "Administering Centre", "def-adm": "Name or code of administering centre (e.g. Region Halland)", - "PPN": "Passport Number", - "NN": "National Person Identifier", - "CZ": "Citizenship Card Number", - "HC": "Health Card number", - "NI": "National Unique Individual Identifier", - "MB": "Member Number", - "NH": "National Health Plan Identifier", "testManufacturers": "RAT Test name and manufacturer", "testResult": "Test Result", "testType": "Type of Test", "testName": "NAA Test Name", "sampleDateTime": "Date/Time of Sample Collection", "testDateTime": "Date/Time of Test Result", - "testCenter": "Testing Centre" + "testCenter": "Testing Centre", + "recovery-first-date": "First Positive Test Result", + "recovery-test-country": "Country of Test", + "cert-valid-from-go": "Certificate Valid From - To", + "valid-from": "Certificate Valid From", + "valid-to": "Certificate Valid To" } \ No newline at end of file diff --git a/src/components/landing-page.component.tsx b/src/components/landing-page.component.tsx index f8dabc0..b058df1 100644 --- a/src/components/landing-page.component.tsx +++ b/src/components/landing-page.component.tsx @@ -46,6 +46,7 @@ const LandingPage = (props: any) => { + ) diff --git a/src/components/record-recovery-cert-data.component.tsx b/src/components/record-recovery-cert-data.component.tsx new file mode 100644 index 0000000..d2bd507 --- /dev/null +++ b/src/components/record-recovery-cert-data.component.tsx @@ -0,0 +1,551 @@ +/* + * eu-digital-green-certificates/ dgca-issuance-web + * + * (C) 2021, T-Systems International GmbH + * + * Deutsche Telekom AG and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { Button, Card, Col, Form, FormControlProps, Row } from 'react-bootstrap'; + +import '../i18n'; +import { useTranslation } from 'react-i18next'; +import useLocalStorage from '../misc/local-storage'; + +import useNavigation from '../misc/navigation'; +import Spinner from './spinner/spinner.component'; +import { IdentifierType } from '../misc/enum'; + +import DatePicker from "react-datepicker"; +import { registerLocale } from "react-datepicker"; +import "react-datepicker/dist/react-datepicker.css"; +//import de from 'date-fns/locale/de'; + +import { EUDGC, RecoveryEntry, DiseaseAgentTargeted } from '../generated-files/dgc-combined-schema'; +import { useGetDiseaseAgents, IValueSet } from '../api'; + +import schema from '../generated-files/DGC.combined-schema.json'; +import { Validator } from 'jsonschema'; +import utils from '../misc/utils'; +const validator = new Validator(); +const iso3311a2 = require('iso-3166-1-alpha-2'); + +//registerLocale('de', de) + + +const RecordRecoveryCertData = (props: any) => { + + const navigation = useNavigation(); + const { t } = useTranslation(); + + // data read from the API + const diseaseAgentsData = useGetDiseaseAgents(); + + const [isInit, setIsInit] = React.useState(false) + + const [givenName, setGivenName] = React.useState(''); + const [familyName, setFamilyName] = React.useState(''); + + const [standardisedGivenName, setStandardisedGivenName] = React.useState(''); + const [standardisedFamilyName, setStandardisedFamilyName] = React.useState(''); + + const [dateOfBirth, setDateOfBirth] = React.useState(); + + const [disease, setDisease] = React.useState(''); + + const [diseasOptions, setDiseasOptions] = React.useState(); + + const [firstPositiveResultDate, setFirstPositiveResultDate] = React.useState(); + const [certificateIssuer, setCertificateIssuer] = React.useState(''); + const [testCountryCode, setTestCountryCode] = React.useState(''); + const [ dateValidFrom, setDateValidFrom] = React.useState(); + const [ dateValidTo, setDateValidTo] = React.useState(); + + const [isoCountryOptions, setIsoCountryOptions] = React.useState(); + const [defaultTestCountryCode, setDefaultTestCountryCode] = useLocalStorage('defaultTestCountryCode', ''); + + + React.useEffect(() => { + if (!props.eudgc) { + return; + } + + const eudgc: EUDGC = props.eudgc; + + setFamilyName(eudgc.nam!.fn!); + setStandardisedFamilyName(eudgc.nam!.fnt!); + setGivenName(eudgc.nam!.gn!); + setStandardisedGivenName(eudgc.nam!.gnt!); + setDateOfBirth(new Date(eudgc.dob!)); + setDisease(eudgc.r![0].tg!); + setFirstPositiveResultDate(new Date(eudgc.r![0].fr!)); + setTestCountryCode(eudgc.r![0].co!); + setCertificateIssuer(eudgc.r![0].is!); + setDateValidFrom(new Date(eudgc.r![0].df!)) + setDateValidTo(new Date(eudgc.r![0].du!)) + }, [props.eudgc]); + + React.useEffect(() => { + setIso3311a2(); + }, []); + + React.useEffect(() => { + if(!testCountryCode) { + setTestCountryCode(defaultTestCountryCode); + } + + }, [defaultTestCountryCode]); + + React.useEffect(() => { + if (testCountryCode !== defaultTestCountryCode) { + setDefaultTestCountryCode(testCountryCode); + } + + }, [testCountryCode]); + + + React.useEffect(() => { + if (navigation) { + setTimeout(setIsInit, 200, true); + } + }, [navigation]); + + + React.useEffect(() => { + if (diseaseAgentsData) { + const options = getOptionsForValueSet(diseaseAgentsData) + setDiseasOptions(options); + } + }, [diseaseAgentsData]) + + const getOptionsForValueSet = (valueSet: IValueSet): JSX.Element[] => { + const result: JSX.Element[] = []; + for (const key of Object.keys(valueSet)) { + result.push() + } + + return result; + } + + const setIso3311a2 = () => { + const options: JSX.Element[] = []; + const codes: string[] = iso3311a2.getCodes().sort(); + + // options.push(); + + for (const code of codes) { + options.push() + } + + setIsoCountryOptions(options); + } + + const handleError = (error: any) => { + let msg = ''; + + if (error) { + msg = error.message + } + props.setError({ error: error, message: msg, onCancel: navigation!.toLanding }); + } + + const handleStandardisedNameChanged = (changedValue: string, setStandardisedName: (value: string) => void) => { + const upperCaseChangedValue = changedValue.toUpperCase(); + + if (utils.isStandardisedNameValid(upperCaseChangedValue)) { + setStandardisedName(upperCaseChangedValue); + } + } + + const handleDateOfBirthChange = (evt: Date | [Date, Date] | null) => { + const date = handleDateChange(evt); + setDateOfBirth(date); + } + + const handleFirstPositiveResultDate = (evt: Date | [Date, Date] | null) => { + const date = handleDateChange(evt); + setFirstPositiveResultDate(date); + } + + const handleDateValidFrom = (evt: Date | [Date, Date] | null) => { + const date = handleDateChange(evt); + setDateValidFrom(date); + } + + const handleDateValidTo = (evt: Date | [Date, Date] | null) => { + const date = handleDateChange(evt); + setDateValidTo(date); + } + + const handleDateChange = (evt: Date | [Date, Date] | null) => { + let date: Date; + + if (Array.isArray(evt)) + date = evt[0]; + else + date = evt as Date; + + if (date) { + date.setHours(12); + } + + return date; + } + + const handleCancel = () => { + props.setEudgc(undefined); + navigation?.toLanding(); + } + + const handleSubmit = (event: React.FormEvent) => { + + event.preventDefault(); + event.stopPropagation(); + + const form = event.currentTarget; + + if (form.checkValidity()) { + + const r: RecoveryEntry = { + tg: disease, + fr: firstPositiveResultDate!.toISOString().split('T')[0], + co: testCountryCode, + is: certificateIssuer, + df: dateValidFrom!.toISOString().split('T')[0], + du: dateValidTo!.toISOString().split('T')[0], + ci: '' + }; + + const eudgc: EUDGC = { + ver: '1.0.0', + nam: { + fn: familyName, + fnt: standardisedFamilyName!, + gn: givenName, + gnt: standardisedGivenName + }, + dob: dateOfBirth!.toISOString().split('T')[0], + r: [r] + } + + var result = validator.validate(eudgc, schema); + + if (result.valid) { + //console.log(JSON.stringify(eudgc)); + + props.setEudgc(eudgc); + setTimeout(navigation!.toShowCert, 200); + } + else { + console.error(result); + props.setError({ error: result, message: result.errors[0].message, onCancel: navigation!.toLanding }); + } + } + } + + const formatDate = (date: Date): string => `${date.toISOString().substr(0, 10)}`; + + + return ( + !isInit ? : + <> + + +
+ + {/* + header with title and id card query + */} + + + + {t('translation:record-recovery-cert-dat')} + + + {t('translation:query-id-card')} + + +
+
+ + {/* + content area with patient inputs and check box + */} + + + {/* first name input */} + + {t('translation:first-name') + '*'} + + + setGivenName(event.target.value)} + placeholder={t('translation:first-name')} + type='text' + required + maxLength={50} + /> + + + + {/* name input */} + + {t('translation:name') + '*'} + + + setFamilyName(event.target.value)} + placeholder={t('translation:name')} + type='text' + required + maxLength={50} + /> + + + +
+ + {/* standardised first name input */} + + {t('translation:standardised-first-name') + '*'} + + + handleStandardisedNameChanged(evt.target.value, setStandardisedGivenName)} + placeholder={t('translation:standardised-first-name')} + type='text' + required + pattern={utils.pattern.standardisedName} + maxLength={50} + /> + + + + {/*standardised name input */} + + {t('translation:standardised-name') + '*'} + + + handleStandardisedNameChanged(evt.target.value, setStandardisedFamilyName)} + placeholder={t('translation:standardised-name')} + type='text' + required + pattern={utils.pattern.standardisedName} + maxLength={50} + /> + + + +
+ + {/* date of birth input */} + + {t('translation:date-of-birth') + '*'} + + + + + + +
+ + {/* combobox disease */} + + {t('translation:disease-agent') + '*'} + + + setDisease(event.target.value)} + placeholder={t('translation:def-disease-agent')} + required + > + + {diseasOptions} + + + + +
+ + {/* Date of First Positive Test Result */} + + {t('translation:recovery-first-date') + '*'} + + + + + + + {/* Combobox for the vaccin countries in iso-3166-1-alpha-2 */} + + {t('translation:recovery-test-country') + '*'} + + + setTestCountryCode(event.target.value)} + placeholder={t('translation:country')} + required + > + + {isoCountryOptions} + + + + +
+ + + + {/* certificateIssuer */} + + {t('translation:certificateIssuer') + '*'} + + + setCertificateIssuer(event.target.value)} + placeholder={t('translation:certificateIssuer')} + type='text' + required + maxLength={50} + /> + + + + {/* Date: Certificate Valid From - To */} + + {t('translation:cert-valid-from-go') + '*'} + + + + { '-'} + + + + +
+
+ + {/* + footer with clear and nex button + */} + + + + + + + + + + + +
+
+ + ) +} + +export default RecordRecoveryCertData; \ No newline at end of file diff --git a/src/components/show-certificate.component.tsx b/src/components/show-certificate.component.tsx index b50d777..6b0d002 100644 --- a/src/components/show-certificate.component.tsx +++ b/src/components/show-certificate.component.tsx @@ -134,6 +134,9 @@ const ShowCertificate = (props: any) => { if (eudgc.t) { navigation!.toRecordTest(); } + if (eudgc.t) { + navigation!.toRecordRecovery(); + } if (eudgc.r) { navigation!.toLanding(); } diff --git a/src/misc/navigation.tsx b/src/misc/navigation.tsx index 8d538e6..8438407 100644 --- a/src/misc/navigation.tsx +++ b/src/misc/navigation.tsx @@ -33,6 +33,7 @@ export interface INavigation { toLanding: () => void, toRecordVac: () => void, toRecordTest: () => void, + toRecordRecovery: () => void, toShowCert: () => void } @@ -45,6 +46,7 @@ export const useRoutes = () => { landing: basePath, recordVac: basePath + '/record/vac', recordTest: basePath + '/record/test', + recordRecovery: basePath + '/record/recovery', showCert: basePath + '/record/show' } @@ -68,6 +70,7 @@ export const useNavigation = () => { c.landing = routes.landing.replace(':mandant', mandant as string); c.recordVac = routes.recordVac.replace(':mandant', mandant as string); c.recordTest = routes.recordTest.replace(':mandant', mandant as string); + c.recordRecovery = routes.recordRecovery.replace(':mandant', mandant as string); c.showCert = routes.showCert.replace(':mandant', mandant as string); setCalculatedRoutes(c); @@ -84,6 +87,7 @@ export const useNavigation = () => { toLanding: () => { history.push(calculatedRoutes.landing); }, toRecordVac: () => { history.push(calculatedRoutes.recordVac); }, toRecordTest: () => { history.push(calculatedRoutes.recordTest); }, + toRecordRecovery: () => { history.push(calculatedRoutes.recordRecovery); }, toShowCert: () => { history.push(calculatedRoutes.showCert); } } diff --git a/src/routing.component.tsx b/src/routing.component.tsx index 6338ae2..0a7908b 100644 --- a/src/routing.component.tsx +++ b/src/routing.component.tsx @@ -40,6 +40,7 @@ import RecordVaccinationCertData from './components/record-vaccination-cert-data import ShowCertificate from './components/show-certificate.component'; import { EUDGC } from './generated-files/dgc-combined-schema'; import RecordTestCertData from './components/record-test-cert-data.component'; +import RecordRecoveryCertData from './components/record-recovery-cert-data.component'; const Routing = (props: any) => { @@ -102,6 +103,13 @@ const Routing = (props: any) => { + + + +