From 4ced8d8df5804445cf6bbc9980814bca25061eab Mon Sep 17 00:00:00 2001 From: Ha Minh Chien Date: Fri, 8 Mar 2024 17:20:28 +0700 Subject: [PATCH] update --- .../src/app/auth/use-case/populate-context.ts | 8 +- .../src/domain/auth/utils.ts | 1 + .../form/FormDateRangePicker/index.tsx | 55 ++ .../components/InfoConfirmView/columns.tsx | 0 .../components/InfoConfirmView/index.tsx | 474 ++++++++++++++++++ .../components/InfoEditView/index.tsx | 452 +++++++++++++++++ .../components/InfoInputForm/index.tsx | 4 +- .../features/sample-info/components/index.ts | 2 + .../pages/InfoConfirmPage/index.tsx | 462 +---------------- .../pages/InfoConfirmPage/loader.ts | 73 +-- .../sample-info/pages/InfoEditPage/index.tsx | 434 ++-------------- .../sample-info/pages/InfoEditPage/loader.ts | 110 ++-- .../sample-info/pages/InfoInputPage/loader.ts | 67 +-- .../pages/ManageTestElementPage/loader.ts | 18 +- .../test/pages/ManageTestPage/loader.ts | 61 +-- .../src/infra/api/access-service/index.ts | 84 ++++ apps/hcdc-web-app/src/infra/router/routes.tsx | 36 +- apps/hcdc-web-app/src/main.tsx | 8 +- apps/hcdc-web-app/src/shared/utils/index.ts | 1 + .../src/shared/utils/pagination.ts | 8 + 20 files changed, 1252 insertions(+), 1106 deletions(-) create mode 100644 apps/hcdc-web-app/src/components/form/FormDateRangePicker/index.tsx create mode 100644 apps/hcdc-web-app/src/features/sample-info/components/InfoConfirmView/columns.tsx create mode 100644 apps/hcdc-web-app/src/features/sample-info/components/InfoConfirmView/index.tsx create mode 100644 apps/hcdc-web-app/src/features/sample-info/components/InfoEditView/index.tsx create mode 100644 apps/hcdc-web-app/src/shared/utils/pagination.ts diff --git a/apps/hcdc-access-service/src/app/auth/use-case/populate-context.ts b/apps/hcdc-access-service/src/app/auth/use-case/populate-context.ts index a2ef0d18..9f89cecb 100644 --- a/apps/hcdc-access-service/src/app/auth/use-case/populate-context.ts +++ b/apps/hcdc-access-service/src/app/auth/use-case/populate-context.ts @@ -17,8 +17,14 @@ export class AuthPopulateContextUseCase { ) {} async execute(input: AuthPayload): Promise { + const { user, ability } = await this.createUserAbility(input.userId) + + return { user, ability } + } + + async createUserAbility(userId: string) { const user = await this.userRepository.findOne({ - filter: { _id: input.userId }, + filter: { _id: userId }, populates: [ { path: 'roles', fields: ['permissions'] satisfies (keyof Role)[] }, ], diff --git a/apps/hcdc-access-service/src/domain/auth/utils.ts b/apps/hcdc-access-service/src/domain/auth/utils.ts index ce8c87e0..f30fd9d8 100644 --- a/apps/hcdc-access-service/src/domain/auth/utils.ts +++ b/apps/hcdc-access-service/src/domain/auth/utils.ts @@ -60,6 +60,7 @@ export function authorizePopulates( for (const populate of populates) { const { subject, action } = authMapping(populate.path) const authMatchObj = accessibleBy(ability, action)[subject] + if (populate.match) { let matchObject: FilterQuery diff --git a/apps/hcdc-web-app/src/components/form/FormDateRangePicker/index.tsx b/apps/hcdc-web-app/src/components/form/FormDateRangePicker/index.tsx new file mode 100644 index 00000000..6cb396b6 --- /dev/null +++ b/apps/hcdc-web-app/src/components/form/FormDateRangePicker/index.tsx @@ -0,0 +1,55 @@ +import { OutlinedInputProps } from '@mui/material' +import { Control, Path, FieldValues } from 'react-hook-form' + +import { FormDateTimePicker } from '../FormDateTimePicker' + +export type FormDateRangePickerProps = + Omit & { + fromDateName: Path + toDateName: Path + label: string + control: Control + disableError?: boolean + } + +export function FormDateRangePicker< + TFieldValues extends FieldValues = FieldValues, +>({ + fromDateName, + toDateName, + control, + disableError = false, + ...outlinedInputProps +}: FormDateRangePickerProps) { + const fromDate = control._getWatch(fromDateName) as Date + const toDate = control._getWatch(fromDateName) as Date + + return ( + <> + + + + ) +} diff --git a/apps/hcdc-web-app/src/features/sample-info/components/InfoConfirmView/columns.tsx b/apps/hcdc-web-app/src/features/sample-info/components/InfoConfirmView/columns.tsx new file mode 100644 index 00000000..e69de29b diff --git a/apps/hcdc-web-app/src/features/sample-info/components/InfoConfirmView/index.tsx b/apps/hcdc-web-app/src/features/sample-info/components/InfoConfirmView/index.tsx new file mode 100644 index 00000000..5d77a6b2 --- /dev/null +++ b/apps/hcdc-web-app/src/features/sample-info/components/InfoConfirmView/index.tsx @@ -0,0 +1,474 @@ +import { GridActionsCellItem } from '@mui/x-data-grid' +import CheckIcon from '@mui/icons-material/Check' +import EditIcon from '@mui/icons-material/Edit' +import { useNavigate, useSearchParams } from 'react-router-dom' +import { useEffect, useState } from 'react' +import { format, startOfDay, endOfDay } from 'date-fns' +import { + AuthSubject, + PatientGender, + SampleAction, + checkPermission, +} from '@diut/hcdc' +import { DATETIME_FORMAT } from '@diut/common' +import { Box, Paper, IconButton } from '@mui/material' +import Grid from '@mui/material/Unstable_Grid2' +import LoopIcon from '@mui/icons-material/Loop' + +import { + OmittedSampleResponseDto, + SampleSearchResponseDto, + useSampleSearchQuery, + useSampleUpdateInfoByIdMutation, +} from 'src/infra/api/access-service/sample' +import { DataTable } from 'src/components/table' +import { + PatientResponseDto, + useLazyPatientFindByIdQuery, +} from 'src/infra/api/access-service/patient' +import { useTypedSelector } from 'src/infra/redux' +import { usePagination } from 'src/shared/hooks' +import { + FormContainer, + FormDateTimePicker, + FormSelect, + FormTextField, +} from 'src/components/form' +import { useForm } from 'react-hook-form' +import { DiagnosisResponseDto } from 'src/infra/api/access-service/diagnosis' +import { BranchResponseDto } from 'src/infra/api/access-service/branch' +import { DoctorResponseDto } from 'src/infra/api/access-service/doctor' +import { PatientTypeResponseDto } from 'src/infra/api/access-service/patient-type' +import { TestResponseDto } from 'src/infra/api/access-service/test' +import { authSlice } from 'src/features/auth' +import { makeDateFilter } from 'src/shared' +import { urlInfoEditPage } from '../../pages/InfoEditPage' + +const ANY_PATIENT_TYPE = 'ANY_PATIENT_TYPE' +const ANY_SAMPLE_ORIGIN = 'ANY_SAMPLE_ORIGIN' + +interface FilterData { + fromDate: Date + toDate: Date + sampleId: string + infoCompleted: 'all' | 'true' | 'false' + patientType: string + sampleOrigin: string +} + +export type InfoConfirmViewProps = { + diagnosisMap: Map + sampleOriginMap: Map + doctorMap: Map + patientTypeMap: Map + testMap: Map +} + +export function InfoConfirmView(props: InfoConfirmViewProps) { + const userAbility = useTypedSelector(authSlice.selectors.selectAbility) + const navigate = useNavigate() + const [searchParams, setSearchParams] = useSearchParams() + const patientTypeParam = searchParams.get('patientType') ?? ANY_PATIENT_TYPE + const sampleOriginParam = + searchParams.get('sampleOrigin') ?? ANY_SAMPLE_ORIGIN + + const { filterObj, setFilterObj, onPageChange, onPageSizeChange } = + usePagination({ + offset: 0, + limit: 10, + sort: { infoAt: -1, sampleId: -1 }, + filter: { + infoAt: makeDateFilter(), + }, + }) + + const { control, handleSubmit, watch, setValue } = useForm({ + defaultValues: { + fromDate: + searchParams.get('fromDate') != null + ? new Date(searchParams.get('fromDate')!) + : new Date(), + toDate: + searchParams.get('toDate') != null + ? new Date(searchParams.get('toDate')!) + : new Date(), + sampleId: searchParams.get('sampleId') ?? '', + infoCompleted: + (searchParams.get('infoCompleted') as FilterData['infoCompleted']) ?? + 'all', + patientType: patientTypeParam, + sampleOrigin: sampleOriginParam, + }, + }) + const fromDate = watch('fromDate') + const toDate = watch('toDate') + const infoCompleted = watch('infoCompleted') + const patientType = watch('patientType') + const sampleOrigin = watch('sampleOrigin') + + const { + data, + isFetching: isFetchingSamples, + refetch, + } = useSampleSearchQuery(filterObj) + + const handleSubmitFilter = ({ + fromDate, + toDate, + sampleId, + infoCompleted, + patientType, + sampleOrigin, + }: FilterData) => { + setSearchParams( + { + sampleId, + infoCompleted, + fromDate: fromDate.toISOString(), + toDate: toDate.toISOString(), + patientType, + sampleOrigin, + }, + { replace: true }, + ) + return setFilterObj((obj) => ({ + ...obj, + filter: { + ...obj.filter, + sampleId: + sampleId.length > 0 + ? { $regex: sampleId + '$', $options: 'i' } + : undefined, + infoAt: + sampleId.length > 0 ? undefined : makeDateFilter(fromDate, toDate), + infoCompleted: + infoCompleted === 'all' + ? undefined + : infoCompleted === 'true' + ? true + : false, + patientTypeId: + patientType !== ANY_PATIENT_TYPE ? patientType : undefined, + sampleOriginId: + sampleOrigin !== ANY_SAMPLE_ORIGIN ? sampleOrigin : undefined, + }, + })) + } + + useEffect(() => { + if (toDate < fromDate) { + setValue('fromDate', toDate) + } else { + handleSubmit(handleSubmitFilter)() + } + }, [fromDate, toDate, infoCompleted, patientType, sampleOrigin]) + + const [getPatient, { isFetching: isFetchingPatients }] = + useLazyPatientFindByIdQuery() + + const [samples, setSamples] = useState() + const [patients, setPatients] = useState<{ + [id: string]: PatientResponseDto + }>({}) + + async function expandId(samples: OmittedSampleResponseDto[]) { + const promises = samples.map(async (sample) => { + const { patientId, results } = sample + getPatient(patientId, true).then((res) => { + setPatients((cache) => + Object.assign({}, cache, { + [patientId]: res.data!, + }), + ) + }) + }) + return Promise.all(promises) + } + + useEffect(() => { + if (!isFetchingSamples) { + expandId(data?.items!) + setSamples(data!) + } + }, [isFetchingSamples, JSON.stringify(filterObj)]) + + const [updateSample, { isLoading: isConfirming }] = + useSampleUpdateInfoByIdMutation() + + const handleConfirmClick = (sample: OmittedSampleResponseDto) => () => { + updateSample({ + id: sample._id, + sampleUpdateInfoRequestDto: { + isConfirmed: true, + }, + }) + } + + const handleEditClick = (sample: OmittedSampleResponseDto) => () => { + navigate( + urlInfoEditPage({ sampleId: sample._id, patientId: sample.patientId }), + ) + } + + return ( + + + + + + 0} + /> + + + 0} + /> + + + label} + getOptionValue={({ value }) => value} + /> + + + ({ + label: name, + value: _id, + }), + ), + ]} + getOptionLabel={({ label }) => label} + getOptionValue={({ value }) => value} + /> + + + ({ + label: name, + value: _id, + }), + ), + ]} + getOptionLabel={({ label }) => label} + getOptionValue={({ value }) => value} + /> + + + + + + + + + + row._id} + columns={[ + { + field: 'startActions', + type: 'actions', + width: 60, + cellClassName: 'actions', + renderHeader: () => ( + { + refetch() + }} + > + + + ), + getActions: ({ row }) => + row.isConfirmed + ? [] + : [ + } + label="Xác nhận" + color="primary" + onClick={handleConfirmClick(row)} + disabled={isConfirming} + />, + ], + }, + { + field: 'infoAt', + headerName: 'Nhận bệnh', + width: 100, + sortable: false, + valueGetter: ({ value }) => { + return format(new Date(value), DATETIME_FORMAT) + }, + }, + { + field: 'sampleId', + headerName: 'ID XN', + width: 120, + sortable: false, + renderCell: ({ value }) => {value}, + }, + { + field: 'name', + headerName: 'Tên', + sortable: false, + width: 100, + valueGetter: ({ row }) => patients[row.patientId]?.name, + }, + { + field: 'birthYear', + headerName: 'Năm', + width: 60, + sortable: false, + valueGetter: ({ row }) => patients[row.patientId]?.birthYear, + }, + { + field: 'gender', + headerName: 'Giới', + width: 60, + sortable: false, + valueGetter: ({ row }) => { + if (patients[row.patientId]?.gender === PatientGender.Female) { + return 'Nữ' + } else { + return 'Nam' + } + }, + }, + { + field: 'address', + headerName: 'Địa chỉ', + width: 80, + sortable: false, + valueGetter: ({ row }) => patients[row.patientId]?.address, + }, + { + field: 'isTraBuuDien', + headerName: 'BĐ', + width: 60, + align: 'center', + sortable: false, + valueGetter: ({ value }) => { + if (value === true) { + return '✓' + } + return '' + }, + }, + { + field: 'doctor', + headerName: 'Bác sỹ', + width: 100, + sortable: false, + valueGetter: ({ row }) => props.doctorMap.get(row.doctorId)?.name, + }, + { + field: 'tests', + headerName: 'Chỉ định', + minWidth: 100, + flex: 1, + sortable: false, + valueGetter: ({ row }) => { + return row.results + .map(({ testId }) => props.testMap.get(testId)?.name) + .join(', ') + }, + }, + { + field: 'diagnosis', + headerName: 'CĐ', + width: 70, + sortable: false, + valueGetter: ({ row }) => + props.diagnosisMap.get(row.diagnosisId)?.name, + }, + { + field: 'patientType', + headerName: 'Đối T.', + width: 70, + sortable: false, + valueGetter: ({ row }) => + props.patientTypeMap.get(row.patientTypeId)?.name, + }, + { + field: 'endActions', + type: 'actions', + width: 60, + cellClassName: 'actions', + getActions: ({ row }) => + checkPermission( + userAbility, + AuthSubject.Sample, + SampleAction.UpdateInfo, + { ...row } as any, + ) || row.isConfirmed === false + ? [ + } + label="Sửa" + onClick={handleEditClick(row)} + />, + ] + : [], + }, + ]} + paginationMode="server" + rowCount={samples?.total ?? 0} + page={samples?.offset ?? 0} + pageSize={samples?.limit ?? 10} + onPageChange={onPageChange} + onPageSizeChange={onPageSizeChange} + /> + + + ) +} diff --git a/apps/hcdc-web-app/src/features/sample-info/components/InfoEditView/index.tsx b/apps/hcdc-web-app/src/features/sample-info/components/InfoEditView/index.tsx new file mode 100644 index 00000000..ee5f1a3e --- /dev/null +++ b/apps/hcdc-web-app/src/features/sample-info/components/InfoEditView/index.tsx @@ -0,0 +1,452 @@ +import { + AuthSubject, + PatientGender, + SampleAction, + checkPermission, +} from '@diut/hcdc' +import { useEffect, useMemo, useState } from 'react' +import { LoadingButton } from '@mui/lab' +import { + Button, + FormControl, + FormControlLabel, + RadioGroup, + Radio, + Paper, + TextField, + Box, + Typography, +} from '@mui/material' +import Grid from '@mui/material/Unstable_Grid2' +import { Controller, useForm } from 'react-hook-form' +import { toast } from 'react-toastify' +import { useNavigate } from 'react-router-dom' +import { difference, omit } from 'lodash' + +import { + FormContainer, + FormSwitch, + FormTextField, + FormDateTimePicker, + FormCheckboxGroup, + FormSelect, +} from 'src/components/form' +import { formResolver, FormSchema } from '../InfoInputForm/validation' +import { TestSelector } from 'src/features/test/components/TestSelector' +import { + SampleResponseDto, + SampleUnpopulatedResponseDto, + useSampleDeleteByIdMutation, + useSampleUpdateInfoByIdMutation, +} from 'src/infra/api/access-service/sample' +import { + PatientResponseDto, + usePatientUpdateByIdMutation, +} from 'src/infra/api/access-service/patient' +import { useTypedSelector } from 'src/infra/redux' +import { BarcodeModal } from '../BarcodeModal' +import { PatientTypeResponseDto } from 'src/infra/api/access-service/patient-type' +import { DiagnosisResponseDto } from 'src/infra/api/access-service/diagnosis' +import { DoctorResponseDto } from 'src/infra/api/access-service/doctor' +import { SampleTypeResponseDto } from 'src/infra/api/access-service/sample-type' +import { BranchResponseDto } from 'src/infra/api/access-service/branch' +import { authSlice } from 'src/features/auth' + +const currentYear = new Date().getFullYear() + +export type InfoEditViewProps = { + sampleRes: SampleResponseDto + patientRes: PatientResponseDto + patientTypes: PatientTypeResponseDto[] + diagnoses: DiagnosisResponseDto[] + doctors: DoctorResponseDto[] + sampleTypes: SampleTypeResponseDto[] + origins: BranchResponseDto[] +} + +export function InfoEditView(props: InfoEditViewProps) { + const userAbility = useTypedSelector(authSlice.selectors.selectAbility) + const navigate = useNavigate() + const originalTestIds = props.sampleRes.results.map(({ testId }) => testId) + + const { + control, + handleSubmit, + watch, + setValue, + formState: { isSubmitting }, + } = useForm({ + resolver: formResolver, + defaultValues: { + externalId: props.patientRes.externalId, + name: props.patientRes.name, + infoAt: new Date(props.sampleRes.infoAt), + sampledAt: new Date(props.sampleRes.sampledAt), + gender: props.patientRes.gender as PatientGender, + birthYear: props.patientRes.birthYear, + address: props.patientRes.address, + originId: props.sampleRes.originId, + sampleId: props.sampleRes.sampleId, + phoneNumber: props.patientRes.phoneNumber, + SSN: props.patientRes.SSN, + isTraBuuDien: props.sampleRes.isTraBuuDien, + isNgoaiGio: props.sampleRes.isNgoaiGio, + patientTypeId: props.sampleRes.patientTypeId, + diagnosisId: props.sampleRes.diagnosisId, + doctorId: props.sampleRes.doctorId, + note: props.sampleRes.note, + sampleTypeIds: props.sampleRes.sampleTypeIds, + testIds: originalTestIds, + }, + }) + + const name = watch('name') + const testIds = watch('testIds') + const sampleId = watch('sampleId') + const birthYear = watch('birthYear') + const [age, setAge] = useState(currentYear - birthYear) + + useEffect(() => { + setAge(currentYear - birthYear) + }, [birthYear]) + + useEffect(() => { + //@ts-ignore + if (typeof birthYear === 'string' && birthYear.length > 0) { + setValue('birthYear', Number(birthYear)) + } + }, [birthYear]) + + const [testSelectorOpen, setTestSelectorOpen] = useState(false) + + const [updateSample] = useSampleUpdateInfoByIdMutation() + const [updatePatient] = usePatientUpdateByIdMutation() + + const [deleteSample, { isLoading: isDeletingSample }] = + useSampleDeleteByIdMutation() + + const [barcodeModalOpen, setBarcodeModalOpen] = useState(false) + + return ( + + + +
+ + {props.sampleRes.infoBy?.name} + + +
+
+ { + return Promise.all([ + updatePatient({ + id: props.patientRes._id, + patientUpdateRequestDto: values, + }).unwrap(), + updateSample({ + id: props.sampleRes._id, + sampleUpdateInfoRequestDto: { + ...values, + addedTestIds: difference(values.testIds, originalTestIds), + removedTestIds: difference(originalTestIds, values.testIds), + sampledAt: values.sampledAt.toISOString(), + infoAt: values.infoAt.toISOString(), + }, + }).unwrap(), + ]).then(() => { + setBarcodeModalOpen(true) + }) + })} + > + + + {/* ----------------------------- Row 1 ----------------------------- */} + + + + + + + + + + + + + {/* ----------------------------- Row 2 ----------------------------- */} + + ( + + + } + label="Nam" + /> + } + label="Nữ" + /> + + + )} + /> + + + + + + { + setValue('birthYear', currentYear - Number(e.target.value), { + shouldValidate: true, + }) + }} + fullWidth + label="Tuổi" + /> + + + + + + option._id} + getOptionLabel={(option) => option.name} + /> + + {/* ----------------------------- Row 3 ----------------------------- */} + + + + + + + + + + + + + + + + {/* ----------------------------- Row 4 ----------------------------- */} + + option._id} + getOptionLabel={(option) => option.name} + /> + + + option._id} + getOptionLabel={(option) => option.name} + /> + + + option._id} + getOptionLabel={(option) => option.name} + /> + + + + + {/* ----------------------------- Row 5 ----------------------------- */} + + + + + option.name} + getOptionValue={(option) => option._id} + label="Loại mẫu" + /> + + + + Sửa thông tin + + + + + + { + setTestSelectorOpen(false) + }} + previousState={props.sampleRes.results.map(({ testId }) => testId)} + onSubmit={(items) => { + setValue( + 'testIds', + items.map((item) => item._id), + ) + }} + showCombos + /> + { + setBarcodeModalOpen(false) + navigate(-1) + }} + sampleId={sampleId} + name={name} + birthYear={birthYear} + /> +
+ ) +} diff --git a/apps/hcdc-web-app/src/features/sample-info/components/InfoInputForm/index.tsx b/apps/hcdc-web-app/src/features/sample-info/components/InfoInputForm/index.tsx index a6d85c88..c8d2f8b5 100644 --- a/apps/hcdc-web-app/src/features/sample-info/components/InfoInputForm/index.tsx +++ b/apps/hcdc-web-app/src/features/sample-info/components/InfoInputForm/index.tsx @@ -102,14 +102,14 @@ export function InfoInputForm(props: InputFormProps) { }, [props.origins[0]?._id]) const birthYear = watch('birthYear') - const [age, setAge] = useState(currentYear - getValues().birthYear) + const [age, setAge] = useState(currentYear - birthYear) useEffect(() => { setAge(currentYear - birthYear) }, [birthYear]) useEffect(() => { - //@ts-ignore + // @ts-ignore if (typeof birthYear === 'string' && birthYear.length > 0) { setValue('birthYear', Number(birthYear)) } diff --git a/apps/hcdc-web-app/src/features/sample-info/components/index.ts b/apps/hcdc-web-app/src/features/sample-info/components/index.ts index 652f81de..893b3b04 100644 --- a/apps/hcdc-web-app/src/features/sample-info/components/index.ts +++ b/apps/hcdc-web-app/src/features/sample-info/components/index.ts @@ -2,3 +2,5 @@ export * from './BarcodeModal' export * from './PrintBarcode' export * from './SingleBarcode' export * from './InfoInputForm' +export * from './InfoConfirmView' +export * from './InfoEditView' diff --git a/apps/hcdc-web-app/src/features/sample-info/pages/InfoConfirmPage/index.tsx b/apps/hcdc-web-app/src/features/sample-info/pages/InfoConfirmPage/index.tsx index 14beb548..6e367a50 100644 --- a/apps/hcdc-web-app/src/features/sample-info/pages/InfoConfirmPage/index.tsx +++ b/apps/hcdc-web-app/src/features/sample-info/pages/InfoConfirmPage/index.tsx @@ -1,456 +1,28 @@ -import { GridActionsCellItem } from '@mui/x-data-grid' -import CheckIcon from '@mui/icons-material/Check' -import EditIcon from '@mui/icons-material/Edit' -import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom' -import { useEffect, useState } from 'react' -import { format, startOfDay, endOfDay } from 'date-fns' -import { Gender } from '@diut/hcdc' -import { DATETIME_FORMAT } from '@diut/common' -import { Box, Paper, IconButton } from '@mui/material' -import Grid from '@mui/material/Unstable_Grid2' -import LoopIcon from '@mui/icons-material/Loop' +import { useEffect } from 'react' +import { useLoaderData, useRevalidator } from 'react-router-dom' -import { - SampleResponseDto, - SearchSampleResponseDto, - useSampleSearchQuery, - useSampleUpdateByIdMutation, -} from 'src/infra/api/access-service/sample' -import { DataTable } from 'src/components/table' -import { - PatientResponseDto, - useLazyPatientFindByIdQuery, -} from 'src/infra/api/access-service/patient' -import { useTypedSelector } from 'src/core' -import { selectUserId, selectUserIsAdmin } from 'src/infra/auth' -import { usePagination } from 'src/shared/hooks' import { infoConfirmPageLoader } from './loader' -import { - FormContainer, - FormDateTimePicker, - FormSelect, - FormTextField, -} from 'src/components/form' -import { useForm } from 'react-hook-form' - -const ANY_PATIENT_TYPE = 'ANY_PATIENT_TYPE' -const ANY_SAMPLE_ORIGIN = 'ANY_SAMPLE_ORIGIN' - -interface FilterData { - fromDate: Date - toDate: Date - sampleId: string - infoCompleted: 'all' | 'true' | 'false' - patientType: string - sampleOrigin: string -} +import { InfoConfirmView } from '../../components' +import { useTypedSelector } from 'src/infra/redux' +import { authSlice } from 'src/features/auth' export default function InfoConfirmPage() { - const { indicationMap, doctorMap, patientTypeMap, testMap, sampleOriginMap } = + const { diagnosisMap, doctorMap, patientTypeMap, testMap, sampleOriginMap } = useLoaderData() as Awaited> - const userId = useTypedSelector(selectUserId) - const userIsAdmin = useTypedSelector(selectUserIsAdmin) - const navigate = useNavigate() - const [searchParams, setSearchParams] = useSearchParams() - const patientTypeParam = searchParams.get('patientType') ?? ANY_PATIENT_TYPE - const sampleOriginParam = - searchParams.get('sampleOrigin') ?? ANY_SAMPLE_ORIGIN - - const { filterObj, setFilterObj, onPageChange, onPageSizeChange } = - usePagination({ - offset: 0, - limit: 10, - sort: { infoAt: -1, sampleId: -1 }, - filter: { - infoAt: { - $gte: startOfDay(new Date()).toISOString(), - $lte: endOfDay(new Date()).toISOString(), - }, - }, - }) - - const { control, handleSubmit, watch, setValue } = useForm({ - defaultValues: { - fromDate: - searchParams.get('fromDate') != null - ? new Date(searchParams.get('fromDate')!) - : new Date(), - toDate: - searchParams.get('toDate') != null - ? new Date(searchParams.get('toDate')!) - : new Date(), - sampleId: searchParams.get('sampleId') ?? '', - infoCompleted: - (searchParams.get('infoCompleted') as FilterData['infoCompleted']) ?? - 'all', - patientType: patientTypeParam, - sampleOrigin: sampleOriginParam, - }, - }) - const fromDate = watch('fromDate') - const toDate = watch('toDate') - const infoCompleted = watch('infoCompleted') - const patientType = watch('patientType') - const sampleOrigin = watch('sampleOrigin') - - const { - data, - isFetching: isFetchingSamples, - refetch, - } = useSampleSearchQuery(filterObj) - - const handleSubmitFilter = ({ - fromDate, - toDate, - sampleId, - infoCompleted, - patientType, - sampleOrigin, - }: FilterData) => { - setSearchParams( - { - sampleId, - infoCompleted, - fromDate: fromDate.toISOString(), - toDate: toDate.toISOString(), - patientType, - sampleOrigin, - }, - { replace: true }, - ) - return setFilterObj((obj) => ({ - ...obj, - filter: { - ...obj.filter, - sampleId: - sampleId.length > 0 - ? { $regex: sampleId + '$', $options: 'i' } - : undefined, - infoAt: - sampleId.length > 0 - ? undefined - : { - $gte: startOfDay(fromDate).toISOString(), - $lte: endOfDay(toDate).toISOString(), - }, - infoCompleted: - infoCompleted === 'all' - ? undefined - : infoCompleted === 'true' - ? true - : false, - patientTypeId: - patientType !== ANY_PATIENT_TYPE ? patientType : undefined, - sampleOriginId: - sampleOrigin !== ANY_SAMPLE_ORIGIN ? sampleOrigin : undefined, - }, - })) - } + const branchId = useTypedSelector(authSlice.selectors.selectActiveBranchId)! + const revalidator = useRevalidator() useEffect(() => { - if (toDate < fromDate) { - setValue('fromDate', toDate) - } else { - handleSubmit(handleSubmitFilter)() - } - }, [fromDate, toDate, infoCompleted, patientType, sampleOrigin]) - - const [getPatient, { isFetching: isFetchingPatients }] = - useLazyPatientFindByIdQuery() - - const [samples, setSamples] = useState() - const [patients, setPatients] = useState<{ - [id: string]: PatientResponseDto - }>({}) - - async function expandId(samples: SampleResponseDto[]) { - const promises = samples.map(async (sample) => { - const { patientId, results } = sample - getPatient({ id: patientId }, true).then((res) => { - setPatients((cache) => - Object.assign({}, cache, { - [patientId]: res.data!, - }), - ) - }) - }) - return Promise.all(promises) - } - - useEffect(() => { - if (!isFetchingSamples) { - expandId(data?.items!) - setSamples(data!) - } - }, [isFetchingSamples, JSON.stringify(filterObj)]) - - const [updateSample, { isLoading: isConfirming }] = - useSampleUpdateByIdMutation() - - const handleConfirmClick = (sample: SampleResponseDto) => () => { - updateSample({ - id: sample._id, - updateSampleRequestDto: { - infoCompleted: true, - }, - }) - } - - const handleEditClick = (sample: SampleResponseDto) => () => { - navigate('../edit/' + sample.patientId + '/' + sample._id) - } + revalidator.revalidate() + }, [branchId]) return ( - - - - - - 0} - /> - - - 0} - /> - - - label} - getOptionValue={({ value }) => value} - /> - - - ({ - label: name, - value: _id, - })), - ]} - getOptionLabel={({ label }) => label} - getOptionValue={({ value }) => value} - /> - - - ({ - label: name, - value: _id, - })), - ]} - getOptionLabel={({ label }) => label} - getOptionValue={({ value }) => value} - /> - - - - - - - - - - row._id} - columns={[ - { - field: 'startActions', - type: 'actions', - width: 60, - cellClassName: 'actions', - renderHeader: () => ( - { - refetch() - }} - > - - - ), - getActions: ({ row }) => - row.infoCompleted - ? [] - : [ - } - label="Xác nhận" - color="primary" - onClick={handleConfirmClick(row)} - disabled={isConfirming} - />, - ], - }, - { - field: 'infoAt', - headerName: 'Nhận bệnh', - width: 100, - sortable: false, - valueGetter: ({ value }) => { - return format(new Date(value), DATETIME_FORMAT) - }, - }, - { - field: 'sampleId', - headerName: 'ID XN', - width: 120, - sortable: false, - renderCell: ({ value }) => {value}, - }, - { - field: 'name', - headerName: 'Tên', - sortable: false, - width: 100, - valueGetter: ({ row }) => patients[row.patientId]?.name, - }, - { - field: 'birthYear', - headerName: 'Năm', - width: 60, - sortable: false, - valueGetter: ({ row }) => patients[row.patientId]?.birthYear, - }, - { - field: 'gender', - headerName: 'Giới', - width: 60, - sortable: false, - valueGetter: ({ row }) => { - if (patients[row.patientId]?.gender === Gender.Female) { - return 'Nữ' - } else { - return 'Nam' - } - }, - }, - { - field: 'address', - headerName: 'Địa chỉ', - width: 80, - sortable: false, - valueGetter: ({ row }) => patients[row.patientId]?.address, - }, - { - field: 'isTraBuuDien', - headerName: 'BĐ', - width: 60, - align: 'center', - sortable: false, - valueGetter: ({ value }) => { - if (value === true) { - return '✓' - } - return '' - }, - }, - { - field: 'doctor', - headerName: 'Bác sỹ', - width: 100, - sortable: false, - valueGetter: ({ row }) => doctorMap.get(row.doctorId)?.name, - }, - { - field: 'tests', - headerName: 'Chỉ định', - minWidth: 100, - flex: 1, - sortable: false, - valueGetter: ({ row }) => { - return row.results - .map(({ testId }) => testMap.get(testId)?.name) - .join(', ') - }, - }, - { - field: 'indication', - headerName: 'CĐ', - width: 70, - sortable: false, - valueGetter: ({ row }) => - indicationMap.get(row.indicationId)?.name, - }, - { - field: 'patientType', - headerName: 'Đối T.', - width: 70, - sortable: false, - valueGetter: ({ row }) => - patientTypeMap.get(row.patientTypeId)?.name, - }, - { - field: 'endActions', - type: 'actions', - width: 60, - cellClassName: 'actions', - getActions: ({ row }) => - userIsAdmin || - row.infoBy === userId || - row.infoCompleted === false - ? [ - } - label="Sửa" - onClick={handleEditClick(row)} - />, - ] - : [], - }, - ]} - paginationMode="server" - rowCount={samples?.total ?? 0} - page={samples?.offset ?? 0} - pageSize={samples?.limit ?? 10} - onPageChange={onPageChange} - onPageSizeChange={onPageSizeChange} - /> - - + ) } diff --git a/apps/hcdc-web-app/src/features/sample-info/pages/InfoConfirmPage/loader.ts b/apps/hcdc-web-app/src/features/sample-info/pages/InfoConfirmPage/loader.ts index b69920af..2ad98d50 100644 --- a/apps/hcdc-web-app/src/features/sample-info/pages/InfoConfirmPage/loader.ts +++ b/apps/hcdc-web-app/src/features/sample-info/pages/InfoConfirmPage/loader.ts @@ -1,63 +1,40 @@ import { appStore } from 'src/infra/redux' -import { doctorApi } from 'src/infra/api/access-service/doctor' -import { indicationApi } from 'src/infra/api/access-service/indication' -import { patientTypeApi } from 'src/infra/api/access-service/patient-type' -import { testApi } from 'src/infra/api/access-service/test' -import { sampleOriginApi } from 'src/infra/api/access-service/sample-origin' +import { + fetchDiagnoses, + fetchDoctors, + fetchPatientTypes, + fetchSampleOrigins, + fetchTests, +} from 'src/infra/api' +import { authSlice } from 'src/features/auth' export const infoConfirmPageLoader = async () => { - const [indicationRes, doctorRes, patientTypeRes, testRes, sampleOriginRes] = + const branchId = authSlice.selectors.selectActiveBranchId( + appStore.getState(), + )! + const branch = authSlice.selectors.selectActiveBranch( + appStore.getState(), + branchId, + )! + + const [diagnosisRes, doctorRes, patientTypeRes, testRes, sampleOriginRes] = await Promise.all([ - appStore - .dispatch( - indicationApi.endpoints.indicationSearch.initiate({ - searchIndicationRequestDto: { sort: { index: 1 } }, - }), - ) - .unwrap(), - appStore - .dispatch( - doctorApi.endpoints.doctorSearch.initiate({ - searchDoctorRequestDto: { sort: { index: 1 } }, - }), - ) - .unwrap(), - appStore - .dispatch( - patientTypeApi.endpoints.patientTypeSearch.initiate({ - searchPatientTypeRequestDto: { sort: { index: 1 } }, - }), - ) - .unwrap(), - appStore - .dispatch( - testApi.endpoints.testSearch.initiate({ - searchTestRequestDto: { - sort: { - category: 1, - }, - }, - }), - ) - .unwrap(), - appStore - .dispatch( - sampleOriginApi.endpoints.sampleOriginSearch.initiate({ - searchSampleOriginRequestDto: { sort: { index: 1 } }, - }), - ) - .unwrap(), + appStore.dispatch(fetchDiagnoses(branchId)).unwrap(), + appStore.dispatch(fetchDoctors(branchId)).unwrap(), + appStore.dispatch(fetchPatientTypes(branchId)).unwrap(), + appStore.dispatch(fetchTests(branchId)).unwrap(), + appStore.dispatch(fetchSampleOrigins(branch.sampleOriginIds)).unwrap(), ]) const tests = testRes?.items ?? [] - const indications = indicationRes?.items ?? [] + const diagnoses = diagnosisRes?.items ?? [] const sampleOrigins = sampleOriginRes?.items ?? [] const doctors = doctorRes?.items ?? [] const patientTypes = patientTypeRes?.items ?? [] return { - indicationMap: new Map( - indications.map((indication) => [indication._id, indication]), + diagnosisMap: new Map( + diagnoses.map((diagnosis) => [diagnosis._id, diagnosis]), ), sampleOriginMap: new Map( sampleOrigins.map((sampleOrigin) => [sampleOrigin._id, sampleOrigin]), diff --git a/apps/hcdc-web-app/src/features/sample-info/pages/InfoEditPage/index.tsx b/apps/hcdc-web-app/src/features/sample-info/pages/InfoEditPage/index.tsx index b04f7760..bd6aed0b 100644 --- a/apps/hcdc-web-app/src/features/sample-info/pages/InfoEditPage/index.tsx +++ b/apps/hcdc-web-app/src/features/sample-info/pages/InfoEditPage/index.tsx @@ -1,420 +1,44 @@ -import { Gender } from '@diut/hcdc' -import { useEffect, useState } from 'react' -import { LoadingButton } from '@mui/lab' -import { - Button, - FormControl, - FormControlLabel, - RadioGroup, - Radio, - Paper, - TextField, - Box, - Typography, -} from '@mui/material' -import Grid from '@mui/material/Unstable_Grid2' -import { Controller, useForm } from 'react-hook-form' -import { toast } from 'react-toastify' -import { useLoaderData, useNavigate, useParams } from 'react-router-dom' +import { useEffect } from 'react' +import { useLoaderData, useParams, useRevalidator } from 'react-router-dom' -import { - FormContainer, - FormSwitch, - FormTextField, - FormDateTimePicker, - FormCheckboxGroup, - FormSelect, -} from 'src/components/form' -import { - formResolver, - FormSchema, -} from '../../components/InfoInputForm/validation' -import { TestSelector } from 'src/features/test/components/TestSelector' -import { - useSampleDeleteByIdMutation, - useSampleUpdateByIdMutation, -} from 'src/infra/api/access-service/sample' -import { usePatientUpdateByIdMutation } from 'src/infra/api/access-service/patient' -import { infoEditPageLoader } from './loader' -import { useTypedSelector } from 'src/core' -import { selectUserIsAdmin } from 'src/infra/auth' -import { BarcodeModal } from '../../components/BarcodeModal' +import { InfoEditPageParams, infoEditPageLoader } from './loader' +import { InfoEditView } from '../../components' +import { useTypedSelector } from 'src/infra/redux' +import { authSlice } from 'src/features/auth' -const currentYear = new Date().getFullYear() +export function urlInfoEditPage(params: InfoEditPageParams) { + return `/info/edit/${params.patientId}/${params.sampleId}` +} export default function InfoEditPage() { - const navigate = useNavigate() - const { sampleId, patientId } = useParams() const { - author, - sampleInfo, - patientInfo, patientTypes, - indications, + diagnoses, doctors, sampleTypes, - sampleOrigins, + origins, + patientRes, + sampleRes, } = useLoaderData() as Awaited> - const userIsAdmin = useTypedSelector(selectUserIsAdmin) - - const { - control, - handleSubmit, - watch, - setValue, - getValues, - formState: { isSubmitting }, - } = useForm({ - resolver: formResolver, - defaultValues: { - ...sampleInfo, - infoAt: new Date(sampleInfo.infoAt), - sampledAt: new Date(sampleInfo.sampledAt), - tests: sampleInfo.results.map(({ testId, bioProductName }) => ({ - id: testId, - bioProductName, - })), - ...patientInfo, - }, - }) - - const birthYear = watch('birthYear') - const [age, setAge] = useState(currentYear - getValues().birthYear) - - useEffect(() => { - setAge(currentYear - birthYear) - }, [birthYear]) + const branchId = useTypedSelector(authSlice.selectors.selectActiveBranchId)! + const revalidator = useRevalidator() + const { sampleId, patientId } = useParams() useEffect(() => { - //@ts-ignore - if (typeof birthYear === 'string' && birthYear.length > 0) { - setValue('birthYear', Number(birthYear)) - } - }, [birthYear]) - - const [testSelectorOpen, setTestSelectorOpen] = useState(false) - - const [updateSample] = useSampleUpdateByIdMutation() - const [updatePatient] = usePatientUpdateByIdMutation() - - const [deleteSample, { isLoading: isDeletingSample }] = - useSampleDeleteByIdMutation() - - const [barcodeModalOpen, setBarcodeModalOpen] = useState(false) + revalidator.revalidate() + }, [branchId]) return ( - - - -
- - {author.name} - - -
-
- { - return Promise.all([ - updatePatient({ - id: patientId!, - updatePatientRequestDto: values, - }).unwrap(), - updateSample({ - id: sampleId!, - updateSampleRequestDto: { - ...values, - note: values.note ?? '', - results: sampleInfo.results, // needed for backend update logic - sampleCompleted: sampleInfo.sampleCompleted, // needed for backend update logic - sampledAt: values.sampledAt.toISOString(), - infoAt: values.infoAt.toISOString(), - }, - }).unwrap(), - ]).then(() => { - setBarcodeModalOpen(true) - }) - })} - > - - - {/* ----------------------------- Row 1 ----------------------------- */} - - - - - - - - - - - - - {/* ----------------------------- Row 2 ----------------------------- */} - - ( - - - } - label="Nam" - /> - } - label="Nữ" - /> - - - )} - /> - - - - - - { - setValue('birthYear', currentYear - Number(e.target.value), { - shouldValidate: true, - }) - }} - fullWidth - label="Tuổi" - /> - - - - - - option._id} - getOptionLabel={(option) => option.name} - /> - - {/* ----------------------------- Row 3 ----------------------------- */} - - - - - - - - - - - - - - - - {/* ----------------------------- Row 4 ----------------------------- */} - - option._id} - getOptionLabel={(option) => option.name} - /> - - - option._id} - getOptionLabel={(option) => option.name} - /> - - - option._id} - getOptionLabel={(option) => option.name} - /> - - - - - {/* ----------------------------- Row 5 ----------------------------- */} - - - - - option.name} - getOptionValue={(option) => option._id} - label="Loại mẫu" - /> - - - - Sửa thông tin - - - - - - { - setTestSelectorOpen(false) - }} - previousState={sampleInfo.results.map(({ testId }) => testId)} - onSubmit={(items) => { - setValue( - 'tests', - items.map((item) => ({ - bioProductName: item.bioProduct?.name, - id: item._id, - })), - ) - }} - showCombos - /> - { - setBarcodeModalOpen(false) - navigate(-1) - }} - sampleId={getValues().sampleId} - name={getValues().name} - birthYear={getValues().birthYear} - /> -
+ ) } diff --git a/apps/hcdc-web-app/src/features/sample-info/pages/InfoEditPage/loader.ts b/apps/hcdc-web-app/src/features/sample-info/pages/InfoEditPage/loader.ts index 6ba1dabb..bfcdf345 100644 --- a/apps/hcdc-web-app/src/features/sample-info/pages/InfoEditPage/loader.ts +++ b/apps/hcdc-web-app/src/features/sample-info/pages/InfoEditPage/loader.ts @@ -1,92 +1,54 @@ import { LoaderFunctionArgs } from 'react-router-dom' import { appStore } from 'src/infra/redux' -import { doctorApi } from 'src/infra/api/access-service/doctor' -import { indicationApi } from 'src/infra/api/access-service/indication' import { patientApi } from 'src/infra/api/access-service/patient' -import { patientTypeApi } from 'src/infra/api/access-service/patient-type' import { sampleApi } from 'src/infra/api/access-service/sample' -import { sampleTypeApi } from 'src/infra/api/access-service/sample-type' -import { userApi } from 'src/infra/api/access-service/user' -import { sampleOriginApi } from 'src/infra/api/access-service/sample-origin' +import { + fetchDiagnoses, + fetchDoctors, + fetchPatientTypes, + fetchSampleOrigins, + fetchSampleTypes, +} from 'src/infra/api' + +export type InfoEditPageParams = { + sampleId: string + patientId: string +} export const infoEditPageLoader = async ({ params }: LoaderFunctionArgs) => { - const { sampleId, patientId } = params + const { sampleId, patientId } = params as InfoEditPageParams + const sampleRes = await appStore + .dispatch(sampleApi.endpoints.sampleFindById.initiate(sampleId!)) + .unwrap() + const [ - sampleInfo, - patientInfo, - patientTypes, - indications, - doctors, - sampleTypes, - sampleOrigins, + patientRes, + patientTypeRes, + diagnosisRes, + doctorRes, + sampleTypeRes, + originRes, ] = await Promise.all([ appStore - .dispatch( - sampleApi.endpoints.sampleFindById.initiate({ - id: sampleId!, - }), - ) - .unwrap(), - appStore - .dispatch( - patientApi.endpoints.patientFindById.initiate({ - id: patientId!, - }), - ) - .unwrap(), - appStore - .dispatch( - patientTypeApi.endpoints.patientTypeSearch.initiate({ - searchPatientTypeRequestDto: { sort: { index: 1 } }, - }), - ) + .dispatch(patientApi.endpoints.patientFindById.initiate(patientId!)) .unwrap(), + appStore.dispatch(fetchPatientTypes(sampleRes.branchId)).unwrap(), + appStore.dispatch(fetchDiagnoses(sampleRes.branchId)).unwrap(), + appStore.dispatch(fetchDoctors(sampleRes.branchId)).unwrap(), + appStore.dispatch(fetchSampleTypes(sampleRes.branchId)).unwrap(), appStore - .dispatch( - indicationApi.endpoints.indicationSearch.initiate({ - searchIndicationRequestDto: { sort: { index: 1 } }, - }), - ) - .unwrap(), - appStore - .dispatch( - doctorApi.endpoints.doctorSearch.initiate({ - searchDoctorRequestDto: { sort: { index: 1 } }, - }), - ) - .unwrap(), - appStore - .dispatch( - sampleTypeApi.endpoints.sampleTypeSearch.initiate({ - searchSampleTypeRequestDto: { sort: { index: 1 } }, - }), - ) - .unwrap(), - appStore - .dispatch( - sampleOriginApi.endpoints.sampleOriginSearch.initiate({ - searchSampleOriginRequestDto: { sort: { index: 1 } }, - }), - ) + .dispatch(fetchSampleOrigins(sampleRes.branch?.sampleOriginIds!)) .unwrap(), ]) - const author = - (await appStore - .dispatch( - userApi.endpoints.userFindById.initiate({ id: sampleInfo.infoBy }), - ) - .unwrap()) ?? {} - return { - author, - sampleInfo, - patientInfo, - patientTypes, - indications, - doctors, - sampleTypes, - sampleOrigins, + sampleRes, + patientRes, + patientTypes: patientTypeRes.items, + diagnoses: diagnosisRes.items, + doctors: doctorRes.items, + sampleTypes: sampleTypeRes.items, + origins: originRes.items, } } diff --git a/apps/hcdc-web-app/src/features/sample-info/pages/InfoInputPage/loader.ts b/apps/hcdc-web-app/src/features/sample-info/pages/InfoInputPage/loader.ts index 392e3580..4e15dd1a 100644 --- a/apps/hcdc-web-app/src/features/sample-info/pages/InfoInputPage/loader.ts +++ b/apps/hcdc-web-app/src/features/sample-info/pages/InfoInputPage/loader.ts @@ -1,62 +1,29 @@ import { appStore } from 'src/infra/redux' -import { doctorApi } from 'src/infra/api/access-service/doctor' -import { patientTypeApi } from 'src/infra/api/access-service/patient-type' -import { sampleTypeApi } from 'src/infra/api/access-service/sample-type' -import { diagnosisApi } from 'src/infra/api/access-service/diagnosis' -import { branchApi } from 'src/infra/api/access-service/branch' import { authSlice } from 'src/features/auth' +import { + fetchDiagnoses, + fetchDoctors, + fetchPatientTypes, + fetchSampleOrigins, + fetchSampleTypes, +} from 'src/infra/api' export const infoInputPageLoader = async () => { - const branchId = authSlice.selectors.selectActiveBranchId(appStore.getState()) + const branchId = authSlice.selectors.selectActiveBranchId( + appStore.getState(), + )! const branch = authSlice.selectors.selectActiveBranch( appStore.getState(), branchId, )! - const [patientTypeRes, diagnosisRes, doctorRes, sampleTypeRes, branchRes] = + const [patientTypeRes, diagnosisRes, doctorRes, sampleTypeRes, originRes] = await Promise.all([ - appStore - .dispatch( - patientTypeApi.endpoints.patientTypeSearch.initiate({ - sort: { displayIndex: 1 }, - filter: { branchId }, - }), - ) - .unwrap(), - appStore - .dispatch( - diagnosisApi.endpoints.diagnosisSearch.initiate({ - sort: { displayIndex: 1 }, - filter: { branchId }, - }), - ) - .unwrap(), - appStore - .dispatch( - doctorApi.endpoints.doctorSearch.initiate({ - sort: { displayIndex: 1 }, - filter: { branchId }, - }), - ) - .unwrap(), - appStore - .dispatch( - sampleTypeApi.endpoints.sampleTypeSearch.initiate({ - sort: { displayIndex: 1 }, - filter: { branchId }, - }), - ) - .unwrap(), - appStore - .dispatch( - branchApi.endpoints.branchSearch.initiate({ - sort: { displayIndex: 1 }, - filter: { - _id: { $in: branch.sampleOriginIds }, - }, - }), - ) - .unwrap(), + appStore.dispatch(fetchPatientTypes(branchId)).unwrap(), + appStore.dispatch(fetchDiagnoses(branchId)).unwrap(), + appStore.dispatch(fetchDoctors(branchId)).unwrap(), + appStore.dispatch(fetchSampleTypes(branchId)).unwrap(), + appStore.dispatch(fetchSampleOrigins(branch.sampleOriginIds)).unwrap(), ]) return { @@ -64,6 +31,6 @@ export const infoInputPageLoader = async () => { diagnoses: diagnosisRes.items, doctors: doctorRes.items, sampleTypes: sampleTypeRes.items, - origins: branchRes.items, + origins: originRes.items, } } diff --git a/apps/hcdc-web-app/src/features/test-element/pages/ManageTestElementPage/loader.ts b/apps/hcdc-web-app/src/features/test-element/pages/ManageTestElementPage/loader.ts index 6f7e5aee..81cc322a 100644 --- a/apps/hcdc-web-app/src/features/test-element/pages/ManageTestElementPage/loader.ts +++ b/apps/hcdc-web-app/src/features/test-element/pages/ManageTestElementPage/loader.ts @@ -1,22 +1,14 @@ import { appStore } from 'src/infra/redux' -import { testApi } from 'src/infra/api/access-service/test' import { authSlice } from 'src/features/auth' +import { fetchTests } from 'src/infra/api' export const manageTestElemenentPageLoader = async () => { - const branchId = authSlice.selectors.selectActiveBranchId(appStore.getState()) + const branchId = authSlice.selectors.selectActiveBranchId( + appStore.getState(), + )! const [testRes] = await Promise.all([ - appStore - .dispatch( - testApi.endpoints.testSearch.initiate({ - sort: { displayIndex: 1 }, - filter: { branchId }, - populates: [ - { path: 'testCategory', fields: ['name', 'displayIndex'] }, - ], - }), - ) - .unwrap(), + appStore.dispatch(fetchTests(branchId)).unwrap(), ]) return { diff --git a/apps/hcdc-web-app/src/features/test/pages/ManageTestPage/loader.ts b/apps/hcdc-web-app/src/features/test/pages/ManageTestPage/loader.ts index a92f61e8..1a4c23bc 100644 --- a/apps/hcdc-web-app/src/features/test/pages/ManageTestPage/loader.ts +++ b/apps/hcdc-web-app/src/features/test/pages/ManageTestPage/loader.ts @@ -1,13 +1,17 @@ import { appStore } from 'src/infra/redux' -import { testCategoryApi } from 'src/infra/api/access-service/test-category' -import { bioProductApi } from 'src/infra/api/access-service/bio-product' -import { printFormApi } from 'src/infra/api/access-service/print-form' import { authSlice } from 'src/features/auth' -import { instrumentApi } from 'src/infra/api/access-service/instrument' -import { sampleTypeApi } from 'src/infra/api/access-service/sample-type' +import { + fetchBioProducts, + fetchInstruments, + fetchPrintForms, + fetchSampleTypes, + fetchTestCategories, +} from 'src/infra/api' export const manageTestPageLoader = async () => { - const branchId = authSlice.selectors.selectActiveBranchId(appStore.getState()) + const branchId = authSlice.selectors.selectActiveBranchId( + appStore.getState(), + )! const [ categoryRes, @@ -16,46 +20,11 @@ export const manageTestPageLoader = async () => { printFormRes, sampleTypeRes, ] = await Promise.all([ - appStore - .dispatch( - testCategoryApi.endpoints.testCategorySearch.initiate({ - sort: { displayIndex: 1 }, - filter: { branchId }, - }), - ) - .unwrap(), - appStore - .dispatch( - bioProductApi.endpoints.bioProductSearch.initiate({ - sort: { displayIndex: 1 }, - filter: { branchId }, - }), - ) - .unwrap(), - appStore - .dispatch( - instrumentApi.endpoints.instrumentSearch.initiate({ - sort: { displayIndex: 1 }, - filter: { branchId }, - }), - ) - .unwrap(), - appStore - .dispatch( - printFormApi.endpoints.printFormSearch.initiate({ - sort: { displayIndex: 1 }, - filter: { branchId }, - }), - ) - .unwrap(), - appStore - .dispatch( - sampleTypeApi.endpoints.sampleTypeSearch.initiate({ - sort: { displayIndex: 1 }, - filter: { branchId }, - }), - ) - .unwrap(), + appStore.dispatch(fetchTestCategories(branchId)).unwrap(), + appStore.dispatch(fetchBioProducts(branchId)).unwrap(), + appStore.dispatch(fetchInstruments(branchId)).unwrap(), + appStore.dispatch(fetchPrintForms(branchId)).unwrap(), + appStore.dispatch(fetchSampleTypes(branchId)).unwrap(), ]) return { diff --git a/apps/hcdc-web-app/src/infra/api/access-service/index.ts b/apps/hcdc-web-app/src/infra/api/access-service/index.ts index 80a3ed5a..1b6fe813 100644 --- a/apps/hcdc-web-app/src/infra/api/access-service/index.ts +++ b/apps/hcdc-web-app/src/infra/api/access-service/index.ts @@ -1 +1,85 @@ +import { bioProductApi } from './bio-product' +import { branchApi } from './branch' +import { diagnosisApi } from './diagnosis' +import { doctorApi } from './doctor' +import { instrumentApi } from './instrument' +import { patientTypeApi } from './patient-type' +import { printFormApi } from './print-form' +import { sampleTypeApi } from './sample-type' +import { testApi } from './test' +import { testCategoryApi } from './test-category' + export * from './slice' + +export function fetchTestCategories(branchId: string) { + return testCategoryApi.endpoints.testCategorySearch.initiate({ + sort: { displayIndex: 1 }, + filter: { branchId }, + }) +} + +export function fetchTests(branchId: string) { + return testApi.endpoints.testSearch.initiate({ + sort: { displayIndex: 1 }, + filter: { branchId }, + populates: [{ path: 'testCategory', fields: ['name', 'displayIndex'] }], + }) +} + +export function fetchDiagnoses(branchId: string) { + return diagnosisApi.endpoints.diagnosisSearch.initiate({ + sort: { displayIndex: 1 }, + filter: { branchId }, + }) +} + +export function fetchDoctors(branchId: string) { + return doctorApi.endpoints.doctorSearch.initiate({ + sort: { displayIndex: 1 }, + filter: { branchId }, + }) +} + +export function fetchPatientTypes(branchId: string) { + return patientTypeApi.endpoints.patientTypeSearch.initiate({ + sort: { displayIndex: 1 }, + filter: { branchId }, + }) +} + +export function fetchBioProducts(branchId: string) { + return bioProductApi.endpoints.bioProductSearch.initiate({ + sort: { displayIndex: 1 }, + filter: { branchId }, + }) +} + +export function fetchInstruments(branchId: string) { + return instrumentApi.endpoints.instrumentSearch.initiate({ + sort: { displayIndex: 1 }, + filter: { branchId }, + }) +} + +export function fetchPrintForms(branchId: string) { + return printFormApi.endpoints.printFormSearch.initiate({ + sort: { displayIndex: 1 }, + filter: { branchId }, + }) +} + +export function fetchSampleTypes(branchId: string) { + return sampleTypeApi.endpoints.sampleTypeSearch.initiate({ + sort: { displayIndex: 1 }, + filter: { branchId }, + }) +} + +export function fetchSampleOrigins(branchIds: string[]) { + return branchApi.endpoints.branchSearch.initiate({ + sort: { displayIndex: 1 }, + filter: { + _id: { $in: branchIds }, + }, + }) +} diff --git a/apps/hcdc-web-app/src/infra/router/routes.tsx b/apps/hcdc-web-app/src/infra/router/routes.tsx index 1b5d70a6..4f220f8e 100644 --- a/apps/hcdc-web-app/src/infra/router/routes.tsx +++ b/apps/hcdc-web-app/src/infra/router/routes.tsx @@ -4,10 +4,10 @@ import { Outlet } from 'react-router-dom' import HomePage from 'src/features/homepage/pages/Homepage' import { MainLayout } from 'src/components/layout/MainLayout' import { CustomRouteObject } from 'src/infra/router' -// import { infoEditPageLoader } from 'src/features/sample-info/pages/InfoEditPage/loader' +import { infoEditPageLoader } from 'src/features/sample-info/pages/InfoEditPage/loader' // import { editResultPageLoader } from 'src/features/sample-result/pages/EditResultPage/loader' import { infoInputPageLoader } from 'src/features/sample-info/pages/InfoInputPage/loader' -// import { infoConfirmPageLoader } from 'src/features/sample-info/pages/InfoConfirmPage/loader' +import { infoConfirmPageLoader } from 'src/features/sample-info/pages/InfoConfirmPage/loader' // import { editSelectPageLoader } from 'src/features/sample-result/pages/EditSelectPage/loader' import { manageTestPageLoader } from 'src/features/test/pages/ManageTestPage/loader' import { manageTestElemenentPageLoader } from 'src/features/test-element/pages/ManageTestElementPage/loader' @@ -50,12 +50,12 @@ const ManagePrintFormPage = React.lazy( const InfoInputPage = React.lazy( () => import('src/features/sample-info/pages/InfoInputPage'), ) -// const InfoEditPage = React.lazy( -// () => import('src/features/sample-info/pages/InfoEditPage'), -// ) -// const InfoConfirmPage = React.lazy( -// () => import('src/features/sample-info/pages/InfoConfirmPage'), -// ) +const InfoEditPage = React.lazy( + () => import('src/features/sample-info/pages/InfoEditPage'), +) +const InfoConfirmPage = React.lazy( + () => import('src/features/sample-info/pages/InfoConfirmPage'), +) // const EditResultPage = React.lazy( // () => import('src/features/sample-result/pages/EditResultPage'), // ) @@ -148,16 +148,16 @@ export const appRoutes: CustomRouteObject[] = [ element: , loader: infoInputPageLoader, }, - // { - // path: 'edit/:patientId/:sampleId', - // element: , - // loader: infoEditPageLoader, - // }, - // { - // path: 'confirm', - // element: , - // loader: infoConfirmPageLoader, - // }, + { + path: 'edit/:patientId/:sampleId', + element: , + loader: infoEditPageLoader, + }, + { + path: 'confirm', + element: , + loader: infoConfirmPageLoader, + }, ], }, // { diff --git a/apps/hcdc-web-app/src/main.tsx b/apps/hcdc-web-app/src/main.tsx index ee0f91cf..6214580c 100644 --- a/apps/hcdc-web-app/src/main.tsx +++ b/apps/hcdc-web-app/src/main.tsx @@ -1,4 +1,4 @@ -import React from 'react' +// import React from 'react' import ReactDOM from 'react-dom/client' import { checkEnvVariables } from './config' @@ -8,7 +8,7 @@ checkEnvVariables() const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement) root.render( - - - , + // + , + // , ) diff --git a/apps/hcdc-web-app/src/shared/utils/index.ts b/apps/hcdc-web-app/src/shared/utils/index.ts index a6979941..9855d0a9 100644 --- a/apps/hcdc-web-app/src/shared/utils/index.ts +++ b/apps/hcdc-web-app/src/shared/utils/index.ts @@ -1,3 +1,4 @@ export * from './app-exception' export * from './image-crop' export * from './print-barcode' +export * from './pagination' diff --git a/apps/hcdc-web-app/src/shared/utils/pagination.ts b/apps/hcdc-web-app/src/shared/utils/pagination.ts new file mode 100644 index 00000000..4f5ce0d8 --- /dev/null +++ b/apps/hcdc-web-app/src/shared/utils/pagination.ts @@ -0,0 +1,8 @@ +import { endOfDay, startOfDay } from 'date-fns' + +export function makeDateFilter(fromDate = new Date(), toDate = new Date()) { + return { + $gte: startOfDay(fromDate).toISOString(), + $lte: endOfDay(toDate).toISOString(), + } +}