coronasafe/care_fe

View on GitHub
src/components/Questionnaire/QuestionnaireProperties.tsx

Summary

Maintainability
D
2 days
Test Coverage
File `QuestionnaireProperties.tsx` has 383 lines of code (exceeds 250 allowed). Consider refactoring.
import { Building, Tags, X } from "lucide-react";
import { useTranslation } from "react-i18next";
 
import { cn } from "@/lib/utils";
 
import CareIcon from "@/CAREUI/icons/CareIcon";
 
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
 
import { QuestionnaireDetail } from "@/types/questionnaire/questionnaire";
import {
QuestionStatus,
SubjectType,
} from "@/types/questionnaire/questionnaire";
import { QuestionnaireTagModel } from "@/types/questionnaire/tags";
 
import CloneQuestionnaireSheet from "./CloneQuestionnaireSheet";
import CreateQuestionnaireTagSheet from "./CreateQuestionnaireTagSheet";
import ManageQuestionnaireOrganizationsSheet from "./ManageQuestionnaireOrganizationsSheet";
import { OrgSelectorPopover } from "./ManageQuestionnaireOrganizationsSheet";
import ManageQuestionnaireTagsSheet from "./ManageQuestionnaireTagsSheet";
import { TagSelectorPopover } from "./ManageQuestionnaireTagsSheet";
 
interface Organization {
id: string;
name: string;
description?: string;
}
 
interface OrganizationResponse {
results: Organization[];
}
 
interface QuestionnairePropertiesProps {
questionnaire: QuestionnaireDetail;
updateQuestionnaireField: <K extends keyof QuestionnaireDetail>(
field: K,
value: QuestionnaireDetail[K],
) => void;
id?: string;
organizations?: OrganizationResponse;
organizationSelection: {
selectedOrgs: Organization[];
onToggle: (orgId: string) => void;
searchQuery: string;
setSearchQuery: (query: string) => void;
available?: OrganizationResponse;
isLoading?: boolean;
error: string | undefined;
setError: (error?: string) => void;
};
tags?: QuestionnaireTagModel[];
tagSelection: {
selectedTags: QuestionnaireTagModel[];
onToggle: (tagId: string) => void;
searchQuery: string;
setSearchQuery: (query: string) => void;
available?: QuestionnaireTagModel[];
isLoading?: boolean;
onTagCreated?: (tag: QuestionnaireTagModel) => void;
};
}
 
Similar blocks of code found in 2 locations. Consider refactoring.
function StatusSelector({
value,
onChange,
}: {
value: QuestionStatus;
onChange: (value: QuestionStatus) => void;
}) {
const { t } = useTranslation();
 
return (
<div className="space-y-2 w-fit">
<Label htmlFor="status">{t("status")}</Label>
<RadioGroup
value={value}
onValueChange={onChange}
className="flex items-center gap-0 border border-gray-300 divide-x rounded-md bg-white [&>div:has([data-state=checked])]:text-primary-500 [&>div:has([data-state=checked])]:bg-primary-200"
>
{["active", "draft", "retired"].map((status) => (
<div
key={status}
className={cn(
"flex items-center px-2 py-1",
status === "active" && "rounded-l-md",
status === "retired" && "rounded-r-md",
)}
>
<RadioGroupItem value={status} id={`status-${status}`} />
<Label
htmlFor={`status-${status}`}
className="text-sm font-normal text-gray-950"
>
{t(status)}
</Label>
</div>
))}
</RadioGroup>
</div>
);
}
 
Similar blocks of code found in 2 locations. Consider refactoring.
function SubjectTypeSelector({
value,
onChange,
}: {
value: SubjectType;
onChange: (value: SubjectType) => void;
}) {
const { t } = useTranslation();
 
return (
<div className="space-y-2">
<Label htmlFor="subject_type">{t("subject_type")}</Label>
<RadioGroup
value={value}
onValueChange={onChange}
className="flex w-fit items-center gap-0 border border-gray-300 divide-x rounded-md bg-white [&>div:has([data-state=checked])]:bg-primary-200"
>
{[
{ value: "patient", label: "patient" },
{ value: "encounter", label: "encounter" },
].map((type) => (
<div
key={type.value}
className={cn(
"flex items-center px-2 py-1",
type.value === "patient" && "rounded-l-md",
type.value === "encounter" && "rounded-r-md",
)}
>
<RadioGroupItem
value={type.value}
id={`subject-type-${type.value}`}
/>
<Label
htmlFor={`subject-type-${type.value}`}
className="text-sm font-normal text-gray-950"
>
{t(type.label)}
</Label>
</div>
))}
</RadioGroup>
</div>
);
}
 
