Skip to content

Commit 32ffa12

Browse files
committed
Implemented form population and submission flow with QR generation
- Added form population with test data and sync demonstration. - Enhanced "Create response" button to open a pre-populated form with a notification if pre-fill was successful. - Redirected user to QR grid after form submission. - Enabled form preview submission to generate QR codes seamlessly.
1 parent 9fd37a6 commit 32ffa12

File tree

24 files changed

+769
-148
lines changed

24 files changed

+769
-148
lines changed

aidbox-forms-smart-launch-2/next.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ const nextConfig: NextConfig = {
66
NEXT_PUBLIC_VERSION: version,
77
},
88
output: "standalone",
9+
logging: {
10+
fetches: {
11+
fullUrl: true,
12+
},
13+
},
914
};
1015

1116
export default nextConfig;

aidbox-forms-smart-launch-2/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"next": "15.0.3",
3232
"react": "19.0.0-rc-66855b96-20241106",
3333
"react-dom": "19.0.0-rc-66855b96-20241106",
34+
"recharts": "^2.15.0",
3435
"tailwind-merge": "^2.5.4",
3536
"tailwindcss-animate": "^1.0.7",
3637
"use-local-storage-state": "^19.5.0"

aidbox-forms-smart-launch-2/pnpm-lock.yaml

Lines changed: 261 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

