From 6d880c73b4d65a67c00c5908c07931f950dbe063 Mon Sep 17 00:00:00 2001 From: Bodhish Thomas Date: Sun, 2 Feb 2025 20:46:36 +0530 Subject: [PATCH 01/19] Add create questionnaire route and enhance questionnaire editor functionality (#10363) --- src/Routers/routes/questionnaireRoutes.tsx | 1 + .../Questionnaire/QuestionnaireEditor.tsx | 207 ++++++++++++++++-- src/components/Questionnaire/show.tsx | 2 +- src/components/ui/button.tsx | 2 +- src/components/ui/input.tsx | 2 +- src/components/ui/phone-input.tsx | 2 +- src/components/ui/select.tsx | 2 +- src/components/ui/textarea.tsx | 2 +- 8 files changed, 196 insertions(+), 24 deletions(-) diff --git a/src/Routers/routes/questionnaireRoutes.tsx b/src/Routers/routes/questionnaireRoutes.tsx index ccc89f17f90..670861016a7 100644 --- a/src/Routers/routes/questionnaireRoutes.tsx +++ b/src/Routers/routes/questionnaireRoutes.tsx @@ -6,6 +6,7 @@ import { AppRoutes } from "@/Routers/AppRouter"; const QuestionnaireRoutes: AppRoutes = { "/questionnaire": () => , + "/questionnaire/create": () => , "/questionnaire/:id": ({ id }) => , "/questionnaire/:id/edit": ({ id }) => , }; diff --git a/src/components/Questionnaire/QuestionnaireEditor.tsx b/src/components/Questionnaire/QuestionnaireEditor.tsx index 22f4a701058..880d42b4547 100644 --- a/src/components/Questionnaire/QuestionnaireEditor.tsx +++ b/src/components/Questionnaire/QuestionnaireEditor.tsx @@ -1,5 +1,6 @@ import { DragDropContext, Draggable, Droppable } from "@hello-pangea/dnd"; import { useMutation, useQuery } from "@tanstack/react-query"; +import { Building, Check, Loader2, X } from "lucide-react"; import { useNavigate } from "raviger"; import { useEffect, useState } from "react"; import { toast } from "sonner"; @@ -15,6 +16,14 @@ import { CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { @@ -32,6 +41,7 @@ import Loading from "@/components/Common/Loading"; import mutate from "@/Utils/request/mutate"; import query from "@/Utils/request/query"; +import organizationApi from "@/types/organization/organizationApi"; import { AnswerOption, EnableWhen, @@ -46,10 +56,11 @@ import { } from "@/types/questionnaire/questionnaire"; import questionnaireApi from "@/types/questionnaire/questionnaireApi"; +import ManageQuestionnaireOrganizationsSheet from "./ManageQuestionnaireOrganizationsSheet"; import { QuestionnaireForm } from "./QuestionnaireForm"; interface QuestionnaireEditorProps { - id: string; + id?: string; } export default function QuestionnaireEditor({ id }: QuestionnaireEditorProps) { @@ -58,6 +69,8 @@ export default function QuestionnaireEditor({ id }: QuestionnaireEditorProps) { const [expandedQuestions, setExpandedQuestions] = useState>( new Set(), ); + const [selectedOrgIds, setSelectedOrgIds] = useState([]); + const [orgSearchQuery, setOrgSearchQuery] = useState(""); const { data: initialQuestionnaire, @@ -66,13 +79,36 @@ export default function QuestionnaireEditor({ id }: QuestionnaireEditorProps) { } = useQuery({ queryKey: ["questionnaireDetail", id], queryFn: query(questionnaireApi.detail, { - pathParams: { id }, + pathParams: { id: id! }, }), + enabled: !!id, + }); + + const { data: availableOrganizations, isLoading: isLoadingOrganizations } = + useQuery({ + queryKey: ["organizations", orgSearchQuery], + queryFn: query(organizationApi.list, { + queryParams: { + org_type: "role", + name: orgSearchQuery || undefined, + }, + }), + }); + + const { mutate: createQuestionnaire, isPending: isCreating } = useMutation({ + mutationFn: mutate(questionnaireApi.create), + onSuccess: (data: QuestionnaireDetail) => { + toast.success("Questionnaire created successfully"); + navigate(`/questionnaire/${data.slug}`); + }, + onError: (_error) => { + toast.error("Failed to create questionnaire"); + }, }); - const { mutate: updateQuestionnaire, isPending } = useMutation({ + const { mutate: updateQuestionnaire, isPending: isUpdating } = useMutation({ mutationFn: mutate(questionnaireApi.update, { - pathParams: { id }, + pathParams: { id: id! }, }), onSuccess: () => { toast.success("Questionnaire updated successfully"); @@ -83,7 +119,22 @@ export default function QuestionnaireEditor({ id }: QuestionnaireEditorProps) { }); const [questionnaire, setQuestionnaire] = - useState(null); + useState(() => { + if (!id) { + return { + id: "", + title: "", + description: "", + status: "draft", + version: "1.0", + subject_type: "patient", + questions: [], + slug: "", + tags: [], + } as QuestionnaireDetail; + } + return null; + }); useEffect(() => { if (initialQuestionnaire) { @@ -91,7 +142,7 @@ export default function QuestionnaireEditor({ id }: QuestionnaireEditorProps) { } }, [initialQuestionnaire]); - if (isLoading) return ; + if (id && isLoading) return ; if (error) { return ( @@ -122,8 +173,19 @@ export default function QuestionnaireEditor({ id }: QuestionnaireEditorProps) { setQuestionnaire((prev) => (prev ? { ...prev, [field]: value } : null)); }; + const handleSave = () => { + if (id) { + updateQuestionnaire(questionnaire); + } else { + createQuestionnaire({ + ...questionnaire, + organizations: selectedOrgIds, + }); + } + }; + const handleCancel = () => { - navigate(`/questionnaire/${id}`); + navigate(id ? `/questionnaire/${id}` : "/questionnaire"); }; const handleDragEnd = (result: any) => { @@ -148,12 +210,21 @@ export default function QuestionnaireEditor({ id }: QuestionnaireEditorProps) { }); }; + const handleToggleOrganization = (orgId: string) => { + setSelectedOrgIds((current) => + current.includes(orgId) + ? current.filter((id) => id !== orgId) + : [...current, orgId], + ); + }; + return (
- {/* Top bar: Title + Buttons */}
-

Edit Questionnaire

+

+ {id ? "Edit Questionnaire" : "Create Questionnaire"} +

{questionnaire.description}

@@ -161,12 +232,9 @@ export default function QuestionnaireEditor({ id }: QuestionnaireEditorProps) { Cancel -
@@ -182,7 +250,6 @@ export default function QuestionnaireEditor({ id }: QuestionnaireEditorProps) {
- {/* Left Sidebar: Navigation */}
@@ -318,11 +385,100 @@ export default function QuestionnaireEditor({ id }: QuestionnaireEditorProps) {
+ +
+ + {id ? ( + + + Manage Organizations + + } + /> + ) : ( +
+
+ {selectedOrgIds.length > 0 ? ( + availableOrganizations?.results + .filter((org) => selectedOrgIds.includes(org.id)) + .map((org) => ( + + {org.name} + + + )) + ) : ( +

+ No organizations selected +

+ )} +
+ + + + + No organizations found. + + {isLoadingOrganizations ? ( +
+ +
+ ) : ( + availableOrganizations?.results.map((org) => ( + + handleToggleOrganization(org.id) + } + > +
+ + {org.name} + {org.description && ( + + - {org.description} + + )} +
+ {selectedOrgIds.includes(org.id) && ( + + )} +
+ )) + )} +
+
+
+
+ )} +
- {/* Main Content */}
@@ -340,6 +496,22 @@ export default function QuestionnaireEditor({ id }: QuestionnaireEditorProps) { />
+
+ + + updateQuestionnaireField("slug", e.target.value) + } + placeholder="unique-identifier-for-questionnaire" + className="font-mono" + /> +

+ A unique URL-friendly identifier for this questionnaire +

+
+