From a58274ff547cc4b519b2cd81281c32fd59eefcfb Mon Sep 17 00:00:00 2001 From: Bodhish Thomas Date: Thu, 2 Jan 2025 18:34:29 +0530 Subject: [PATCH 1/4] Refactor routing and API structure for organization and questionnaire components - Moved questionnaire routes to a new file for better organization. - Updated AppRouter to include the new QuestionnaireRoutes. - Refactored organization API calls to use a new centralized organizationApi module, improving maintainability and clarity. - Removed deprecated organization routes from the API definition. - Updated various components to utilize the new organizationApi for API calls, ensuring consistency across the application. --- src/Routers/AppRouter.tsx | 7 +-- src/Routers/routes/questionnaireRoutes.tsx | 11 ++++ src/Utils/request/api.tsx | 56 +------------------ src/pages/Landing/LandingPage.tsx | 4 +- src/pages/Organization/OrganizationIndex.tsx | 4 +- .../Organization/OrganizationPatients.tsx | 4 +- src/pages/Organization/OrganizationUsers.tsx | 4 +- src/pages/Organization/OrganizationView.tsx | 4 +- .../components/EditUserRoleSheet.tsx | 5 +- .../Organization/components/LinkUserSheet.tsx | 3 +- .../components/OrganizationLayout.tsx | 4 +- .../components/OrganizationSelector.tsx | 16 ++---- src/types/organization/organization.ts | 1 - src/types/organization/organizationApi.ts | 55 ++++++++++++++++++ 14 files changed, 92 insertions(+), 86 deletions(-) create mode 100644 src/Routers/routes/questionnaireRoutes.tsx create mode 100644 src/types/organization/organizationApi.ts diff --git a/src/Routers/AppRouter.tsx b/src/Routers/AppRouter.tsx index c2b59e69871..9387f7d3da9 100644 --- a/src/Routers/AppRouter.tsx +++ b/src/Routers/AppRouter.tsx @@ -23,9 +23,8 @@ import { PlugConfigEdit } from "@/pages/Apps/PlugConfigEdit"; import { PlugConfigList } from "@/pages/Apps/PlugConfigList"; import UserDashboard from "@/pages/UserDashboard"; -import { QuestionnaireList } from "../components/Questionnaire"; -import { QuestionnaireShow } from "../components/Questionnaire/show"; import OrganizationRoutes from "./routes/OrganizationRoutes"; +import QuestionnaireRoutes from "./routes/questionnaireRoutes"; // List of paths where the sidebar should be hidden const PATHS_WITHOUT_SIDEBAR = ["/"]; @@ -55,6 +54,7 @@ const Routes: AppRoutes = { ...ScheduleRoutes, ...UserRoutes, ...OrganizationRoutes, + ...QuestionnaireRoutes, "/session-expired": () => , "/not-found": () => , @@ -63,9 +63,6 @@ const Routes: AppRoutes = { // Only include the icon route in development environment ...(import.meta.env.PROD ? { "/icons": () => } : {}), - // Questionnaire Routes - "/questionnaire": () => , - "/questionnaire/:id": ({ id }) => , "/apps": () => , "/apps/plug-configs/:slug": ({ slug }) => , "/login": () => , diff --git a/src/Routers/routes/questionnaireRoutes.tsx b/src/Routers/routes/questionnaireRoutes.tsx new file mode 100644 index 00000000000..69d3a114d5a --- /dev/null +++ b/src/Routers/routes/questionnaireRoutes.tsx @@ -0,0 +1,11 @@ +import { QuestionnaireList } from "@/components/Questionnaire"; +import { QuestionnaireShow } from "@/components/Questionnaire/show"; + +import { AppRoutes } from "../AppRouter"; + +const QuestionnaireRoutes: AppRoutes = { + "/questionnaire": () => , + "/questionnaire/:id": ({ id }) => , +}; + +export default QuestionnaireRoutes; diff --git a/src/Utils/request/api.tsx b/src/Utils/request/api.tsx index 25a11d135fc..4b96be5cba7 100644 --- a/src/Utils/request/api.tsx +++ b/src/Utils/request/api.tsx @@ -49,10 +49,7 @@ import { FacilityOrganizationResponse, } from "@/types/facilityOrganization/facilityOrganization"; import { - Organization, - OrganizationResponse, OrganizationUserRole, - OrganizationUserRoleResponse, RoleResponse, } from "@/types/organization/organization"; import { PlugConfig } from "@/types/plugConfig"; @@ -745,57 +742,6 @@ const routes = { TRes: Type>(), }, - // Organization Routes - organization: { - listMine: { - path: "/api/v1/organization/mine/", - method: "GET", - TRes: {} as OrganizationResponse, - }, - list: { - path: "/api/v1/organization/", - method: "GET", - TRes: {} as OrganizationResponse, - }, - get: { - path: "/api/v1/organization/{id}/", - method: "GET", - TRes: {} as Organization, - }, - listUsers: { - path: "/api/v1/organization/{id}/users/", - method: "GET", - TRes: {} as OrganizationUserRoleResponse, - }, - assignUser: { - path: "/api/v1/organization/{id}/users/", - method: "POST", - TRes: {} as OrganizationUserRole, - TBody: {} as { user: string; role: string }, - }, - updateUserRole: { - path: "/api/v1/organization/{id}/users/{userRoleId}/", - method: "PUT", - TRes: {} as OrganizationUserRole, - TBody: {} as { user: string; role: string }, - }, - removeUserRole: { - path: "/api/v1/organization/{id}/users/{userRoleId}/", - method: "DELETE", - TRes: {} as Record, - }, - listPatients: { - path: "/api/v1/patient/", - method: "GET", - TRes: Type>(), - }, - getPublicOrganizations: { - path: "/api/v1/govt/organization/", - method: "GET", - TRes: Type>(), - }, - }, - facilityOrganization: { list: { path: "/api/v1/facility/{facilityId}/organizations/", @@ -816,7 +762,7 @@ const routes = { listUsers: { path: "/api/v1/facility/{facilityId}/organizations/{organizationId}/users/", method: "GET", - TRes: {} as OrganizationUserRoleResponse, + TRes: {} as PaginatedResponse, }, assignUser: { path: "/api/v1/facility/{facilityId}/organizations/{organizationId}/users/", diff --git a/src/pages/Landing/LandingPage.tsx b/src/pages/Landing/LandingPage.tsx index 50b67f0065c..190d7e2f05f 100644 --- a/src/pages/Landing/LandingPage.tsx +++ b/src/pages/Landing/LandingPage.tsx @@ -13,10 +13,10 @@ import { CommandItem, } from "@/components/ui/command"; -import routes from "@/Utils/request/api"; import query from "@/Utils/request/query"; import { PaginatedResponse } from "@/Utils/request/types"; import { Organization } from "@/types/organization/organization"; +import organizationApi from "@/types/organization/organizationApi"; const { customLogo, stateLogo, mainLogo, keralaGeoId } = careConfig; @@ -32,7 +32,7 @@ export function LandingPage() { const { data: districtsResponse } = useQuery>( { queryKey: ["districts", STATE_GEO_ID], - queryFn: query(routes.organization.getPublicOrganizations, { + queryFn: query(organizationApi.getPublicOrganizations, { queryParams: { parent: STATE_GEO_ID }, }), }, diff --git a/src/pages/Organization/OrganizationIndex.tsx b/src/pages/Organization/OrganizationIndex.tsx index 4c800436426..ddd3abbab0d 100644 --- a/src/pages/Organization/OrganizationIndex.tsx +++ b/src/pages/Organization/OrganizationIndex.tsx @@ -16,15 +16,15 @@ import { Skeleton } from "@/components/ui/skeleton"; import Page from "@/components/Common/Page"; -import routes from "@/Utils/request/api"; import query from "@/Utils/request/query"; import type { Organization } from "@/types/organization/organization"; import { getOrgLevel } from "@/types/organization/organization"; +import organizationApi from "@/types/organization/organizationApi"; export default function OrganizationIndex() { const { data, isLoading } = useQuery({ queryKey: ["organization", "mine"], - queryFn: query(routes.organization.listMine), + queryFn: query(organizationApi.listMine), }); if (isLoading) { diff --git a/src/pages/Organization/OrganizationPatients.tsx b/src/pages/Organization/OrganizationPatients.tsx index 6516df3580d..05dcdd15901 100644 --- a/src/pages/Organization/OrganizationPatients.tsx +++ b/src/pages/Organization/OrganizationPatients.tsx @@ -12,9 +12,9 @@ import SearchByMultipleFields from "@/components/Common/SearchByMultipleFields"; import useFilters from "@/hooks/useFilters"; -import routes from "@/Utils/request/api"; import query from "@/Utils/request/query"; import { Patient } from "@/types/emr/newPatient"; +import organizationApi from "@/types/organization/organizationApi"; import OrganizationLayout from "./components/OrganizationLayout"; @@ -29,7 +29,7 @@ export default function OrganizationPatients({ id, navOrganizationId }: Props) { const { data: patients, isLoading } = useQuery({ queryKey: ["organizationPatients", id, qParams], - queryFn: query(routes.organization.listPatients, { + queryFn: query(organizationApi.listPatients, { pathParams: { id }, queryParams: { geo_organization: id, diff --git a/src/pages/Organization/OrganizationUsers.tsx b/src/pages/Organization/OrganizationUsers.tsx index a8ec551c458..c1fae8281fe 100644 --- a/src/pages/Organization/OrganizationUsers.tsx +++ b/src/pages/Organization/OrganizationUsers.tsx @@ -9,9 +9,9 @@ import { Card, CardContent } from "@/components/ui/card"; import { Avatar } from "@/components/Common/Avatar"; -import routes from "@/Utils/request/api"; import query from "@/Utils/request/query"; import { OrganizationUserRole } from "@/types/organization/organization"; +import organizationApi from "@/types/organization/organizationApi"; import AddUserSheet from "./components/AddUserSheet"; import EditUserRoleSheet from "./components/EditUserRoleSheet"; @@ -34,7 +34,7 @@ export default function OrganizationUsers({ id, navOrganizationId }: Props) { const { data: users, isLoading: isLoadingUsers } = useQuery({ queryKey: ["organizationUsers", id], - queryFn: query(routes.organization.listUsers, { + queryFn: query(organizationApi.listUsers, { pathParams: { id }, }), enabled: !!id, diff --git a/src/pages/Organization/OrganizationView.tsx b/src/pages/Organization/OrganizationView.tsx index 6fc27d1e288..92a0b741eca 100644 --- a/src/pages/Organization/OrganizationView.tsx +++ b/src/pages/Organization/OrganizationView.tsx @@ -11,9 +11,9 @@ import { Input } from "@/components/ui/input"; import Pagination from "@/components/Common/Pagination"; -import routes from "@/Utils/request/api"; import query from "@/Utils/request/query"; import { Organization, getOrgLevel } from "@/types/organization/organization"; +import organizationApi from "@/types/organization/organizationApi"; import OrganizationLayout from "./components/OrganizationLayout"; @@ -29,7 +29,7 @@ export default function OrganizationView({ id, navOrganizationId }: Props) { const { data: children, isLoading } = useQuery({ queryKey: ["organization", id, "children", page, limit, searchQuery], - queryFn: query(routes.organization.list, { + queryFn: query(organizationApi.list, { queryParams: { parent: id, offset: (page - 1) * limit, diff --git a/src/pages/Organization/components/EditUserRoleSheet.tsx b/src/pages/Organization/components/EditUserRoleSheet.tsx index 712d61cbc02..a5f8cc489da 100644 --- a/src/pages/Organization/components/EditUserRoleSheet.tsx +++ b/src/pages/Organization/components/EditUserRoleSheet.tsx @@ -36,6 +36,7 @@ import routes from "@/Utils/request/api"; import mutate from "@/Utils/request/mutate"; import query from "@/Utils/request/query"; import { OrganizationUserRole } from "@/types/organization/organization"; +import organizationApi from "@/types/organization/organizationApi"; interface Props { organizationId: string; @@ -60,7 +61,7 @@ export default function EditUserRoleSheet({ const { mutate: updateRole } = useMutation({ mutationFn: (body: { user: string; role: string }) => - mutate(routes.organization.updateUserRole, { + mutate(organizationApi.updateUserRole, { pathParams: { id: organizationId, userRoleId: userRole.id }, body, })(body), @@ -81,7 +82,7 @@ export default function EditUserRoleSheet({ const { mutate: removeRole } = useMutation({ mutationFn: () => - mutate(routes.organization.removeUserRole, { + mutate(organizationApi.removeUserRole, { pathParams: { id: organizationId, userRoleId: userRole.id }, })({}), onSuccess: () => { diff --git a/src/pages/Organization/components/LinkUserSheet.tsx b/src/pages/Organization/components/LinkUserSheet.tsx index 32cd87d2053..ef31f2d475f 100644 --- a/src/pages/Organization/components/LinkUserSheet.tsx +++ b/src/pages/Organization/components/LinkUserSheet.tsx @@ -27,6 +27,7 @@ import UserSelector from "@/components/Common/UserSelector"; import routes from "@/Utils/request/api"; import mutate from "@/Utils/request/mutate"; import query from "@/Utils/request/query"; +import organizationApi from "@/types/organization/organizationApi"; import { UserBase } from "@/types/user/user"; import UserApi from "@/types/user/userApi"; @@ -69,7 +70,7 @@ export default function LinkUserSheet({ const { mutate: assignUser } = useMutation({ mutationFn: (body: { user: string; role: string }) => - mutate(routes.organization.assignUser, { + mutate(organizationApi.assignUser, { pathParams: { id: organizationId }, body, })(body), diff --git a/src/pages/Organization/components/OrganizationLayout.tsx b/src/pages/Organization/components/OrganizationLayout.tsx index af7e5dc52ee..b28e50884cc 100644 --- a/src/pages/Organization/components/OrganizationLayout.tsx +++ b/src/pages/Organization/components/OrganizationLayout.tsx @@ -14,13 +14,13 @@ import { Menubar, MenubarMenu, MenubarTrigger } from "@/components/ui/menubar"; import Page from "@/components/Common/Page"; -import routes from "@/Utils/request/api"; import query from "@/Utils/request/query"; import { Organization, OrganizationParent, getOrgLevel, } from "@/types/organization/organization"; +import organizationApi from "@/types/organization/organizationApi"; interface Props { // NavOrganizationId is used to show the organization switcher in the sidebar, it may not the parent organization @@ -70,7 +70,7 @@ export default function OrganizationLayout({ const { data: org, isLoading } = useQuery({ queryKey: ["organization", id], - queryFn: query(routes.organization.get, { + queryFn: query(organizationApi.get, { pathParams: { id }, }), }); diff --git a/src/pages/Organization/components/OrganizationSelector.tsx b/src/pages/Organization/components/OrganizationSelector.tsx index a16d754dd14..14ea412cc65 100644 --- a/src/pages/Organization/components/OrganizationSelector.tsx +++ b/src/pages/Organization/components/OrganizationSelector.tsx @@ -11,13 +11,9 @@ import useDebouncedState from "@/hooks/useDebouncedState"; import { ORGANIZATION_LEVELS } from "@/common/constants"; -import routes from "@/Utils/request/api"; import query from "@/Utils/request/query"; -import { - Organization, - OrganizationResponse, - getOrgLevel, -} from "@/types/organization/organization"; +import { Organization, getOrgLevel } from "@/types/organization/organization"; +import organizationApi from "@/types/organization/organizationApi"; interface OrganizationSelectorProps { value?: string; @@ -44,9 +40,9 @@ export default function OrganizationSelector(props: OrganizationSelectorProps) { } : {}; - const { data: getAllOrganizations } = useQuery({ + const { data: getAllOrganizations } = useQuery({ queryKey: ["organizations-root", searchQuery], - queryFn: query(routes.organization.list, { + queryFn: query(organizationApi.list, { queryParams: { org_type: "govt", parent: "", @@ -64,7 +60,7 @@ export default function OrganizationSelector(props: OrganizationSelectorProps) { selectedLevels[selectedLevels.length - 1]?.id, searchQuery, ], - queryFn: query(routes.organization.list, { + queryFn: query(organizationApi.list, { queryParams: { parent: selectedLevels[selectedLevels.length - 1]?.id, org_type: "govt", @@ -81,7 +77,7 @@ export default function OrganizationSelector(props: OrganizationSelectorProps) { ? getAllOrganizations?.results : currentLevelOrganizations?.results; - const selectedOrg = orgList?.find((org) => org.id === value); + const selectedOrg = orgList?.find((org: Organization) => org.id === value); if (!selectedOrg) return; const newLevels = selectedLevels.slice(0, level); diff --git a/src/types/organization/organization.ts b/src/types/organization/organization.ts index 04b3de3bc9f..246088f8d9c 100644 --- a/src/types/organization/organization.ts +++ b/src/types/organization/organization.ts @@ -45,7 +45,6 @@ export interface Role { updated_at: string; } -export type OrganizationResponse = PaginatedResponse; export type OrganizationUserRoleResponse = PaginatedResponse; export type RoleResponse = PaginatedResponse; diff --git a/src/types/organization/organizationApi.ts b/src/types/organization/organizationApi.ts new file mode 100644 index 00000000000..745cc7c7a6a --- /dev/null +++ b/src/types/organization/organizationApi.ts @@ -0,0 +1,55 @@ +import { HttpMethod, Type } from "@/Utils/request/api"; +import { PaginatedResponse } from "@/Utils/request/types"; + +import { Patient } from "../emr/newPatient"; +import { Organization, OrganizationUserRole } from "./organization"; + +export default { + listMine: { + path: "/api/v1/organization/mine/", + method: HttpMethod.GET, + TRes: {} as PaginatedResponse, + }, + list: { + path: "/api/v1/organization/", + method: HttpMethod.GET, + TRes: {} as PaginatedResponse, + }, + get: { + path: "/api/v1/organization/{id}/", + method: HttpMethod.GET, + TRes: {} as Organization, + }, + listUsers: { + path: "/api/v1/organization/{id}/users/", + method: HttpMethod.GET, + TRes: {} as PaginatedResponse, + }, + assignUser: { + path: "/api/v1/organization/{id}/users/", + method: HttpMethod.POST, + TRes: {} as OrganizationUserRole, + TBody: {} as { user: string; role: string }, + }, + updateUserRole: { + path: "/api/v1/organization/{id}/users/{userRoleId}/", + method: HttpMethod.PUT, + TRes: {} as OrganizationUserRole, + TBody: {} as { user: string; role: string }, + }, + removeUserRole: { + path: "/api/v1/organization/{id}/users/{userRoleId}/", + method: HttpMethod.DELETE, + TRes: {} as Record, + }, + listPatients: { + path: "/api/v1/patient/", + method: HttpMethod.GET, + TRes: Type>(), + }, + getPublicOrganizations: { + path: "/api/v1/govt/organization/", + method: HttpMethod.GET, + TRes: Type>(), + }, +}; From edca4efb799d84a9a5eb892e6335fe6f2f4ad6f7 Mon Sep 17 00:00:00 2001 From: Bodhish Thomas Date: Thu, 2 Jan 2025 18:42:25 +0530 Subject: [PATCH 2/4] Refactor questionnaire API routes and update components to use new structure - Moved questionnaire routes from the api.tsx file to a new questionnaireApi module for better organization and maintainability. - Updated components (QuestionnaireList, QuestionnaireForm, QuestionnaireSearch, and QuestionnaireShow) to utilize the new questionnaireApi for API calls, ensuring consistency across the application. - Removed deprecated questionnaire routes from the previous API structure. --- src/Utils/request/api.tsx | 61 --------------- .../Questionnaire/QuestionnaireForm.tsx | 3 +- .../Questionnaire/QuestionnaireSearch.tsx | 4 +- src/components/Questionnaire/index.tsx | 4 +- src/components/Questionnaire/show.tsx | 4 +- src/types/questionnaire/questionnaireApi.ts | 75 +++++++++++++++++++ 6 files changed, 83 insertions(+), 68 deletions(-) create mode 100644 src/types/questionnaire/questionnaireApi.ts diff --git a/src/Utils/request/api.tsx b/src/Utils/request/api.tsx index 4b96be5cba7..c1ee0c6c5a7 100644 --- a/src/Utils/request/api.tsx +++ b/src/Utils/request/api.tsx @@ -59,7 +59,6 @@ import { } from "@/types/questionnaire/batch"; import { Code } from "@/types/questionnaire/code"; import { Diagnosis } from "@/types/questionnaire/diagnosis"; -import type { QuestionnaireDetail } from "@/types/questionnaire/questionnaire"; import type { QuestionnaireResponse } from "@/types/questionnaire/questionnaireResponse"; import { Symptom } from "@/types/questionnaire/symptom"; import { @@ -604,66 +603,6 @@ const routes = { }, }, - // Questionnaire Routes - questionnaire: { - list: { - path: "/api/v1/questionnaire/", - method: "GET", - TRes: Type>(), - }, - - detail: { - path: "/api/v1/questionnaire/{id}/", - method: "GET", - TRes: Type(), - }, - - create: { - path: "/api/v1/questionnaire/", - method: "POST", - TRes: Type(), - TBody: Type>(), - }, - - update: { - path: "/api/v1/questionnaire/{id}/", - method: "PUT", - TRes: Type(), - TBody: Type(), - }, - - partialUpdate: { - path: "/api/v1/questionnaire/{id}/", - method: "PATCH", - TRes: Type(), - TBody: Type>(), - }, - - delete: { - path: "/api/v1/questionnaire/{id}/", - method: "DELETE", - TRes: Type>(), - }, - - submit: { - path: "/api/v1/questionnaire/{id}/submit/", - method: "POST", - TRes: Type>(), - TBody: Type<{ - resource_id: string; - encounter?: string; - patient: string; - responses: Array<{ - question_id: string; - value: string | number | boolean; - note?: string; - bodysite?: string; - method?: string; - }>; - }>(), - }, - }, - batchRequest: { path: "/api/v1/batch_requests/", method: "POST", diff --git a/src/components/Questionnaire/QuestionnaireForm.tsx b/src/components/Questionnaire/QuestionnaireForm.tsx index bb11c82ee77..278d12dcdf6 100644 --- a/src/components/Questionnaire/QuestionnaireForm.tsx +++ b/src/components/Questionnaire/QuestionnaireForm.tsx @@ -21,6 +21,7 @@ import { import type { QuestionnaireResponse } from "@/types/questionnaire/form"; import type { Question } from "@/types/questionnaire/question"; import { QuestionnaireDetail } from "@/types/questionnaire/questionnaire"; +import questionnaireApi from "@/types/questionnaire/questionnaireApi"; import { QuestionRenderer } from "./QuestionRenderer"; import { QuestionnaireSearch } from "./QuestionnaireSearch"; @@ -70,7 +71,7 @@ export function QuestionnaireForm({ data: questionnaireData, loading: isQuestionnaireLoading, error: questionnaireError, - } = useQuery(routes.questionnaire.detail, { + } = useQuery(questionnaireApi.detail, { pathParams: { id: questionnaireSlug ?? "" }, prefetch: !!questionnaireSlug && !FIXED_QUESTIONNAIRES[questionnaireSlug], }); diff --git a/src/components/Questionnaire/QuestionnaireSearch.tsx b/src/components/Questionnaire/QuestionnaireSearch.tsx index 23dd5965f46..fd92931c7b8 100644 --- a/src/components/Questionnaire/QuestionnaireSearch.tsx +++ b/src/components/Questionnaire/QuestionnaireSearch.tsx @@ -11,10 +11,10 @@ import { } from "@/components/ui/popover"; import { Skeleton } from "@/components/ui/skeleton"; -import routes from "@/Utils/request/api"; import query from "@/Utils/request/query"; import { conditionalAttribute } from "@/Utils/utils"; import type { QuestionnaireDetail } from "@/types/questionnaire/questionnaire"; +import questionnaireApi from "@/types/questionnaire/questionnaireApi"; interface QuestionnaireSearchProps { onSelect: (questionnaire: QuestionnaireDetail) => void; @@ -38,7 +38,7 @@ export function QuestionnaireSearch({ const { data: questionnaires, isLoading } = useQuery({ queryKey: ["questionnaires", "list", search, subjectType], - queryFn: query(routes.questionnaire.list, { + queryFn: query(questionnaireApi.list, { queryParams: { title: search, ...conditionalAttribute(!!subjectType, { diff --git a/src/components/Questionnaire/index.tsx b/src/components/Questionnaire/index.tsx index 288bd644623..3e3386f8cc7 100644 --- a/src/components/Questionnaire/index.tsx +++ b/src/components/Questionnaire/index.tsx @@ -3,15 +3,15 @@ import { useNavigate } from "raviger"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; -import routes from "@/Utils/request/api"; import useQuery from "@/Utils/request/useQuery"; import { QuestionnaireDetail } from "@/types/questionnaire/questionnaire"; +import questionnaireApi from "@/types/questionnaire/questionnaireApi"; import Loading from "../Common/Loading"; export function QuestionnaireList() { const navigate = useNavigate(); - const { data: response, loading } = useQuery(routes.questionnaire.list); + const { data: response, loading } = useQuery(questionnaireApi.list); if (loading) { return ; diff --git a/src/components/Questionnaire/show.tsx b/src/components/Questionnaire/show.tsx index 1b266c51058..814b65b457a 100644 --- a/src/components/Questionnaire/show.tsx +++ b/src/components/Questionnaire/show.tsx @@ -9,9 +9,9 @@ import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import routes from "@/Utils/request/api"; import useQuery from "@/Utils/request/useQuery"; import type { Question } from "@/types/questionnaire/question"; +import questionnaireApi from "@/types/questionnaire/questionnaireApi"; import Loading from "../Common/Loading"; import { QuestionnaireForm } from "./QuestionnaireForm"; @@ -66,7 +66,7 @@ export function QuestionnaireShow({ id }: QuestionnaireShowProps) { data: questionnaire, loading, error, - } = useQuery(routes.questionnaire.detail, { + } = useQuery(questionnaireApi.detail, { pathParams: { id }, }); diff --git a/src/types/questionnaire/questionnaireApi.ts b/src/types/questionnaire/questionnaireApi.ts new file mode 100644 index 00000000000..f7e7e8467ae --- /dev/null +++ b/src/types/questionnaire/questionnaireApi.ts @@ -0,0 +1,75 @@ +import { HttpMethod, Type } from "@/Utils/request/api"; +import { PaginatedResponse } from "@/Utils/request/types"; + +import { Organization } from "../organization/organization"; +import { QuestionnaireDetail } from "./questionnaire"; + +export default { + list: { + path: "/api/v1/questionnaire/", + method: HttpMethod.GET, + TRes: Type>(), + }, + + detail: { + path: "/api/v1/questionnaire/{id}/", + method: HttpMethod.GET, + TRes: Type(), + }, + + create: { + path: "/api/v1/questionnaire/", + method: HttpMethod.POST, + TRes: Type(), + TBody: Type>(), + }, + + update: { + path: "/api/v1/questionnaire/{id}/", + method: HttpMethod.PUT, + TRes: Type(), + TBody: Type(), + }, + + partialUpdate: { + path: "/api/v1/questionnaire/{id}/", + method: HttpMethod.PATCH, + TRes: Type(), + TBody: Type>(), + }, + + delete: { + path: "/api/v1/questionnaire/{id}/", + method: HttpMethod.DELETE, + TRes: Type>(), + }, + + submit: { + path: "/api/v1/questionnaire/{id}/submit/", + method: HttpMethod.POST, + TRes: Type>(), + TBody: Type<{ + resource_id: string; + encounter?: string; + patient: string; + responses: Array<{ + question_id: string; + value: string | number | boolean; + note?: string; + bodysite?: string; + method?: string; + }>; + }>(), + }, + getOrganizations: { + path: "/api/v1/questionnaire/{id}/get_organizations/", + method: HttpMethod.GET, + TRes: Type>(), + }, + setOrganizations: { + path: "/api/v1/questionnaire/{id}/set_organizations/", + method: HttpMethod.POST, + TRes: Type>(), + TBody: {} as { organization: string[] }, + }, +}; From 77c8a9bf406b2d12dba7deee0212d5b993d399da Mon Sep 17 00:00:00 2001 From: Bodhish Thomas Date: Thu, 2 Jan 2025 19:01:01 +0530 Subject: [PATCH 3/4] ManageQuestionnaireOrganizationsSheet component and integrate with QuestionnaireShow - Introduced a new ManageQuestionnaireOrganizationsSheet component for managing organizations associated with a questionnaire. - Integrated the new component into the QuestionnaireShow component, allowing users to add or remove organizations. - Updated the questionnaireApi to reflect the correct request body structure for setting organizations. - Enhanced the user interface for organization selection and management, including search functionality and visual feedback for selected organizations. --- .../ManageQuestionnaireOrganizationsSheet.tsx | 229 ++++++++++++++++++ src/components/Questionnaire/show.tsx | 2 + src/types/questionnaire/questionnaireApi.ts | 2 +- 3 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 src/components/Questionnaire/ManageQuestionnaireOrganizationsSheet.tsx diff --git a/src/components/Questionnaire/ManageQuestionnaireOrganizationsSheet.tsx b/src/components/Questionnaire/ManageQuestionnaireOrganizationsSheet.tsx new file mode 100644 index 00000000000..9e2422ee300 --- /dev/null +++ b/src/components/Questionnaire/ManageQuestionnaireOrganizationsSheet.tsx @@ -0,0 +1,229 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { Building, Check, Loader2, X } from "lucide-react"; +import { useEffect, useState } from "react"; +import { toast } from "sonner"; + +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { + Sheet, + SheetContent, + SheetDescription, + SheetFooter, + SheetHeader, + SheetTitle, + SheetTrigger, +} from "@/components/ui/sheet"; + +import mutate from "@/Utils/request/mutate"; +import query from "@/Utils/request/query"; +import organizationApi from "@/types/organization/organizationApi"; +import questionnaireApi from "@/types/questionnaire/questionnaireApi"; + +interface Props { + questionnaireId: string; + trigger?: React.ReactNode; +} + +export default function ManageQuestionnaireOrganizationsSheet({ + questionnaireId, + trigger, +}: Props) { + const queryClient = useQueryClient(); + const [open, setOpen] = useState(false); + const [searchQuery, setSearchQuery] = useState(""); + const [selectedIds, setSelectedIds] = useState([]); + + const { data: organizations, isLoading } = useQuery({ + queryKey: ["questionnaire", questionnaireId, "organizations"], + queryFn: query(questionnaireApi.getOrganizations, { + pathParams: { id: questionnaireId }, + }), + enabled: open, + }); + + const { data: availableOrganizations, isLoading: isLoadingOrganizations } = + useQuery({ + queryKey: ["organizations", searchQuery], + queryFn: query(organizationApi.list, { + queryParams: { + org_type: "role", + name: searchQuery || undefined, + }, + }), + enabled: open, + }); + + const { mutate: setOrganizations, isPending: isUpdating } = useMutation({ + mutationFn: (organizations: string[]) => + mutate(questionnaireApi.setOrganizations, { + pathParams: { id: questionnaireId }, + body: { organizations: organizations }, + })({ organizations: organizations }), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["questionnaire", questionnaireId, "organizations"], + }); + toast.success("Organizations updated successfully"); + setOpen(false); + }, + }); + + // Initialize selected IDs when organizations are loaded + useEffect(() => { + if (organizations?.results) { + setSelectedIds(organizations.results.map((org) => org.id)); + } + }, [organizations?.results]); + + const handleToggleOrganization = (orgId: string) => { + setSelectedIds((current) => + current.includes(orgId) + ? current.filter((id) => id !== orgId) + : [...current, orgId], + ); + }; + + const handleSave = () => { + setOrganizations(selectedIds); + }; + + const selectedOrganizations = organizations?.results.filter((org) => + selectedIds.includes(org.id), + ); + + const hasChanges = + JSON.stringify(organizations?.results.map((org) => org.id).sort()) !== + JSON.stringify(selectedIds.sort()); + + return ( + + + {trigger || ( + + )} + + + + Manage Organizations + + Add or remove organizations from this questionnaire + + + +
+ {/* Selected Organizations */} +
+