function OrganizationSelector({
id,
organizations,
selection,
}: {
id?: string;
organizations?: OrganizationResponse;
selection: QuestionnairePropertiesProps["organizationSelection"];
}) {
const { t } = useTranslation();
 
if (id) {
return (
<>
<div className="flex flex-wrap gap-2 mb-2">
Similar blocks of code found in 2 locations. Consider refactoring.
{organizations?.results.map((org) => (
<Badge
key={org.id}
variant="secondary"
className="flex items-center gap-1"
>
<Building className="h-3 w-3" />
{org.name}
</Badge>
))}
{(!organizations?.results || organizations.results.length === 0) && (
<p className="text-sm text-red-500">{selection.error}</p>
)}
</div>
Similar blocks of code found in 2 locations. Consider refactoring.
<ManageQuestionnaireOrganizationsSheet
questionnaireId={id}
trigger={
<Button variant="outline" className="w-full justify-start">
<Building className="mr-2 size-4" />
{t("manage_organization_one")}
</Button>
}
/>
</>
);
}
 
return (
<div className="space-y-4">
<div className="flex flex-wrap gap-2">
Similar blocks of code found in 2 locations. Consider refactoring.
{selection.selectedOrgs.length > 0 ? (
selection.selectedOrgs.map((org) => (
<Badge
key={org.id}
variant="secondary"
className="flex items-center gap-1"
>
{org.name}
<Button
variant="ghost"
size="icon"
className="size-4 p-0 hover:bg-transparent"
onClick={() => selection.onToggle(org.id)}
>
<X className="h-3 w-3" />
</Button>
</Badge>
))
) : (
<p className="text-sm text-gray-500">
{t("no_organizations_selected")}
</p>
)}
</div>
{selection.error && (
<p className="text-sm text-red-500">{selection.error}</p>
)}
<OrgSelectorPopover
title={t("select_organizations")}
selected={selection.selectedOrgs.map((org) => org.id)}
onToggle={(value) => {
selection.onToggle(value);
if (selection.error) selection.setError(undefined);
}}
searchQuery={selection.searchQuery}
onSearchChange={selection.setSearchQuery}
isLoading={selection.isLoading}
organizations={selection.available}
/>
</div>
);
}
 
function TagSelector({
id,
selection,
questionnaire,
}: {
id?: string;
selection: QuestionnairePropertiesProps["tagSelection"];
questionnaire: QuestionnaireDetail;
}) {
const { t } = useTranslation();
 
if (id) {
return (
<>
<div className="flex flex-wrap gap-2 mb-2">
Similar blocks of code found in 2 locations. Consider refactoring.
{questionnaire.tags.map((tag) => (
<Badge
key={tag.id}
variant="secondary"
className="flex items-center gap-1"
>
<Building className="h-3 w-3" />
{tag.name}
</Badge>
))}
{questionnaire.tags.length === 0 && (
<p className="text-sm text-gray-500">{t("no_tags_selected")}</p>
)}
</div>
Similar blocks of code found in 2 locations. Consider refactoring.
<ManageQuestionnaireTagsSheet
questionnaire={questionnaire}
trigger={
<Button variant="outline" className="w-full justify-start">
<Tags className="mr-2 size-4" />
{t("manage_tags")}
</Button>
}
/>
</>
);
}
 
return (
<div className="space-y-4">
<div className="flex flex-wrap gap-2">
Similar blocks of code found in 2 locations. Consider refactoring.
{selection.selectedTags.length > 0 ? (
selection.selectedTags.map((tag) => (
<Badge
key={tag.id}
variant="secondary"
className="flex items-center gap-1"
>
{tag.name}
<Button
variant="ghost"
size="icon"
className="size-4 p-0 hover:bg-transparent"
onClick={() => selection.onToggle(tag.id)}
>
<X className="h-3 w-3" />
</Button>
</Badge>
))
) : (
<p className="text-sm text-gray-500">{t("no_tags_selected")}</p>
)}
</div>
 
<TagSelectorPopover
title={t("select_tags")}
selected={selection.selectedTags}
onToggle={selection.onToggle}
searchQuery={selection.searchQuery}
onSearchChange={selection.setSearchQuery}
isLoading={selection.isLoading}
tagOptions={selection.available}
/>
 
{!id && (
<CreateQuestionnaireTagSheet
onTagCreated={(tag) => {
selection.onTagCreated?.(tag);
}}
trigger={
<Button variant="outline" className="w-full justify-start">
<Tags className="mr-2 size-4" />
{t("create_tag")}
</Button>
}
/>
)}
</div>
);
}
 
export function QuestionnaireProperties({
questionnaire,
updateQuestionnaireField,
id,
organizations,
organizationSelection,
tagSelection,
}: QuestionnairePropertiesProps) {
const { t } = useTranslation();
 
return (
<Card className="border-none bg-transparent shadow-none space-y-4 mt-2 ml-2">
<CardHeader className="p-0">
<CardTitle>{t("properties")}</CardTitle>
</CardHeader>
<CardContent className="space-y-6 p-0">
<StatusSelector
value={questionnaire.status}
onChange={(val) => updateQuestionnaireField("status", val)}
/>
 
<SubjectTypeSelector
value={questionnaire.subject_type}
onChange={(val) => updateQuestionnaireField("subject_type", val)}
/>
 
<div className="space-y-2">
<Label>
{t("organizations")} <span className="text-red-500">*</span>
</Label>
<OrganizationSelector
id={id}
organizations={organizations}
selection={organizationSelection}
/>
</div>
Similar blocks of code found in 2 locations. Consider refactoring.
<div className="space-y-2">
<Label>{t("tags")}</Label>
<TagSelector
id={id}
selection={tagSelection}
questionnaire={questionnaire}
/>
</div>
{id && (
<CloneQuestionnaireSheet
questionnaire={questionnaire}
Similar blocks of code found in 2 locations. Consider refactoring.
trigger={
<Button variant="outline" className="w-full justify-start">
<CareIcon icon="l-copy" className="mr-2 size-4" />
{t("clone_questionnaire")}
</Button>
}
/>
)}
 
<div className="space-y-2">
<Label htmlFor="version">{t("version")}</Label>
<Input
id="version"
value={questionnaire.version || "0.0.1"}
disabled={true}
onChange={(e) =>
updateQuestionnaireField("version", e.target.value)
}
/>
</div>
</CardContent>
</Card>
);
}