From 7fd3e2b38c7728d6640a800255f486e8d9b5dbca Mon Sep 17 00:00:00 2001 From: Ha Minh Chien Date: Fri, 1 Mar 2024 17:57:31 +0700 Subject: [PATCH] update --- .../v1/test-element/dto/create.request-dto.ts | 8 +- .../v1/test-element/dto/normal-rule.dto.ts | 2 +- .../form/FormAutocomplete/index.tsx | 26 +- .../pages/EditResultPage/components/utils.ts | 6 +- .../pages/EditResultPage/index.tsx | 6 +- .../pages/EditResultPage/utils.ts | 11 +- .../components/NormalRuleEditor/columns.tsx | 84 +++++ .../components/NormalRuleEditor/index.tsx | 93 ++++++ .../components/TestElementTable/columns.tsx | 57 ++++ .../components/TestElementTable/index.tsx | 184 +++++++++++ .../features/test-element/components/index.ts | 1 + .../src/features/test-element/index.ts | 1 + .../TestElementTable/HighlightRuleEditor.tsx | 179 ----------- .../components/TestElementTable/columns.tsx | 203 ------------ .../components/TestElementTable/index.tsx | 294 ------------------ .../pages/ManageTestElementPage/index.tsx | 82 ++++- .../pages/ManageTestElementPage/loader.ts | 24 +- .../src/infra/api/access-service/sample.ts | 4 +- apps/hcdc-web-app/src/infra/router/routes.tsx | 18 +- .../dtos/create-test-element.request-dto.ts | 8 +- .../dtos/test-element.response-dto.ts | 8 +- 21 files changed, 568 insertions(+), 731 deletions(-) create mode 100644 apps/hcdc-web-app/src/features/test-element/components/NormalRuleEditor/columns.tsx create mode 100644 apps/hcdc-web-app/src/features/test-element/components/NormalRuleEditor/index.tsx create mode 100644 apps/hcdc-web-app/src/features/test-element/components/TestElementTable/columns.tsx create mode 100644 apps/hcdc-web-app/src/features/test-element/components/TestElementTable/index.tsx create mode 100644 apps/hcdc-web-app/src/features/test-element/components/index.ts create mode 100644 apps/hcdc-web-app/src/features/test-element/index.ts delete mode 100644 apps/hcdc-web-app/src/features/test-element/pages/ManageTestElementPage/components/TestElementTable/HighlightRuleEditor.tsx delete mode 100644 apps/hcdc-web-app/src/features/test-element/pages/ManageTestElementPage/components/TestElementTable/columns.tsx delete mode 100644 apps/hcdc-web-app/src/features/test-element/pages/ManageTestElementPage/components/TestElementTable/index.tsx diff --git a/apps/hcdc-access-service/src/presentation/http/v1/test-element/dto/create.request-dto.ts b/apps/hcdc-access-service/src/presentation/http/v1/test-element/dto/create.request-dto.ts index 16efea9b..8f030ca7 100644 --- a/apps/hcdc-access-service/src/presentation/http/v1/test-element/dto/create.request-dto.ts +++ b/apps/hcdc-access-service/src/presentation/http/v1/test-element/dto/create.request-dto.ts @@ -12,7 +12,7 @@ import { } from 'class-validator' import { exampleTestElement } from 'src/domain' -import { TestElementNormalRuleDto } from './normal-rule.dto' +import { TestElementTestElementNormalRuleDto } from './normal-rule.dto' export class TestElementCreateRequestDto { @Expose() @@ -53,12 +53,12 @@ export class TestElementCreateRequestDto { @Expose() @ApiProperty({ ...exampleTestElement.normalRules, - type: () => TestElementNormalRuleDto, + type: () => TestElementTestElementNormalRuleDto, }) @IsArray() @ValidateNested({ each: true }) - @Type(() => TestElementNormalRuleDto) - normalRules: TestElementNormalRuleDto[] + @Type(() => TestElementTestElementNormalRuleDto) + normalRules: TestElementTestElementNormalRuleDto[] @Expose() @ApiProperty(exampleTestElement.testId) diff --git a/apps/hcdc-access-service/src/presentation/http/v1/test-element/dto/normal-rule.dto.ts b/apps/hcdc-access-service/src/presentation/http/v1/test-element/dto/normal-rule.dto.ts index 175d7437..7b528311 100644 --- a/apps/hcdc-access-service/src/presentation/http/v1/test-element/dto/normal-rule.dto.ts +++ b/apps/hcdc-access-service/src/presentation/http/v1/test-element/dto/normal-rule.dto.ts @@ -11,7 +11,7 @@ import { import { exampleNormalRule } from 'src/domain' -export class TestElementNormalRuleDto { +export class TestElementTestElementNormalRuleDto { @Expose() @ApiProperty(exampleNormalRule.category) @IsEnum(PatientCategory) diff --git a/apps/hcdc-web-app/src/components/form/FormAutocomplete/index.tsx b/apps/hcdc-web-app/src/components/form/FormAutocomplete/index.tsx index 887f0eee..d227e6fc 100644 --- a/apps/hcdc-web-app/src/components/form/FormAutocomplete/index.tsx +++ b/apps/hcdc-web-app/src/components/form/FormAutocomplete/index.tsx @@ -20,6 +20,7 @@ export type FormAutocompleteProps< getOptionValue: (option: OptionType) => unknown disableError?: boolean + multiple?: boolean groupBy?: AutocompleteProps< OptionType, undefined, @@ -30,7 +31,7 @@ export type FormAutocompleteProps< export function FormAutocomplete< TFieldValues extends FieldValues = FieldValues, - OptionType = any, + OptionType = unknown, >({ name, label, @@ -39,6 +40,7 @@ export function FormAutocomplete< getOptionLabel, getOptionValue, groupBy, + multiple = false, disableError = false, }: FormAutocompleteProps) { return ( @@ -56,18 +58,28 @@ export function FormAutocomplete< return ( { - onChange(value.map(getOptionValue)) + if (value) { + if (Array.isArray(value)) { + onChange(value.map(getOptionValue)) + } else { + onChange(getOptionValue(value)) + } + } }} - value={options.filter((option) => - value?.includes(getOptionValue(option)), - )} - filterSelectedOptions + value={ + multiple === true + ? options.filter((option) => + value?.includes(getOptionValue(option)), + ) + : options.find((option) => getOptionValue(option) === value) + } renderInput={(params) => ( void - getHighlightRule: (highlightRules: HighlightRuleDto[]) => HighlightRuleDto + getHighlightRule: ( + highlightRules: TestElementNormalRuleDto[], + ) => TestElementNormalRuleDto sampleId: string } diff --git a/apps/hcdc-web-app/src/features/sample-result/pages/EditResultPage/index.tsx b/apps/hcdc-web-app/src/features/sample-result/pages/EditResultPage/index.tsx index c45531b4..3cf06da6 100644 --- a/apps/hcdc-web-app/src/features/sample-result/pages/EditResultPage/index.tsx +++ b/apps/hcdc-web-app/src/features/sample-result/pages/EditResultPage/index.tsx @@ -27,7 +27,7 @@ import { merge } from 'lodash' import { useSampleUpdateByIdMutation } from 'src/infra/api/access-service/sample' import { TestResponseDto } from 'src/infra/api/access-service/test' import { - HighlightRuleDto, + TestElementNormalRuleDto, TestElementResponseDto, useLazyTestElementSearchQuery, } from 'src/infra/api/access-service/test-element' @@ -65,13 +65,13 @@ export default function EditResultPage() { }, [sampleId]) const getHighlightRule = useCallback( - (highlightRules: HighlightRuleDto[]) => { + (highlightRules: TestElementNormalRuleDto[]) => { return ( highlightRules.find(({ category }) => category === patientCategory) ?? highlightRules.find( ({ category }) => category === PatientCategory.Any, ) ?? - ({} as HighlightRuleDto) + ({} as TestElementNormalRuleDto) ) }, [patientCategory], diff --git a/apps/hcdc-web-app/src/features/sample-result/pages/EditResultPage/utils.ts b/apps/hcdc-web-app/src/features/sample-result/pages/EditResultPage/utils.ts index 6cc03697..e60bfe81 100644 --- a/apps/hcdc-web-app/src/features/sample-result/pages/EditResultPage/utils.ts +++ b/apps/hcdc-web-app/src/features/sample-result/pages/EditResultPage/utils.ts @@ -1,14 +1,14 @@ import { PatientCategory } from '@diut/hcdc' -import { HighlightRuleDto } from 'src/infra/api/access-service/test-element' +import { TestElementNormalRuleDto } from 'src/infra/api/access-service/test-element' export function getTechnicalHint( patientCategory: PatientCategory, - highlightRules: HighlightRuleDto[], + highlightRules: TestElementNormalRuleDto[], ) { const highlightRule = highlightRules.find(({ category }) => category === patientCategory) ?? highlightRules.find(({ category }) => category === PatientCategory.Any) ?? - ({} as HighlightRuleDto) + ({} as TestElementNormalRuleDto) const { min, max, normalValue, description, category } = highlightRule @@ -49,7 +49,10 @@ export function getTechnicalHint( return '' } -export function checkHighlight(highlightRule: HighlightRuleDto, value: string) { +export function checkHighlight( + highlightRule: TestElementNormalRuleDto, + value: string, +) { const { min, max, normalValue } = highlightRule if (min != undefined && max != undefined) { diff --git a/apps/hcdc-web-app/src/features/test-element/components/NormalRuleEditor/columns.tsx b/apps/hcdc-web-app/src/features/test-element/components/NormalRuleEditor/columns.tsx new file mode 100644 index 00000000..6441fb54 --- /dev/null +++ b/apps/hcdc-web-app/src/features/test-element/components/NormalRuleEditor/columns.tsx @@ -0,0 +1,84 @@ +import { PatientCategory, PatientCategoryValues } from '@diut/hcdc' +import { GridColDef } from '@mui/x-data-grid' + +import { TestElementNormalRuleDto } from 'src/infra/api/access-service/test-element' + +const patientCategoryDisplayText = { + [PatientCategory.Any]: 'Tất cả', + [PatientCategory.YoungMale]: 'Bé trai', + [PatientCategory.YoungFemale]: 'Bé gái', + [PatientCategory.MatureMale]: 'Nam', + [PatientCategory.MatureFemale]: 'Nữ', + [PatientCategory.Pregnant]: 'Thai phụ', +} + +export type TestElementNormalRuleDtoWithId = TestElementNormalRuleDto & { + id: string +} + +export const normalRuleColumns: GridColDef[] = [ + { + field: 'category', + headerName: 'Phân loại', + type: 'singleSelect', + minWidth: 150, + sortable: false, + editable: true, + valueOptions: PatientCategoryValues.map((category) => ({ + value: category, + label: patientCategoryDisplayText[category], + })), + // valueFormatter: ({ value }) => patientCategoryDisplayText[value as PatientCategory], + }, + { + field: 'normalLowerBound', + headerName: 'Min', + type: 'number', + width: 100, + sortable: false, + editable: true, + }, + { + field: 'normalUpperBound', + headerName: 'Max', + type: 'number', + width: 100, + sortable: false, + editable: true, + }, + { + field: 'normalValue', + headerName: 'So sánh', + minWidth: 100, + flex: 1, + sortable: false, + editable: true, + }, + { + field: 'description', + headerName: 'Mô tả', + minWidth: 100, + flex: 1, + sortable: false, + editable: true, + }, + { + field: 'note', + headerName: 'Tham khảo', + minWidth: 100, + flex: 1, + sortable: false, + editable: true, + }, + { + field: 'defaultChecked', + type: 'boolean', + headerName: 'Mặc định', + minWidth: 80, + sortable: false, + editable: true, + valueGetter: ({ value }) => { + return value ?? false + }, + }, +] diff --git a/apps/hcdc-web-app/src/features/test-element/components/NormalRuleEditor/index.tsx b/apps/hcdc-web-app/src/features/test-element/components/NormalRuleEditor/index.tsx new file mode 100644 index 00000000..c5caabcd --- /dev/null +++ b/apps/hcdc-web-app/src/features/test-element/components/NormalRuleEditor/index.tsx @@ -0,0 +1,93 @@ +import { useEffect, useState } from 'react' +import { Box, Button } from '@mui/material' + +import { + TestElementNormalRuleDto, + TestElementResponseDto, +} from 'src/infra/api/access-service/test-element' +import { CrudTable } from 'src/components/table' +import { SideAction } from 'src/components/ui/SideAction' +import { TestElementNormalRuleDtoWithId, normalRuleColumns } from './columns' + +type NormalRuleEditorProps = { + element: TestElementResponseDto | null + onClose: Function + onSubmit: (normalRules: TestElementNormalRuleDto[]) => void + isSubmitting: boolean +} + +export function NormalRuleEditor(props: NormalRuleEditorProps) { + const [items, setItems] = useState([]) + + useEffect(() => { + if (props.element?.normalRules) { + setItems( + props.element?.normalRules.map((rule) => ({ + id: JSON.stringify(rule), + ...rule, + })), + ) + } + }, [props.element?.normalRules]) + + const handleSubmit = () => { + props.onSubmit( + items.map((rule) => ({ + ...rule, + id: undefined, + defaultChecked: rule.defaultChecked ?? false, + })), + ) + props.onClose() + } + + const handleCancel = () => { + props.onClose() + } + + return ( + + + { + setItems((items) => [ + ...items, + { ...item, id: JSON.stringify(item) }, + ]) + }} + onItemUpdate={(item) => { + setItems((items) => [ + { ...item, id: JSON.stringify(item) }, + ...items.filter((rule) => rule.id !== item.id), + ]) + }} + onItemDelete={(item) => { + setItems((items) => items.filter((rule) => rule.id !== item.id)) + }} + /> + + + + + + + ) +} diff --git a/apps/hcdc-web-app/src/features/test-element/components/TestElementTable/columns.tsx b/apps/hcdc-web-app/src/features/test-element/components/TestElementTable/columns.tsx new file mode 100644 index 00000000..3de7eccf --- /dev/null +++ b/apps/hcdc-web-app/src/features/test-element/components/TestElementTable/columns.tsx @@ -0,0 +1,57 @@ +import { GridColDef } from '@mui/x-data-grid' + +import { TestElementResponseDto } from 'src/infra/api/access-service/test-element' + +export const testElementColumns: GridColDef[] = [ + { + field: 'displayIndex', + headerName: 'Thứ tự nhập', + type: 'number', + minWidth: 70, + sortable: false, + editable: true, + }, + { + field: 'name', + headerName: 'Tên thành phần', + minWidth: 200, + flex: 1, + sortable: false, + editable: true, + }, + { + field: 'printIndex', + headerName: 'Thứ tự In', + type: 'number', + minWidth: 70, + sortable: false, + editable: true, + }, + { + field: 'reportIndex', + headerName: 'Thứ tự Sổ', + type: 'number', + minWidth: 70, + sortable: false, + editable: true, + }, + { + field: 'isParent', + type: 'boolean', + headerName: 'TP lớn', + minWidth: 80, + flex: 1, + sortable: false, + editable: true, + valueGetter: ({ value }) => { + return value ?? false + }, + }, + { + field: 'unit', + headerName: 'Đơn vị', + minWidth: 80, + sortable: false, + editable: true, + }, +] diff --git a/apps/hcdc-web-app/src/features/test-element/components/TestElementTable/index.tsx b/apps/hcdc-web-app/src/features/test-element/components/TestElementTable/index.tsx new file mode 100644 index 00000000..52a10cc0 --- /dev/null +++ b/apps/hcdc-web-app/src/features/test-element/components/TestElementTable/index.tsx @@ -0,0 +1,184 @@ +import { FormControl } from '@mui/material' +import { toast } from 'react-toastify' +import { useEffect, useState } from 'react' +import { useForm } from 'react-hook-form' + +import { + useTestElementCreateMutation, + useTestElementDeleteByIdMutation, + useTestElementSearchQuery, + useTestElementUpdateByIdMutation, + useLazyTestElementSearchQuery, + TestElementResponseDto, +} from 'src/infra/api/access-service/test-element' +import { CrudTable } from 'src/components/table' +import { useCrudPagination } from 'src/shared/hooks' +import { testElementColumns } from './columns' +import { TestResponseDto } from 'src/infra/api/access-service/test' +import { NormalRuleEditor } from '../NormalRuleEditor' +import { useTypedSelector } from 'src/infra/redux' +import { authSlice } from 'src/features/auth' +import { FormAutocomplete, FormContainer } from 'src/components/form' + +type TestElementTableProps = { + page: number + pageSize: number + setPage: (page: number) => void + setPageSize: (pageSize: number) => void + tests: TestResponseDto[] + testId: string + setTestId: (id: string) => void +} + +type FormData = { + testId: string +} + +export function TestElementTable(props: TestElementTableProps) { + const branchId = useTypedSelector(authSlice.selectors.selectActiveBranchId)! + const { filterObj, setFilterObj, onPageChange, onPageSizeChange } = + useCrudPagination( + { + offset: props.page, + limit: props.pageSize, + sort: { displayIndex: 1 }, + filter: { branchId, testId: props.testId }, + }, + props.setPage, + props.setPageSize, + ) + + const [ruleRow, setRuleRow] = useState(null) + + const { control, watch, setValue } = useForm({ + defaultValues: { + testId: props.testId, + }, + }) + const selectedTestId = watch('testId') + + useEffect(() => { + if (props.testId) { + setValue('testId', props.testId) + props.setPage(0) + setFilterObj((prev) => ({ + ...prev, + offset: 0, + filter: { ...prev.filter, testId: props.testId }, + })) + } + }, [props.testId]) + + useEffect(() => { + if (selectedTestId) { + props.setTestId(selectedTestId) + } + }, [selectedTestId]) + + const { data, isFetching } = useTestElementSearchQuery(filterObj) + const [searchTestElements] = useLazyTestElementSearchQuery() + + const [createTestElement, { isLoading: isCreating }] = + useTestElementCreateMutation() + const [updateTestElement, { isLoading: isUpdating }] = + useTestElementUpdateByIdMutation() + const [deleteTestElement, { isLoading: isDeleting }] = + useTestElementDeleteByIdMutation() + + return ( + <> + { + await createTestElement({ + name: item.name, + displayIndex: item.displayIndex, + printIndex: item.printIndex, + reportIndex: item.reportIndex, + isParent: item.isParent, + unit: item.unit, + normalRules: [], + testId: props.testId, + branchId, + }).unwrap() + }} + onItemUpdate={async (newItem) => { + await updateTestElement({ + id: newItem._id, + testElementUpdateRequestDto: { + name: newItem.name, + displayIndex: newItem.displayIndex, + printIndex: newItem.printIndex, + reportIndex: newItem.reportIndex, + isParent: newItem.isParent, + unit: newItem.unit, + }, + }).unwrap() + }} + onItemDelete={async (item) => { + await deleteTestElement(item._id).unwrap() + }} + onRefresh={async () => { + await searchTestElements(filterObj).unwrap() + }} + TopLeftComponent={ + + { + e.preventDefault() + }} + > + option.name} + getOptionValue={(option) => option._id} + groupBy={(option) => option.testCategory?.name ?? ''} + /> + + + } + customRowActions={[ + { + label: 'Tham chiếu', + action: (testElement) => { + setRuleRow(testElement) + }, + }, + ]} + /> + { + setRuleRow(null) + }} + onSubmit={(normalRules) => { + updateTestElement({ + id: ruleRow?._id!, + testElementUpdateRequestDto: { normalRules }, + }).then(() => { + toast.success('Đặt tham chiếu thành công.') + }) + }} + isSubmitting={isUpdating} + /> + + ) +} diff --git a/apps/hcdc-web-app/src/features/test-element/components/index.ts b/apps/hcdc-web-app/src/features/test-element/components/index.ts new file mode 100644 index 00000000..6d89f218 --- /dev/null +++ b/apps/hcdc-web-app/src/features/test-element/components/index.ts @@ -0,0 +1 @@ +export * from './TestElementTable' diff --git a/apps/hcdc-web-app/src/features/test-element/index.ts b/apps/hcdc-web-app/src/features/test-element/index.ts new file mode 100644 index 00000000..cb64ac1b --- /dev/null +++ b/apps/hcdc-web-app/src/features/test-element/index.ts @@ -0,0 +1 @@ +export * from './components' diff --git a/apps/hcdc-web-app/src/features/test-element/pages/ManageTestElementPage/components/TestElementTable/HighlightRuleEditor.tsx b/apps/hcdc-web-app/src/features/test-element/pages/ManageTestElementPage/components/TestElementTable/HighlightRuleEditor.tsx deleted file mode 100644 index eb24de07..00000000 --- a/apps/hcdc-web-app/src/features/test-element/pages/ManageTestElementPage/components/TestElementTable/HighlightRuleEditor.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import * as React from 'react' -import { PatientCategory } from '@diut/hcdc' -import { Box, Button } from '@mui/material' -import { GridColDef } from '@mui/x-data-grid' - -import { - HighlightRuleDto, - TestElementResponseDto, -} from 'src/infra/api/access-service/test-element' -import { CrudTable } from 'src/components/table' -import { SideAction } from 'src/components/ui/SideAction' - -const patientCategories = { - [PatientCategory.Any]: 'Tất cả', - [PatientCategory.Boy]: 'Bé trai', - [PatientCategory.Girl]: 'Bé gái', - [PatientCategory.Man]: 'Nam', - [PatientCategory.Woman]: 'Nữ', - [PatientCategory.Pregnant]: 'Thai phụ', -} - -type HighlightRuleDtoWithId = HighlightRuleDto & { - id: string -} - -const columns: GridColDef[] = [ - { - field: 'category', - headerName: 'Phân loại', - type: 'singleSelect', - minWidth: 150, - sortable: false, - editable: true, - valueOptions: Object.keys(patientCategories).map((category) => ({ - value: category, - label: patientCategories[category as PatientCategory], - })), - valueFormatter: ({ value }) => patientCategories[value as PatientCategory], - }, - { - field: 'min', - headerName: 'Min', - type: 'number', - width: 100, - sortable: false, - editable: true, - }, - { - field: 'max', - headerName: 'Max', - type: 'number', - width: 100, - sortable: false, - editable: true, - }, - { - field: 'normalValue', - headerName: 'So sánh', - minWidth: 100, - flex: 1, - sortable: false, - editable: true, - }, - { - field: 'description', - headerName: 'Mô tả', - minWidth: 100, - flex: 1, - sortable: false, - editable: true, - }, - { - field: 'note', - headerName: 'Tham khảo', - minWidth: 100, - flex: 1, - sortable: false, - editable: true, - }, - { - field: 'defaultChecked', - type: 'boolean', - headerName: 'Mặc định', - minWidth: 80, - sortable: false, - editable: true, - valueGetter: ({ value }) => { - return value ?? false - }, - }, -] - -interface HighlightRuleEditorProps { - element: TestElementResponseDto - onClose: Function - onSubmit: (highlightRules: HighlightRuleDto[]) => void - isSubmitting: boolean -} - -export function HighlightRuleEditor({ - element, - onClose, - onSubmit, - isSubmitting, -}: HighlightRuleEditorProps) { - const [items, setItems] = React.useState([]) - - React.useEffect(() => { - if (element?.highlightRules != undefined) { - setItems( - element.highlightRules.map((rule) => ({ - id: JSON.stringify(rule), - ...rule, - })), - ) - } - }, [element?.highlightRules]) - - const handleSubmit = () => { - onSubmit( - items.map((rule) => ({ - ...rule, - id: undefined, - defaultChecked: rule.defaultChecked ?? false, - })), - ) - onClose() - } - - const handleCancel = () => { - onClose() - } - - return ( - - - { - setItems((items) => [ - ...items, - { ...item, id: JSON.stringify(item) }, - ]) - }} - onItemUpdate={(item) => { - setItems((items) => [ - { ...item, id: JSON.stringify(item) }, - ...items.filter((rule) => rule.id !== item.id), - ]) - }} - onItemDelete={(item) => { - setItems((items) => items.filter((rule) => rule.id !== item.id)) - }} - /> - - - - - - - ) -} diff --git a/apps/hcdc-web-app/src/features/test-element/pages/ManageTestElementPage/components/TestElementTable/columns.tsx b/apps/hcdc-web-app/src/features/test-element/pages/ManageTestElementPage/components/TestElementTable/columns.tsx deleted file mode 100644 index 42d70dc2..00000000 --- a/apps/hcdc-web-app/src/features/test-element/pages/ManageTestElementPage/components/TestElementTable/columns.tsx +++ /dev/null @@ -1,203 +0,0 @@ -import { PatientCategory } from '@diut/hcdc' -import { GridColDef } from '@mui/x-data-grid' - -import { TestResponseDto } from 'src/infra/api/access-service/test' -import { TestElementResponseDto } from 'src/infra/api/access-service/test-element' - -export const NO_MIN = -1 -export const NO_MAX = -1 -export const NO_NORMAL_VALUE = '---' -export const NO_DESCRIPTION = '---' -export const NO_NOTE = '---' - -export function useTestElementColumns( - test: TestResponseDto[], -): GridColDef[] { - return [ - { - field: 'test', - headerName: 'Tên XN', - type: 'singleSelect', - minWidth: 250, - sortable: false, - editable: true, - valueOptions: test?.map((item) => ({ - value: item?.name, - label: item?.name, - })), - valueGetter: ({ value }) => { - return value?.name ?? '' - }, - }, - { - field: 'displayIndex', - headerName: 'Thứ tự nhập', - type: 'number', - minWidth: 70, - sortable: false, - editable: true, - }, - { - field: 'printIndex', - headerName: 'Thứ tự In', - type: 'number', - minWidth: 70, - sortable: false, - editable: true, - }, - { - field: 'reportOrder', - headerName: 'Thứ tự Sổ', - type: 'number', - minWidth: 70, - sortable: false, - editable: true, - }, - { - field: 'name', - headerName: 'Tên thành phần', - minWidth: 200, - flex: 1, - sortable: false, - editable: true, - }, - { - field: 'isParent', - type: 'boolean', - headerName: 'TP lớn', - minWidth: 80, - flex: 1, - sortable: false, - editable: true, - valueGetter: ({ value }) => { - return value ?? false - }, - }, - { - field: 'unit', - headerName: 'Đơn vị', - minWidth: 80, - sortable: false, - editable: true, - }, - { - field: 'anyMin', - headerName: 'Min', - type: 'number', - minWidth: 80, - sortable: false, - editable: true, - valueGetter: ({ row }) => { - const rule = row.highlightRules?.[0] ?? {} - if (rule.category !== PatientCategory.Any) { - return NO_MIN - } - - return rule.min - }, - valueFormatter: ({ value }) => { - if (value === NO_MIN) { - return '' - } - }, - }, - { - field: 'anyMax', - headerName: 'Max', - type: 'number', - minWidth: 80, - sortable: false, - editable: true, - valueGetter: ({ row }) => { - const rule = row.highlightRules?.[0] ?? {} - if (rule.category !== PatientCategory.Any) { - return NO_MAX - } - - return rule.max - }, - valueFormatter: ({ value }) => { - if (value === NO_MAX) { - return '' - } - }, - }, - { - field: 'anyNormal', - headerName: 'So sánh', - minWidth: 80, - sortable: false, - editable: true, - valueGetter: ({ row }) => { - const rule = row.highlightRules?.[0] ?? {} - - if (rule.category !== PatientCategory.Any) { - return NO_NORMAL_VALUE - } - - return rule.normalValue - }, - valueFormatter: ({ value }) => { - if (value === NO_NORMAL_VALUE) { - return '' - } - }, - }, - { - field: 'anyDescription', - headerName: 'Mô tả', - minWidth: 100, - sortable: false, - editable: true, - valueGetter: ({ row }) => { - const rule = row.highlightRules?.[0] ?? {} - if (rule.category !== PatientCategory.Any) { - return NO_DESCRIPTION - } - - return rule.description - }, - valueFormatter: ({ value }) => { - if (value === NO_DESCRIPTION) { - return '' - } - }, - }, - { - field: 'anyNote', - headerName: 'Tham khảo', - minWidth: 100, - sortable: false, - editable: true, - valueGetter: ({ row }) => { - const rule = row.highlightRules?.[0] ?? {} - if (rule.category !== PatientCategory.Any) { - return NO_NOTE - } - - return rule.note - }, - valueFormatter: ({ value }) => { - if (value === NO_NOTE) { - return '' - } - }, - }, - { - field: 'anyDefaultChecked', - type: 'boolean', - headerName: 'Mặc định', - width: 80, - sortable: false, - editable: true, - valueGetter: ({ row }) => { - const rule = row.highlightRules?.[0] ?? {} - if (rule.category !== PatientCategory.Any) { - return false - } - - return rule.defaultChecked ?? false - }, - }, - ] -} diff --git a/apps/hcdc-web-app/src/features/test-element/pages/ManageTestElementPage/components/TestElementTable/index.tsx b/apps/hcdc-web-app/src/features/test-element/pages/ManageTestElementPage/components/TestElementTable/index.tsx deleted file mode 100644 index 89e3d4e7..00000000 --- a/apps/hcdc-web-app/src/features/test-element/pages/ManageTestElementPage/components/TestElementTable/index.tsx +++ /dev/null @@ -1,294 +0,0 @@ -import * as React from 'react' -import { - Box, - FormControl, - InputLabel, - MenuItem, - Select, - Skeleton, -} from '@mui/material' -import { toast } from 'react-toastify' -import { PatientCategory } from '@diut/hcdc' -import { useLoaderData } from 'react-router-dom' - -import { - useTestElementCreateMutation, - useTestElementDeleteByIdMutation, - useTestElementSearchQuery, - useTestElementUpdateByIdMutation, - useLazyTestElementSearchQuery, - TestElementResponseDto, - HighlightRuleDto, -} from 'src/infra/api/access-service/test-element' -import { CrudTable } from 'src/components/table' -import { useCrudPagination } from 'src/shared/hooks' -import { - NO_MIN, - NO_MAX, - NO_DESCRIPTION, - NO_NORMAL_VALUE, - useTestElementColumns, - NO_NOTE, -} from './columns' -import { useLazyTestSearchQuery } from 'src/infra/api/access-service/test' -import { HighlightRuleEditor } from './HighlightRuleEditor' -import { manageTestElemenentPageLoader } from '../../loader' - -const ALL_CATEGORIES = 'ALL_CATEGORIES' -const ALL_TESTS = 'ALL_TESTS' - -export function TestElementTable() { - const { testCategories, testRes } = useLoaderData() as Awaited< - ReturnType - > - - const [ruleRow, setRuleRow] = React.useState( - null, - ) - - const [selectedCategoryId, setSelectedCategoryId] = - React.useState(ALL_CATEGORIES) - const [selectedTestId, setSelectedTestId] = React.useState(ALL_TESTS) - - const [searchTest, { data: testLazyRes, isFetching: isLoadingLazyTest }] = - useLazyTestSearchQuery() - - React.useEffect(() => { - if (selectedTestId !== ALL_TESTS) { - setFilterObj((filterObj) => ({ - ...filterObj, - offset: 0, - filter: { - ...filterObj.filter, - test: selectedTestId, - }, - })) - } else { - setFilterObj((filterObj) => ({ - ...filterObj, - offset: 0, - filter: { - ...filterObj.filter, - test: undefined, - }, - })) - } - }, [selectedTestId]) - - React.useEffect(() => { - if (selectedCategoryId === ALL_CATEGORIES) { - searchTest({ - searchTestRequestDto: { - sort: { index: 1 }, - }, - }) - } else { - searchTest({ - searchTestRequestDto: { - sort: { index: 1 }, - filter: { category: selectedCategoryId }, - }, - }) - } - }, [selectedCategoryId]) - - const tests = testLazyRes?.items ?? testRes?.items ?? [] - const columns = useTestElementColumns(tests) - - const { filterObj, setFilterObj, onPageChange, onPageSizeChange } = - useCrudPagination({ - sort: { index: 1 }, - offset: 0, - }) - - const { data, isFetching } = useTestElementSearchQuery(filterObj) - const [searchTestElements] = useLazyTestElementSearchQuery() - - const [createTestElement, { isLoading: isCreating }] = - useTestElementCreateMutation() - const [updateTestElement, { isLoading: isUpdating }] = - useTestElementUpdateByIdMutation() - const [deleteTestElement, { isLoading: isDeleting }] = - useTestElementDeleteByIdMutation() - - return data?.items != undefined ? ( - <> - { - await createTestElement({ - createTestElementRequestDto: { - name: item.name, - displayIndex: item.displayIndex, - printIndex: item.printIndex, - reportOrder: item.reportOrder, - test: tests.find((test) => test.name === (item.test as any)) - ?._id!, - isParent: item.isParent, - highlightRules: processHighlightRules(item), - unit: item.unit, - }, - }).unwrap() - }} - onItemUpdate={async (newItem, oldItem) => { - await updateTestElement({ - id: newItem._id, - updateTestElementRequestDto: { - name: newItem.name, - displayIndex: newItem.displayIndex, - printIndex: newItem.printIndex, - reportOrder: newItem.reportOrder, - test: tests.find((test) => test.name === (newItem.test as any)) - ?._id, - isParent: newItem.isParent, - highlightRules: processHighlightRules(newItem), - unit: newItem.unit, - }, - }).unwrap() - }} - onItemDelete={async (item) => { - await deleteTestElement({ - id: item._id, - }).unwrap() - }} - onRefresh={async () => { - await searchTestElements({ - searchTestElementRequestDto: filterObj, - }).unwrap() - }} - TopRightComponent={ - - - Nhóm xét nghiệm - - - - Tên xét nghiệm - - - - } - customRowActions={[ - { - label: 'Tham chiếu', - action: (testElement) => { - setRuleRow(testElement) - }, - }, - ]} - /> - {ruleRow != null && ( - { - setRuleRow(null) - }} - onSubmit={(highlightRules) => { - updateTestElement({ - id: ruleRow?._id!, - updateTestElementRequestDto: { highlightRules }, - }).then(() => { - toast.success('Đặt tham chiếu thành công.') - }) - }} - isSubmitting={isUpdating} - /> - )} - - ) : ( - - ) -} - -function processHighlightRules(item: any): HighlightRuleDto[] { - const firstRule = item.highlightRules?.[0] - - if ( - firstRule?.category != undefined && - firstRule?.category !== PatientCategory.Any - ) { - return item.highlightRules.map((rule: HighlightRuleDto) => ({ - ...rule, - defaultChecked: rule.defaultChecked ?? false, - })) - } - - const { - anyMin, - anyMax, - anyNormal, - anyDescription, - anyNote, - anyDefaultChecked, - } = item - const result: HighlightRuleDto = { - category: PatientCategory.Any, - defaultChecked: anyDefaultChecked ?? false, - } - - if (anyMin !== NO_MIN) { - result.min = anyMin - } - - if (anyMax !== NO_MAX) { - result.max = anyMax - } - - if (anyNormal !== NO_NORMAL_VALUE) { - result.normalValue = anyNormal - } - - if (anyDescription !== NO_DESCRIPTION) { - result.description = anyDescription - } - - if (anyNote !== NO_NOTE) { - result.note = anyNote - } - - return [result] -} diff --git a/apps/hcdc-web-app/src/features/test-element/pages/ManageTestElementPage/index.tsx b/apps/hcdc-web-app/src/features/test-element/pages/ManageTestElementPage/index.tsx index a955bdd4..1bfbc68a 100644 --- a/apps/hcdc-web-app/src/features/test-element/pages/ManageTestElementPage/index.tsx +++ b/apps/hcdc-web-app/src/features/test-element/pages/ManageTestElementPage/index.tsx @@ -1,9 +1,83 @@ -import { TestElementTable } from './components/TestElementTable' +import { useCallback, useEffect, useState } from 'react' +import { useLoaderData, useSearchParams } from 'react-router-dom' + +import { TestElementTable } from '../../components' +import { manageTestElemenentPageLoader } from './loader' +import { ROWS_PER_PAGE_OPTIONS } from 'src/shared' + +const PARAM_PAGE = 'page' +const PARAM_PAGE_SIZE = 'pageSize' +const PARAM_TEST_ID = 'testId' export default function ManageTestElementPage() { + const { tests } = useLoaderData() as Awaited< + ReturnType + > + const [searchParams, setSearchParams] = useSearchParams({ + [PARAM_PAGE]: '0', + [PARAM_PAGE_SIZE]: ROWS_PER_PAGE_OPTIONS[0].toString(), + [PARAM_TEST_ID]: tests[0]?._id, + }) + const paramTestId = searchParams.get(PARAM_TEST_ID)! + const page = parseInt(searchParams.get(PARAM_PAGE)!) + const pageSize = parseInt(searchParams.get(PARAM_PAGE_SIZE)!) + + const [testId, setSelectedTestId] = useState(paramTestId) + + useEffect(() => { + if (paramTestId) { + setSelectedTestId(paramTestId) + } + }, [searchParams]) + + const setTestId = useCallback( + (id: string) => { + setSearchParams( + (searchParams) => { + searchParams.set(PARAM_TEST_ID, id) + return searchParams + }, + { replace: true }, + ) + }, + [setSearchParams], + ) + + const setPage = useCallback( + (newPage: number) => { + setSearchParams( + (searchParams) => { + searchParams.set(PARAM_PAGE, newPage.toString()) + return searchParams + }, + { replace: true }, + ) + }, + [setSearchParams], + ) + + const setPageSize = useCallback( + (newPageSize: number) => { + setSearchParams( + (searchParams) => { + searchParams.set(PARAM_PAGE_SIZE, newPageSize.toString()) + return searchParams + }, + { replace: true }, + ) + }, + [setSearchParams], + ) + return ( - <> - - + ) } 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 47aeab62..6f7e5aee 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,27 +1,29 @@ import { appStore } from 'src/infra/redux' -import { testCategoryApi } from 'src/infra/api/access-service/test-category' import { testApi } from 'src/infra/api/access-service/test' +import { authSlice } from 'src/features/auth' export const manageTestElemenentPageLoader = async () => { - const [categoryRes, testRes] = await Promise.all([ - appStore - .dispatch( - testCategoryApi.endpoints.testCategorySearch.initiate({ - sort: { displayIndex: 1 }, - }), - ) - .unwrap(), + 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(), ]) return { - testCategories: categoryRes?.items ?? [], - testRes, + tests: testRes.items.toSorted( + (a, b) => + (a.testCategory?.displayIndex ?? 0) - + (b.testCategory?.displayIndex ?? 0), + ), } } diff --git a/apps/hcdc-web-app/src/infra/api/access-service/sample.ts b/apps/hcdc-web-app/src/infra/api/access-service/sample.ts index e1cc2f6a..fa4f4eb2 100644 --- a/apps/hcdc-web-app/src/infra/api/access-service/sample.ts +++ b/apps/hcdc-web-app/src/infra/api/access-service/sample.ts @@ -313,7 +313,7 @@ export type SampleCreateRequestDto = { branchId: string testIds: string[] } -export type TestElementNormalRuleDto = { +export type TestElementTestElementNormalRuleDto = { category: | 'Any' | 'YoungMale' @@ -336,7 +336,7 @@ export type TestElementUnpopulatedResponseDto = { reportIndex: number unit: string isParent: boolean - normalRules: TestElementNormalRuleDto[] + normalRules: TestElementTestElementNormalRuleDto[] testId: string branchId: string } diff --git a/apps/hcdc-web-app/src/infra/router/routes.tsx b/apps/hcdc-web-app/src/infra/router/routes.tsx index a2e1c97a..ab92df0a 100644 --- a/apps/hcdc-web-app/src/infra/router/routes.tsx +++ b/apps/hcdc-web-app/src/infra/router/routes.tsx @@ -10,7 +10,7 @@ import { CustomRouteObject } from 'src/infra/router' // 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' +import { manageTestElemenentPageLoader } from 'src/features/test-element/pages/ManageTestElementPage/loader' // import { printSelectPageLoader } from 'src/features/sample-result/pages/PrintSelectPage/loader' // import { testReportPageLoader } from 'src/features/report/pages/TestReportPage/loader' // import { exportReportPageLoader } from 'src/features/report/pages/ExportReportPage/loader' @@ -32,9 +32,9 @@ const ManageTestCategoryPage = React.lazy( const ManageTestPage = React.lazy( () => import('src/features/test/pages/ManageTestPage'), ) -// const ManageTestElementPage = React.lazy( -// () => import('src/features/test-element/pages/ManageTestElementPage'), -// ) +const ManageTestElementPage = React.lazy( + () => import('src/features/test-element/pages/ManageTestElementPage'), +) const ManageDiagnosisPage = React.lazy( () => import('src/features/diagnosis/pages/ManageDiagnosisPage'), ) @@ -119,11 +119,11 @@ export const appRoutes: CustomRouteObject[] = [ element: , loader: manageTestPageLoader, }, - // { - // path: 'test-elements', - // element: , - // loader: manageTestElemenentPageLoader, - // }, + { + path: 'test-elements', + element: , + loader: manageTestElemenentPageLoader, + }, { path: 'sample-types', element: , diff --git a/resources/test-elements/dtos/create-test-element.request-dto.ts b/resources/test-elements/dtos/create-test-element.request-dto.ts index 7413ea8e..ab4ca2bc 100644 --- a/resources/test-elements/dtos/create-test-element.request-dto.ts +++ b/resources/test-elements/dtos/create-test-element.request-dto.ts @@ -59,13 +59,13 @@ export class CreateTestElementRequestDto { isParent: boolean @ApiProperty({ - type: () => HighlightRuleDto, + type: () => TestElementNormalRuleDto, isArray: true, }) @IsArray() @ValidateNested({ each: true }) - @Type(() => HighlightRuleDto) - highlightRules: HighlightRuleDto[] + @Type(() => TestElementNormalRuleDto) + highlightRules: TestElementNormalRuleDto[] @ApiProperty({ example: '10^3/uL', @@ -76,7 +76,7 @@ export class CreateTestElementRequestDto { unit?: string } -export class HighlightRuleDto { +export class TestElementNormalRuleDto { @Expose() @ApiProperty({ example: PatientCategory.Any, diff --git a/resources/test-elements/dtos/test-element.response-dto.ts b/resources/test-elements/dtos/test-element.response-dto.ts index ace0c299..c75dc0a6 100644 --- a/resources/test-elements/dtos/test-element.response-dto.ts +++ b/resources/test-elements/dtos/test-element.response-dto.ts @@ -17,7 +17,7 @@ import { IsObjectId, } from '@diut/nestjs-infra' import { TestResponseDto } from 'src/resources/tests/dtos/test.response-dto' -import { HighlightRuleDto } from './create-test-element.request-dto' +import { TestElementNormalRuleDto } from './create-test-element.request-dto' export class TestElementResponseDto extends BaseResourceResponseDto { @Expose() @@ -68,13 +68,13 @@ export class TestElementResponseDto extends BaseResourceResponseDto { @Expose() @ApiProperty({ - type: () => HighlightRuleDto, + type: () => TestElementNormalRuleDto, isArray: true, }) @IsArray() @ValidateNested({ each: true }) - @Type(() => HighlightRuleDto) - highlightRules: HighlightRuleDto[] + @Type(() => TestElementNormalRuleDto) + highlightRules: TestElementNormalRuleDto[] @Expose() @ApiProperty({