Skip to content

Commit

Permalink
Add view questionnaire
Browse files Browse the repository at this point in the history
  • Loading branch information
olimsaidov committed Nov 22, 2024
1 parent b6bf03f commit 5c8ce08
Show file tree
Hide file tree
Showing 12 changed files with 344 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export default async function PatientsPage({ searchParams }: PageProps) {
<Table>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead className="pl-6">ID</TableHead>
<TableHead>Name</TableHead>
<TableHead>Gender</TableHead>
<TableHead>Birth Date</TableHead>
Expand All @@ -114,7 +114,7 @@ export default async function PatientsPage({ searchParams }: PageProps) {
<TableBody>
{resources.map((resource) => (
<TableRow key={resource.id}>
<TableCell>{resource.id}</TableCell>
<TableCell className="pl-6">{resource.id}</TableCell>
<TableCell>{constructName(resource.name)}</TableCell>
<TableCell>
{constructGender(resource.gender) || "Not specified"}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export default async function PractitionersPage({ searchParams }: PageProps) {
<Table>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead className="pl-6">ID</TableHead>
<TableHead>Name</TableHead>
<TableHead>Gender</TableHead>
<TableHead>Email</TableHead>
Expand All @@ -113,7 +113,7 @@ export default async function PractitionersPage({ searchParams }: PageProps) {
<TableBody>
{resources.map((resource) => (
<TableRow key={resource.id}>
<TableCell>{resource.id}</TableCell>
<TableCell className="pl-6">{resource.id}</TableCell>
<TableCell>{constructName(resource.name)}</TableCell>
<TableCell>
{constructGender(resource.gender) || "Not specified"}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Bundle, Questionnaire } from "fhir/r4";
import { isDefined } from "@/lib/utils";
import { decidePageSize } from "@/lib/server/utils";
import ky from "ky";
import { QuestionnairesActions } from "@/components/questionnaires-actions";

interface PageProps {
searchParams: Promise<{
Expand Down Expand Up @@ -83,22 +84,20 @@ export default async function PublicLibraryPage({ searchParams }: PageProps) {
<Table>
<TableHeader>
<TableRow>
<TableHead>Title</TableHead>
<TableHead className="pl-6">Title</TableHead>
<TableHead>Status</TableHead>
<TableHead>Date</TableHead>
<TableHead>Actions</TableHead>
<TableHead className="w-[1%] pr-6">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{resources.map((resource) => (
<TableRow key={resource.id}>
<TableCell>{resource.title}</TableCell>
<TableCell className="pl-6">{resource.title}</TableCell>
<TableCell>{resource.status}</TableCell>
<TableCell>{resource.date}</TableCell>
<TableCell>
<Button variant="outline" size="sm" asChild>
<Link href={`/questionnaires/${resource.id}`}>View</Link>
</Button>
<TableCell className="text-right pr-6">
<QuestionnairesActions questionnaire={resource} library />
</TableCell>
</TableRow>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Pager } from "@/components/pager";
import { Bundle, Questionnaire } from "fhir/r4";
import { isDefined } from "@/lib/utils";
import { decidePageSize } from "@/lib/server/utils";
import { QuestionnairesActions } from "@/components/questionnaires-actions";

interface PageProps {
searchParams: Promise<{
Expand Down Expand Up @@ -81,22 +82,20 @@ export default async function QuestionnairesPage({ searchParams }: PageProps) {
<Table>
<TableHeader>
<TableRow>
<TableHead>Title</TableHead>
<TableHead className="pl-6">Title</TableHead>
<TableHead>Status</TableHead>
<TableHead>Date</TableHead>
<TableHead>Actions</TableHead>
<TableHead className="w-[1%] pr-6">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{resources.map((resource) => (
<TableRow key={resource.id}>
<TableCell>{resource.title}</TableCell>
<TableCell className="pl-6">{resource.title}</TableCell>
<TableCell>{resource.status}</TableCell>
<TableCell>{resource.date}</TableCell>
<TableCell>
<Button variant="outline" size="sm" asChild>
<Link href={`/questionnaires/${resource.id}`}>View</Link>
</Button>
<TableCell className="text-right pr-6">
<QuestionnairesActions questionnaire={resource} />
</TableCell>
</TableRow>
))}
Expand Down
16 changes: 15 additions & 1 deletion aidbox-forms-smart-launch-2/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Metadata } from "next";
import "./globals.css";
import { Toaster } from "@/components/toaster";

export const metadata: Metadata = {
title: "Aidbox Forms Smart Launch",
Expand All @@ -14,7 +15,20 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<body className="antialiased">{children}</body>
<head>
<script
src="https://form-builder.aidbox.app/static/aidbox-forms-renderer-webcomponent.js"
async
></script>
<script
src="https://form-builder.aidbox.app/static/aidbox-forms-builder-webcomponent.js"
async
></script>
</head>
<body className="antialiased">
{children}
<Toaster />
</body>
</html>
);
}
44 changes: 44 additions & 0 deletions aidbox-forms-smart-launch-2/src/components/forms-renderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useEffect, useRef } from "react";
import { useAwaiter } from "@/hooks/use-awaiter";
import { Questionnaire } from "fhir/r4";

export function FormsRenderer({
questionnaire,
onChange,
}: {
questionnaire: Questionnaire;
onChange?: (questionnaire: Questionnaire) => void;
}) {
const ref = useRef<HTMLIFrameElement>(null);

useEffect(() => {
const current = ref.current;

if (current) {
const handler = (e: Event) => {
onChange?.((e as CustomEvent<Questionnaire>).detail);
};

current.addEventListener("change", handler);

return () => {
current.removeEventListener("change", handler);
};
}
}, [onChange]);

useAwaiter(ref);

return (
<aidbox-form-renderer
ref={ref}
questionnaire={JSON.stringify(questionnaire)}
style={{
width: "100%",
height: "100%",
border: "none",
flex: 1,
}}
/>
);
}
127 changes: 127 additions & 0 deletions aidbox-forms-smart-launch-2/src/components/questionnaires-actions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
"use client";

import { Questionnaire } from "fhir/r4";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";
import {
Copy,
Edit,
Eye,
Import,
MoreHorizontal,
Plus,
Trash2,
} from "lucide-react";
import Link from "next/link";
import { Suspense, useState } from "react";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { FormsRenderer } from "@/components/forms-renderer";
import { useToast } from "@/hooks/use-toast";
import { Spinner } from "@/components/spinner";

export function QuestionnairesActions({
questionnaire,
library,
}: {
questionnaire: Questionnaire;
library?: boolean;
}) {
const [viewing, setViewing] = useState(false);
const { toast } = useToast();

return (
<>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Open menu</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem
onClick={() =>
navigator &&
navigator.clipboard.writeText(questionnaire.id as string)
}
>
<Copy />
Copy ID
</DropdownMenuItem>
<DropdownMenuSeparator />

<DropdownMenuItem onClick={() => setViewing(true)}>
<Eye />
Preview
</DropdownMenuItem>

{!library && (
<DropdownMenuItem asChild>
<Link href={`/questionnaires/${questionnaire.id}`}>
<Edit />
Edit
</Link>
</DropdownMenuItem>
)}

{!library && (
<DropdownMenuItem
className="text-destructive focus:text-destructive"
onClick={() => {}}
>
<Trash2 />
Delete
</DropdownMenuItem>
)}

{!library && (
<DropdownMenuItem onClick={() => {}}>
<Plus />
Create response
</DropdownMenuItem>
)}

{library && (
<DropdownMenuItem onClick={() => {}}>
<Import />
Import
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
<Dialog onOpenChange={setViewing} open={viewing}>
<DialogContent className="flex flex-col max-w-[calc(100vw_-_4rem)] h-[calc(100vh_-_4rem)]">
<DialogHeader>
<DialogTitle>Preview</DialogTitle>
</DialogHeader>
{viewing && (
<Suspense fallback={<Spinner expand={true} />}>
<FormsRenderer
questionnaire={questionnaire}
onChange={() => {
toast({
title: "Not saved",
description: "This is a preview, changes will not be saved",
});
}}
/>
</Suspense>
)}
</DialogContent>
</Dialog>
</>
);
}
36 changes: 36 additions & 0 deletions aidbox-forms-smart-launch-2/src/components/spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { type SVGProps } from "react";

export function Spinner(props: SVGProps<SVGSVGElement> & { expand?: boolean }) {
const svg = (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 300 150"
{...props}
width={props.expand ? "80" : undefined}
>
<path
fill="none"
stroke="#EA4A35"
strokeWidth="15"
strokeLinecap="round"
strokeDasharray="300 385"
strokeDashoffset="0"
d="M275 75c0 31-27 50-50 50-58 0-92-100-150-100-28 0-50 22-50 50s23 50 50 50c58 0 92-100 150-100 24 0 50 19 50 50Z"
>
<animate
attributeName="stroke-dashoffset"
calcMode="spline"
dur="2"
values="685;-685"
keySplines="0 0 1 1"
repeatCount="indefinite"
></animate>
</path>
</svg>
);
return props.expand ? (
<div className="flex-1 grid place-items-center">{svg} </div>
) : (
svg
);
}
35 changes: 35 additions & 0 deletions aidbox-forms-smart-launch-2/src/components/toaster.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"use client";

import { useToast } from "@/hooks/use-toast";
import {
Toast,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
} from "@/components/ui/toast";

export function Toaster() {
const { toasts } = useToast();

return (
<ToastProvider>
{toasts.map(function ({ id, title, description, action, ...props }) {
return (
<Toast key={id} {...props}>
<div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>}
{description && (
<ToastDescription>{description}</ToastDescription>
)}
</div>
{action}
<ToastClose />
</Toast>
);
})}
<ToastViewport />
</ToastProvider>
);
}
Loading

0 comments on commit 5c8ce08

Please sign in to comment.