aidbox-forms-smart-launch-2/src/app/(authorized)/dashboard/page.tsx

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,79 @@
11
import { PageHeader } from "@/components/page-header";
2+
import { getSyncStats, saveSyncStats, sync } from "@/lib/server/sync";
3+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
4+
import { SyncDataChart } from "@/components/sync-data-chart";
5+
import { SyncDataTable } from "@/components/sync-data-table";
6+
import { ago } from "@/lib/utils";
7+
import { getCurrentClient } from "@/lib/server/smart";
8+
import { revalidatePath } from "next/cache";
9+
import { SyncButton } from "@/components/sync-button";
10+
11+
export default async function Page() {
12+
const stats = await getSyncStats();
13+
14+
async function refreshAction() {
15+
"use server";
16+
const client = await getCurrentClient();
17+
const resources = await sync(client);
18+
await saveSyncStats(resources);
19+
revalidatePath("/dashboard");
20+
}
221

3-
export default function Page() {
422
return (
523
<>
624
<PageHeader
725
items={[{ href: "/", label: "Home" }, { label: "Dashboard" }]}
826
/>
9-
<div className="flex flex-1 flex-col gap-4 p-4">
10-
<div className="grid auto-rows-min gap-4 md:grid-cols-3">
11-
<div className="aspect-video rounded-xl bg-muted/50" />
12-
<div className="aspect-video rounded-xl bg-muted/50" />
13-
<div className="aspect-video rounded-xl bg-muted/50" />
27+
<div className="container mx-auto p-4">
28+
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3 mb-8">
29+
<Card>
30+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
31+
<CardTitle className="text-sm font-medium">
32+
Last Sync Time
33+
</CardTitle>
34+
</CardHeader>
35+
<CardContent className="flex flex-row items-center gap-2">
36+
<div
37+
className="text-2xl font-bold"
38+
title={new Date(stats.timestamp).toLocaleString()}
39+
>
40+
{ago(new Date(stats.timestamp))}
41+
</div>
42+
43+
<SyncButton onClickAction={refreshAction} />
44+
</CardContent>
45+
</Card>
46+
<Card>
47+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
48+
<CardTitle className="text-sm font-medium">
49+
Total Resources Synchronized
50+
</CardTitle>
51+
</CardHeader>
52+
<CardContent>
53+
<div className="text-2xl font-bold">
54+
{Object.values(stats.resources).reduce((a, b) => a + b, 0)}
55+
</div>
56+
</CardContent>
57+
</Card>
58+
</div>
59+
<div className="grid gap-4 md:grid-cols-2 mb-8 items-start">
60+
<Card>
61+
<CardHeader>
62+
<CardTitle>Resources per Type</CardTitle>
63+
</CardHeader>
64+
<CardContent>
65+
<SyncDataChart resources={stats.resources} />
66+
</CardContent>
67+
</Card>
68+
<Card>
69+
<CardHeader>
70+
<CardTitle>Resource Type Details</CardTitle>
71+
</CardHeader>
72+
<CardContent>
73+
<SyncDataTable resources={stats.resources} />
74+
</CardContent>
75+
</Card>
1476
</div>
15-
<div className="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min" />
1677
</div>
1778
</>
1879
);

aidbox-forms-smart-launch-2/src/app/(authorized)/questionnaire-responses/[id]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export default async function EditQuestionnaireResponsePage({
3232
.then(getFirst);
3333

3434
if (!questionnaire) {
35-
throw new Error("Questionnaire not found");
35+
throw new Error("Related questionnaire not found");
3636
}
3737

3838
async function saveQuestionnaireResponse(

aidbox-forms-smart-launch-2/src/app/(authorized)/questionnaires/[id]/page.tsx

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { getCurrentAidbox } from "@/lib/server/smart";
22
import { PageHeader } from "@/components/page-header";
33
import { Questionnaire } from "fhir/r4";
44
import { QuestionnaireEditor } from "@/components/questionnaire-editor";
5+
import { HTTPError } from "ky";
6+
import { revalidatePath } from "next/cache";
57

68
interface PageProps {
79
params: Promise<{
@@ -20,12 +22,21 @@ export default async function EditQuestionnairePage({ params }: PageProps) {
2022
async function saveQuestionnaire(questionnaire: Questionnaire) {
2123
"use server";
2224

23-
const aidbox = await getCurrentAidbox();
24-
return aidbox
25-
.put(`fhir/Questionnaire/${id}`, {
26-
json: questionnaire,
27-
})
28-
.json<Questionnaire>();
25+
try {
26+
const aidbox = await getCurrentAidbox();
27+
await aidbox
28+
.put(`fhir/Questionnaire/${id}`, {
29+
json: questionnaire,
30+
})
31+
.json<Questionnaire>();
32+
33+
revalidatePath("/questionnaires");
34+
} catch (error) {
35+
if (error instanceof HTTPError) {
36+
console.dir(await error.response.json(), { depth: 1000 });
37+
}
38+
throw error;
39+
}
2940
}
3041

3142
return (

aidbox-forms-smart-launch-2/src/app/(authorized)/questionnaires/page.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,8 @@ export default async function QuestionnairesPage({ searchParams }: PageProps) {
165165
<TableRow>
166166
<TableHead className="pl-6">Title</TableHead>
167167
<TableHead>Status</TableHead>
168-
<TableHead>Date</TableHead>
168+
<TableHead>Version</TableHead>
169+
<TableHead>Last Updated</TableHead>
169170
<TableHead className="w-[1%] pr-6">Actions</TableHead>
170171
</TableRow>
171172
</TableHeader>
@@ -174,7 +175,11 @@ export default async function QuestionnairesPage({ searchParams }: PageProps) {
174175
<TableRow key={resource.id}>
175176
<TableCell className="pl-6">{resource.title}</TableCell>
176177
<TableCell>{resource.status}</TableCell>
177-
<TableCell>{resource.date}</TableCell>
178+
<TableCell>{resource.version}</TableCell>
179+
<TableCell>
180+
{resource.meta?.lastUpdated &&
181+
new Date(resource.meta.lastUpdated).toLocaleString()}
182+
</TableCell>
178183
<TableCell className="text-right pr-6">
179184
<QuestionnairesActions
180185
questionnaire={resource}
@@ -202,7 +207,7 @@ export default async function QuestionnairesPage({ searchParams }: PageProps) {
202207
(page - 1) * pageSize + 1
203208
}-${Math.min(
204209
page * pageSize,
205-
total
210+
total,
206211
)} of ${total} practitioners`}</div>
207212
) : null}
208213
<PageSizeSelect currentSize={pageSize} />

aidbox-forms-smart-launch-2/src/app/globals.css

Lines changed: 75 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -3,84 +3,84 @@
33
@tailwind utilities;
44

55
@layer base {
6-
:root {
7-
--background: 0 0% 100%;
8-
--foreground: 0 0% 3.9%;
9-
--card: 0 0% 100%;
10-
--card-foreground: 0 0% 3.9%;
11-
--popover: 0 0% 100%;
12-
--popover-foreground: 0 0% 3.9%;
13-
--primary: 6.96deg 81.17% 56.27%;
14-
--primary-foreground: 0 0% 98%;
15-
--secondary: 0 0% 96.1%;
16-
--secondary-foreground: 0 0% 9%;
17-
--muted: 0 0% 96.1%;
18-
--muted-foreground: 0 0% 45.1%;
19-
--accent: 0 0% 96.1%;
20-
--accent-foreground: 0 0% 9%;
21-
--destructive: 0 84.2% 60.2%;
22-
--destructive-foreground: 0 0% 98%;
23-
--border: 0 0% 89.8%;
24-
--input: 0 0% 89.8%;
25-
--ring: 0 0% 3.9%;
26-
--chart-1: 12 76% 61%;
27-
--chart-2: 173 58% 39%;
28-
--chart-3: 197 37% 24%;
29-
--chart-4: 43 74% 66%;
30-
--chart-5: 27 87% 67%;
31-
--radius: 0.5rem;
32-
--sidebar-background: 0 0% 98%;
33-
--sidebar-foreground: 240 5.3% 26.1%;
34-
--sidebar-primary: 240 5.9% 10%;
35-
--sidebar-primary-foreground: 0 0% 98%;
36-
--sidebar-accent: 240 4.8% 95.9%;
37-
--sidebar-accent-foreground: 240 5.9% 10%;
38-
--sidebar-border: 220 13% 91%;
39-
--sidebar-ring: 217.2 91.2% 59.8%
40-
}
6+
:root {
7+
--background: 0 0% 100%;
8+
--foreground: 0 0% 3.9%;
9+
--card: 0 0% 100%;
10+
--card-foreground: 0 0% 3.9%;
11+
--popover: 0 0% 100%;
12+
--popover-foreground: 0 0% 3.9%;
13+
--primary: 6.96deg 81.17% 56.27%;
14+
--primary-foreground: 0 0% 98%;
15+
--secondary: 0 0% 96.1%;
16+
--secondary-foreground: 0 0% 9%;
17+
--muted: 0 0% 96.1%;
18+
--muted-foreground: 0 0% 45.1%;
19+
--accent: 0 0% 96.1%;
20+
--accent-foreground: 0 0% 9%;
21+
--destructive: 0 84.2% 60.2%;
22+
--destructive-foreground: 0 0% 98%;
23+
--border: 0 0% 89.8%;
24+
--input: 0 0% 89.8%;
25+
--ring: 0 0% 3.9%;
26+
--chart-1: 12 76% 61%;
27+
--chart-2: 173 58% 39%;
28+
--chart-3: 197 37% 24%;
29+
--chart-4: 43 74% 66%;
30+
--chart-5: 27 87% 67%;
31+
--radius: 0.5rem;
32+
--sidebar-background: 0 0% 98%;
33+
--sidebar-foreground: 240 5.3% 26.1%;
34+
--sidebar-primary: 240 5.9% 10%;
35+
--sidebar-primary-foreground: 0 0% 98%;
36+
--sidebar-accent: 240 4.8% 95.9%;
37+
--sidebar-accent-foreground: 240 5.9% 10%;
38+
--sidebar-border: 220 13% 91%;
39+
--sidebar-ring: 217.2 91.2% 59.8%;
40+
}
4141

42-
.dark {
43-
--background: 0 0% 3.9%;
44-
--foreground: 0 0% 98%;
45-
--card: 0 0% 3.9%;
46-
--card-foreground: 0 0% 98%;
47-
--popover: 0 0% 3.9%;
48-
--popover-foreground: 0 0% 98%;
49-
--primary: 0 0% 98%;
50-
--primary-foreground: 0 0% 9%;
51-
--secondary: 0 0% 14.9%;
52-
--secondary-foreground: 0 0% 98%;
53-
--muted: 0 0% 14.9%;
54-
--muted-foreground: 0 0% 63.9%;
55-
--accent: 0 0% 14.9%;
56-
--accent-foreground: 0 0% 98%;
57-
--destructive: 0 62.8% 30.6%;
58-
--destructive-foreground: 0 0% 98%;
59-
--border: 0 0% 14.9%;
60-
--input: 0 0% 14.9%;
61-
--ring: 0 0% 83.1%;
62-
--chart-1: 220 70% 50%;
63-
--chart-2: 160 60% 45%;
64-
--chart-3: 30 80% 55%;
65-
--chart-4: 280 65% 60%;
66-
--chart-5: 340 75% 55%;
67-
--sidebar-background: 240 5.9% 10%;
68-
--sidebar-foreground: 240 4.8% 95.9%;
69-
--sidebar-primary: 224.3 76.3% 48%;
70-
--sidebar-primary-foreground: 0 0% 100%;
71-
--sidebar-accent: 240 3.7% 15.9%;
72-
--sidebar-accent-foreground: 240 4.8% 95.9%;
73-
--sidebar-border: 240 3.7% 15.9%;
74-
--sidebar-ring: 217.2 91.2% 59.8%
75-
}
42+
.dark {
43+
--background: 0 0% 3.9%;
44+
--foreground: 0 0% 98%;
45+
--card: 0 0% 3.9%;
46+
--card-foreground: 0 0% 98%;
47+
--popover: 0 0% 3.9%;
48+
--popover-foreground: 0 0% 98%;
49+
--primary: 0 0% 98%;
50+
--primary-foreground: 0 0% 9%;
51+
--secondary: 0 0% 14.9%;
52+
--secondary-foreground: 0 0% 98%;
53+
--muted: 0 0% 14.9%;
54+
--muted-foreground: 0 0% 63.9%;
55+
--accent: 0 0% 14.9%;
56+
--accent-foreground: 0 0% 98%;
57+
--destructive: 0 62.8% 30.6%;
58+
--destructive-foreground: 0 0% 98%;
59+
--border: 0 0% 14.9%;
60+
--input: 0 0% 14.9%;
61+
--ring: 0 0% 83.1%;
62+
--chart-1: 220 70% 50%;
63+
--chart-2: 160 60% 45%;
64+
--chart-3: 30 80% 55%;
65+
--chart-4: 280 65% 60%;
66+
--chart-5: 340 75% 55%;
67+
--sidebar-background: 240 5.9% 10%;
68+
--sidebar-foreground: 240 4.8% 95.9%;
69+
--sidebar-primary: 224.3 76.3% 48%;
70+
--sidebar-primary-foreground: 0 0% 100%;
71+
--sidebar-accent: 240 3.7% 15.9%;
72+
--sidebar-accent-foreground: 240 4.8% 95.9%;
73+
--sidebar-border: 240 3.7% 15.9%;
74+
--sidebar-ring: 217.2 91.2% 59.8%;
75+
}
7676
}
7777

7878
@layer base {
79-
* {
80-
@apply border-border;
81-
}
79+
* {
80+
@apply border-border;
81+
}
8282

83-
body {
84-
@apply bg-background text-foreground;
85-
}
83+
body {
84+
@apply bg-background text-foreground;
85+
}
8686
}

aidbox-forms-smart-launch-2/src/app/layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ export default function RootLayout({
1717
<html lang="en">
1818
<head>
1919
<script
20-
src="https://form-builder.aidbox.app/static/aidbox-forms-renderer-webcomponent.js"
20+
src={`${process.env.NEXT_PUBLIC_FORMS_WEBCOMPONENT_BASE_URL || "https://form-builder.aidbox.app"}/static/aidbox-forms-renderer-webcomponent.js`}
2121
async
2222
></script>
2323
<script
24-
src="https://form-builder.aidbox.app/static/aidbox-forms-builder-webcomponent.js"
24+
src={`${process.env.NEXT_PUBLIC_FORMS_WEBCOMPONENT_BASE_URL || "https://form-builder.aidbox.app"}/static/aidbox-forms-builder-webcomponent.js`}
2525
async
2626
></script>
2727
</head>

aidbox-forms-smart-launch-2/src/components/forms-builder.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ export function FormsBuilder({
1818

1919
if (current) {
2020
const handler = (e: Event) => {
21-
onChange?.((e as CustomEvent<Questionnaire>).detail);
21+
const questionnaire = (e as CustomEvent<Questionnaire>).detail;
22+
if (onChange && questionnaire) {
23+
onChange(questionnaire);
24+
}
2225
};
2326

2427
current.addEventListener("change", handler);

0 commit comments

Comments
 (0)