Selected Organizations

+
+ {selectedOrganizations?.map((org) => ( + + {org.name} + + + ))} + {!isLoading && + (!selectedOrganizations || + selectedOrganizations.length === 0) && ( +

+ No organizations selected +

+ )} +
+
+ + {/* Organization Selector */} +
+

Add Organizations

+ + + + No organizations found. + + {isLoadingOrganizations ? ( +
+ +
+ ) : ( + availableOrganizations?.results.map((org) => ( + handleToggleOrganization(org.id)} + > +
+ + {org.name} + {org.description && ( + + - {org.description} + + )} +
+ {selectedIds.includes(org.id) && ( + + )} +
+ )) + )} +
+
+
+
+
+ + +
+ + +
+
+
+
+ ); +} diff --git a/src/components/Questionnaire/show.tsx b/src/components/Questionnaire/show.tsx index 814b65b457a..a1f70a6128f 100644 --- a/src/components/Questionnaire/show.tsx +++ b/src/components/Questionnaire/show.tsx @@ -14,6 +14,7 @@ import type { Question } from "@/types/questionnaire/question"; import questionnaireApi from "@/types/questionnaire/questionnaireApi"; import Loading from "../Common/Loading"; +import ManageQuestionnaireOrganizationsSheet from "./ManageQuestionnaireOrganizationsSheet"; import { QuestionnaireForm } from "./QuestionnaireForm"; interface QuestionnaireShowProps { @@ -106,6 +107,7 @@ export function QuestionnaireShow({ id }: QuestionnaireShowProps) {

{questionnaire.description}

+