Skip to content

Commit 4e5cb4d

Browse files
FEAT: added the print button and ability to print referrals (#28)
1 parent c4b3726 commit 4e5cb4d

4 files changed

Lines changed: 379 additions & 90 deletions

File tree

Lines changed: 126 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,128 +1,164 @@
11
"use client";
2-
import { Button } from "@/components/ui/button";
3-
import { Textarea } from "@/components/ui/textarea";
4-
52
import { useEffect, useState } from "react";
6-
import { Sparkles } from "lucide-react";
3+
import Link from "next/link";
4+
import { Printer, Sparkles } from "lucide-react";
75

8-
import "@/app/globals.css";
6+
import { Button } from "@/components/ui/button";
7+
import { Textarea } from "@/components/ui/textarea";
98
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
10-
import Link from "next/link";
119
import { Label } from "@/components/ui/label";
10+
1211
import { fetchResources } from "@/util/fetchResources";
1312
import { Resource } from "@/types/resources";
13+
import "@/app/globals.css";
14+
15+
import { PrintableReferralsReport } from "@/util/printReferrals";
1416

1517
export default function Page() {
1618
const [clientDescription, setClientDescription] = useState("");
1719
const [result, setResult] = useState<Resource[] | null>(null);
1820
const [loading, setLoading] = useState(false);
21+
const [readyToPrint, setReadyToPrint] = useState(false);
1922

2023
useEffect(() => {
2124
if (result === null) return;
25+
// optional side effects on result change
2226
}, [result]);
2327

2428
async function handleClick() {
2529
setLoading(true);
2630
setResult(null);
27-
2831
try {
29-
const resources = await fetchResources(clientDescription);
32+
const resources = await fetchResources(clientDescription); // returns Resource[]
3033
setResult(resources);
34+
setReadyToPrint(true);
3135
} catch (e: unknown) {
3236
const message = e instanceof Error ? e.message : "Unknown error";
3337
console.error(message);
34-
setResult([]); // or keep `null` if you prefer to hide the results area
38+
setResult([]); // or keep null to hide area
3539
} finally {
3640
setLoading(false);
3741
}
3842
}
3943

44+
function handlePrint() {
45+
// App chrome will hide; print-only section will show
46+
window.print();
47+
}
48+
4049
return (
4150
<>
42-
<div className="flex flex-col gap-2 pl-20 pr-20 mt-8 mb-8">
43-
<div className="m-3">
44-
<Label
45-
className="font-medium text-gray-900 text-lg mb-1"
46-
htmlFor="clientDescriptionInput"
47-
>
48-
Tell us about your client
49-
</Label>
50-
<Textarea
51-
placeholder="Add details about the client's specific situation, needs, and circumstances here..."
52-
id="clientDescriptionInput"
53-
value={clientDescription}
54-
onChange={(e) => setClientDescription(e.target.value)}
55-
className="min-h-[8rem] min-w-[16rem] text-base border-gray-300 focus:ring-blue-500 focus:border-blue-500"
56-
/>
57-
</div>
58-
<Button
59-
type="button"
60-
onClick={() => void handleClick()}
61-
disabled={!clientDescription.trim() || loading}
62-
className="min-w-[16rem] ml-3 mr-3 generate-referrals-button text-lg pt-6 pb-6"
63-
>
64-
<Sparkles className="w-5 h-5" />
65-
{loading ? "Generating Resources..." : "Find Resources"}
66-
</Button>
67-
{displayResourcesFromResult(result)}
68-
</div>
69-
</>
70-
);
71-
}
51+
{/* ----- App chrome (hidden when printing) ----- */}
52+
<div className="print:hidden">
53+
{readyToPrint && (
54+
<div className="space-y-6">
55+
<div className="flex items-center gap-2 justify-end pr-20 pt-4">
56+
<Button
57+
onClick={handlePrint}
58+
variant="outline"
59+
className="hover:bg-gray-100 hover:text-gray-900"
60+
>
61+
<Printer
62+
data-testid="printReferralsButton"
63+
className="w-4 h-4 mr-2"
64+
/>
65+
Print Referrals
66+
</Button>
67+
</div>
68+
</div>
69+
)}
7270

73-
function displayResourcesFromResult(result: Resource[] | null) {
74-
if (result == null) return null;
75-
if (result.length === 0)
76-
return <div className="margin-top-2">No resources found.</div>;
71+
<div className="flex flex-col gap-2 pl-20 pr-20 mt-8 mb-8">
72+
<div className="m-3">
73+
<Label
74+
className="font-medium text-gray-900 text-lg mb-1"
75+
htmlFor="clientDescriptionInput"
76+
>
77+
Tell us about your client
78+
</Label>
79+
<Textarea
80+
placeholder="Add details about the client's specific situation, needs, and circumstances here..."
81+
id="clientDescriptionInput"
82+
value={clientDescription}
83+
onChange={(e) => setClientDescription(e.target.value)}
84+
className="min-h-[8rem] min-w-[16rem] text-base"
85+
data-testid="clientDescriptionInput"
86+
/>
87+
</div>
7788

78-
return (
79-
<div className="m-3">
80-
{result.map((r, i) => (
81-
<Card key={i} className="bg-white shadow-sm mb-5 min-w-[16rem]">
82-
<CardHeader>
83-
<CardTitle className="text-xl font-semibold text-gray-900 flex items-center gap-2">
84-
<span className="flex-shrink-0 w-7 h-7 bg-blue-600 text-white rounded-full flex items-center justify-center text-m font-medium">
85-
{i + 1}
86-
</span>
87-
{r.name}
88-
<Link
89-
href={r.website}
90-
rel="noopener noreferrer"
91-
className="text-base text-gray-500 flex items-center gap-2"
92-
>
93-
{r.website}
94-
</Link>
95-
</CardTitle>
96-
</CardHeader>
97-
<CardContent className="ml-4 mr-4">
98-
{r.description && (
99-
<div className="text-bold mb-2">{r.description}</div>
100-
)}
89+
<Button
90+
type="button"
91+
onClick={() => void handleClick()}
92+
disabled={!clientDescription.trim() || loading}
93+
className="min-w-[16rem] ml-3 mr-3 generate-referrals-button text-lg pt-6 pb-6"
94+
data-testid="findResourcesButton"
95+
>
96+
<Sparkles className="w-5 h-5" />
97+
{loading ? "Generating Resources..." : "Find Resources"}
98+
</Button>
10199

102-
{r.addresses?.length > 0 && (
103-
<div className="mt-1">
104-
<span className="font-semibold">
105-
Address{r.addresses.length > 1 ? "es" : ""}:
106-
</span>{" "}
107-
{r.addresses.join(" | ")}
108-
</div>
109-
)}
100+
{/* On-screen results (normal cards) */}
101+
{result && result.length === 0 && (
102+
<div className="m-3">No resources found.</div>
103+
)}
104+
{result && result.length > 0 && (
105+
<div className="m-3">
106+
{result.map((r, i) => (
107+
<Card key={i} className="bg-white shadow-sm mb-5 min-w-[16rem]">
108+
<CardHeader>
109+
<CardTitle className="text-xl font-semibold text-gray-900 flex items-center gap-2">
110+
<span className="flex-shrink-0 w-7 h-7 bg-blue-600 text-white rounded-full flex items-center justify-center text-m font-medium">
111+
{i + 1}
112+
</span>
113+
{r.name}
114+
{!!r.website && (
115+
<Link
116+
href={r.website}
117+
rel="noopener noreferrer"
118+
target="_blank"
119+
className="text-base text-gray-500 flex items-center gap-2"
120+
>
121+
{r.website}
122+
</Link>
123+
)}
124+
</CardTitle>
125+
</CardHeader>
126+
<CardContent className="ml-4 mr-4">
127+
{r.description && (
128+
<div className="font-medium mb-2">{r.description}</div>
129+
)}
130+
{r.addresses?.length > 0 && (
131+
<div className="mt-1">
132+
<span className="font-semibold">
133+
Address{r.addresses.length > 1 ? "es" : ""}:
134+
</span>{" "}
135+
{r.addresses.join(" | ")}
136+
</div>
137+
)}
138+
{r.phones?.length > 0 && (
139+
<div className="mt-1">
140+
<span className="font-semibold">Phone:</span>{" "}
141+
{r.phones.join(" | ")}
142+
</div>
143+
)}
144+
{r.emails?.length > 0 && (
145+
<div className="mt-1">
146+
<span className="font-semibold">Email:</span>{" "}
147+
{r.emails.join(" | ")}
148+
</div>
149+
)}
150+
</CardContent>
151+
</Card>
152+
))}
153+
</div>
154+
)}
155+
</div>
156+
</div>
110157

111-
{r.phones?.length > 0 && (
112-
<div className="mt-1">
113-
<span className="font-semibold">Phone:</span>{" "}
114-
{r.phones.join(" | ")}
115-
</div>
116-
)}
117-
{r.emails?.length > 0 && (
118-
<div className="mt-1">
119-
<span className="font-semibold">Email:</span>{" "}
120-
{r.emails.join(" | ")}
121-
</div>
122-
)}
123-
</CardContent>
124-
</Card>
125-
))}
126-
</div>
158+
{/* ----- Print-only section ----- */}
159+
<div className="hidden print:block">
160+
<PrintableReferralsReport resources={result ?? []} />
161+
</div>
162+
</>
127163
);
128164
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { Resource } from "@/types/resources";
2+
3+
export function PrintableReferralsReport({
4+
resources,
5+
}: {
6+
resources: Resource[];
7+
}) {
8+
const date = new Date();
9+
return (
10+
<div className="mx-auto max-w-[800px] p-5 text-[14px] leading-relaxed text-slate-800">
11+
<div className="text-center border-b-2 border-blue-600 pb-4 mb-5">
12+
<h1 className="text-[24px] font-bold text-blue-600 m-0">
13+
Goodwill Central Texas
14+
</h1>
15+
<p className="m-0">GenAI Referral Tool - Client Referral Report</p>
16+
<p className="m-0">
17+
Generated on {date.toLocaleDateString()} at{" "}
18+
{date.toLocaleTimeString()}
19+
</p>
20+
</div>
21+
22+
{/* Summary / resources */}
23+
{resources.length === 0 ? (
24+
<div>No resources found.</div>
25+
) : (
26+
<div>
27+
{resources.map((r, i) => (
28+
<div
29+
key={i}
30+
className="mb-4 p-3 border border-slate-200 rounded"
31+
style={{ breakInside: "avoid" }}
32+
>
33+
<div className="flex items-start gap-2 mb-2">
34+
<div className="bg-blue-600 text-white w-6 h-6 rounded-full flex items-center justify-center text-[14px] font-bold">
35+
{i + 1}
36+
</div>
37+
<div className="font-bold text-slate-900">{r.name}</div>
38+
</div>
39+
40+
{r.description && (
41+
<div className="mb-2 text-slate-700">{r.description}</div>
42+
)}
43+
44+
{r.addresses?.length > 0 && (
45+
<div className="text-slate-700">
46+
<span className="font-semibold">
47+
Address{r.addresses.length > 1 ? "es" : ""}:
48+
</span>{" "}
49+
{r.addresses.join(" | ")}
50+
</div>
51+
)}
52+
{r.phones?.length > 0 && (
53+
<div className="text-slate-700">
54+
<span className="font-semibold">Phone:</span>{" "}
55+
{r.phones.join(" | ")}
56+
</div>
57+
)}
58+
{r.emails?.length > 0 && (
59+
<div className="text-slate-700">
60+
<span className="font-semibold">Email:</span>{" "}
61+
{r.emails.join(" | ")}
62+
</div>
63+
)}
64+
{!!r.website && (
65+
<div className="text-slate-700">
66+
<span className="font-semibold">Website:</span> {r.website}
67+
</div>
68+
)}
69+
</div>
70+
))}
71+
</div>
72+
)}
73+
74+
<div className="text-center text-slate-500 text-[12px] mt-5 pt-4 border-t border-slate-200">
75+
Report generated by Goodwill Central Texas GenAI Referral Tool
76+
</div>
77+
</div>
78+
);
79+
}

0 commit comments

Comments
 (0)