From 77c8a9bf406b2d12dba7deee0212d5b993d399da Mon Sep 17 00:00:00 2001 From: Bodhish Thomas Date: Thu, 2 Jan 2025 19:01:01 +0530 Subject: [PATCH] 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}

+