Skip to content

Commit

Permalink
Merge pull request #103 from bettersg/99_store_scheme
Browse files Browse the repository at this point in the history
99 store scheme
  • Loading branch information
rurumeister authored Dec 15, 2024
2 parents 121d375 + 98d9c6a commit 7e87313
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 58 deletions.
8 changes: 4 additions & 4 deletions frontend/src/app/providers.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { Scheme } from "@/components/schemes/schemes-list";
import { SearchResScheme } from "@/components/schemes/schemes-list";
import { NextUIProvider } from "@nextui-org/react";
import React, {
createContext,
Expand All @@ -21,16 +21,16 @@ type ChatContextType = {
setUserQuery: React.Dispatch<React.SetStateAction<string>>;
messages: Message[];
setMessages: React.Dispatch<React.SetStateAction<Message[]>>;
schemes: Scheme[];
setSchemes: React.Dispatch<React.SetStateAction<Scheme[]>>;
schemes: SearchResScheme[];
setSchemes: React.Dispatch<React.SetStateAction<SearchResScheme[]>>;
};

const ChatContext = createContext<ChatContextType | undefined>(undefined);

export const ChatProvider = ({ children }: { children: ReactNode }) => {
const [messages, setMessages] = useState<Message[]>([]);
const [userQuery, setUserQuery] = useState<string>("");
const [schemes, setSchemes] = useState<Scheme[]>([]);
const [schemes, setSchemes] = useState<SearchResScheme[]>([]);

useEffect(() => {
const storedSchemes = localStorage.getItem('schemes');
Expand Down
234 changes: 185 additions & 49 deletions frontend/src/app/schemes/[schemeId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,201 @@
'use client';

import { useChat } from "@/app/providers";
import { Chip, Divider, Image, Link, Spacer } from "@nextui-org/react";
import { useEffect, useState } from "react";
import { Chip, Divider, Image, Link, Skeleton, Spacer } from "@nextui-org/react";
import { useParams } from "next/navigation";
import classes from "./scheme.module.css"
import classes from "./scheme.module.css";
import { SearchResScheme } from "@/components/schemes/schemes-list";

// Type for full scheme properties
type Scheme = SearchResScheme & {
lastUpdated?: string;
eligibility?: {
criteria: string;
requiredDocuments: string[];
};
application?: {
process: string;
deadline: string;
formLink: string;
termsAndConditions: string;
};
contact?: {
phone: string;
email: string;
address: string;
website: string;
socialMedia?: {
facebook?: string;
instagram?: string;
linkedin?: string;
};
feedbackLink: string;
};
additionalInfo?: {
faqs?: { question: string; answer: string }[];
successStories?: { title: string; url: string }[];
relatedSchemes?: { id: string; scheme: string; agency: string; link: string }[];
additionalResources?: { title: string; url: string }[];
programmeDuration?: string;
languageOptions?: string[];
};
};

const mapToFullScheme = (rawData: any): Scheme => {
return {
// Properties from Scheme
schemeType: rawData["Scheme Type"] || "",
schemeName: rawData["Scheme"] || "",
targetAudience: rawData["Who's it for"] || "",
agency: rawData["Agency"] || "",
description: rawData["Description"] || "",
scrapedText: rawData["scraped_text"] || "",
benefits: rawData["What it gives"] || "",
link: rawData["Link"] || "",
image: rawData["Image"] || "",
searchBooster: rawData["search_booster(WL)"] || "",
schemeId: rawData["scheme_id"] || "",
query: rawData["query"] || "",
similarity: rawData["Similarity"] || 0,
quintile: rawData["Quintile"] || 0,

// Additional properties for FullScheme
lastUpdated: rawData["Last Updated"] || "",
eligibility: rawData["Eligibility"] || undefined,
application: rawData["Application"] || undefined,
contact: rawData["Contact"] || undefined,
additionalInfo: rawData["Additional Info"] || undefined,
};
};

export default function SchemePage() {
const { schemes } = useChat();
const { schemeId } = useParams();
const [scheme, setScheme] = useState<Scheme | null>(null);
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(true);

const getScheme = (schemeId: string) => {
const idx = schemes.findIndex(scheme => scheme.schemeId === schemeId);
if (idx >= 0) {
return schemes[idx]
} else {
console.error("No scheme found.")
useEffect(() => {
async function fetchScheme() {
if (!schemeId) return;

try {
const id = Array.isArray(schemeId) ? schemeId[0] : schemeId;
const response = await fetch(
`http://localhost:5001/schemessg-v3-dev/asia-southeast1/schemes/${id}`
);
if (!response.ok) {
throw new Error("Failed to fetch scheme");
}
const res = await response.json();
const schemeRes = mapToFullScheme(res.data);
setScheme(schemeRes);
} catch (err: any) {
console.log(err)
setError("An error occurred");
} finally {
setIsLoading(false);
}
}
// useParams returns string | string[]
const scheme = getScheme(Array.isArray(schemeId) ? schemeId[0] : schemeId);
}

const getSchemeTypes = (schemeTypeStr: string) => {
return schemeTypeStr.split(",");
fetchScheme();
}, [schemeId]);

if (error) {
return <p className="text-error">{error}</p>;
}

return (
scheme &&
<div className={classes.schemeContainer}>
<div>{getSchemeTypes(scheme.schemeType).map((schemeType: string) => <Chip color="primary" className={classes.schemeType}>{schemeType}</Chip>)}</div>
<div className={classes.schemeTitle}>
<Image
width={150}
height={150}
alt={`${scheme.agency} logo`}
radius="sm"
src={scheme.image}
/>
<div>
<p className="text-5xl font-bold">{scheme.schemeName}</p>
<p className="text-xl text-default-500">{scheme.agency}</p>
</div>
</div>
if (isLoading) {
return (
<div className="flex flex-col space-y-6 p-6">
{/* Skeleton loader for scheme type */}
<div>
<Skeleton className="h-3 w-12 rounded-md bg-gray-300" />
<Spacer y={1} />
<p className="text-base">{scheme.description}</p>
<Divider className="my-4" />
<div>
<p className="text-3xl font-bold">Target Audience</p>
<Spacer y={3} />
<p>{scheme.targetAudience}</p>
</div>
<Spacer y={6} />
<div>
<p className="text-3xl font-bold">Benefits</p>
<p>{scheme.benefits}</p>
</div>
<Spacer y={6} />
<div>
<p className="text-3xl font-bold">Contact</p>
<Link isExternal href={scheme.link}>{scheme.link}</Link>
</div>

{/* Skeleton loader for scheme title */}
<div className="flex items-center space-x-4">
<Skeleton className="h-36 w-36 rounded-full bg-gray-300" />
<div className="flex flex-col space-y-2">
<Skeleton className="h-8 w-3/5 rounded-md bg-gray-300" />
<Skeleton className="h-6 w-2/5 rounded-md bg-gray-300" />
</div>
</div>

<Spacer y={2} />

{/* Skeleton loader for description */}
<div className="flex flex-col space-y-3">
<Skeleton className="h-5 w-11/12 rounded-md bg-gray-300" />
<Skeleton className="h-5 w-9/12 rounded-md bg-gray-300" />
</div>
<Divider className="my-4" />

{/* Skeleton loader for other sections */}
<Skeleton className="h-8 w-1/2 rounded-md bg-gray-300" />
<Spacer y={1} />
<Skeleton className="h-5 w-3/4 rounded-md bg-gray-300" />
</div>
);
}

// Helper to extract scheme types
const getSchemeTypes = (schemeTypeStr: string) => schemeTypeStr.split(",");

return (scheme &&
<div className={classes.schemeContainer}>
<div>
{getSchemeTypes(scheme.schemeType).map((type) => (
<Chip key={type} color="primary" className={classes.schemeType}>
{type}
</Chip>
))}
</div>

<div className={classes.schemeTitle}>
<Image
width={150}
height={150}
alt={`${scheme.agency} logo`}
radius="sm"
src={scheme.image}
/>
<div>
<p className="text-5xl font-bold">{scheme.schemeName}</p>
<p className="text-xl text-default-500">{scheme.agency}</p>
</div>
</div>

<Spacer y={1} />

<p className="text-base">{scheme.description}</p>
<Divider className="my-4" />

<div>
<p className="text-3xl font-bold">Target Audience</p>
<Spacer y={3} />
<p>{scheme.targetAudience}</p>
</div>

<Spacer y={6} />

{/* Benefits */}
<div>
<p className="text-3xl font-bold">What It Gives</p>
<p>{scheme.benefits}</p>
</div>

<Spacer y={6} />

{scheme.contact && (
<div>
<p className="text-3xl font-bold">Contact</p>
<p>Phone: {scheme.contact.phone}</p>
<p>Email: {scheme.contact.email}</p>
<p>Website: <Link isExternal href={scheme.contact.website}>{scheme.contact.website}</Link></p>
<p>Address: {scheme.contact.address}</p>
</div>
);
)}
</div>
);
}
5 changes: 3 additions & 2 deletions frontend/src/components/schemes/schemes-list.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Card, CardHeader, CardBody, Image, Spacer } from "@nextui-org/react";
import { Link } from "@nextui-org/react";

export type Scheme = {
// Type for scheme from search results
export type SearchResScheme = {
schemeType: string,
schemeName: string,
targetAudience: string,
Expand All @@ -19,7 +20,7 @@ export type Scheme = {
}

interface SchemesListProps {
schemes: Scheme[]
schemes: SearchResScheme[]
}

export default function SchemesList({ schemes }: SchemesListProps) {
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/search-bar/search-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { SearchIcon } from '../../assets/icons/search-icon';
import classes from './search-bar.module.css';
import { useState } from "react";
import { useChat } from "@/app/providers";
import { Scheme } from "../schemes/schemes-list";
import { SearchResScheme } from "../schemes/schemes-list";

interface SearchBarProps {
setSessionId: (val: string) => void;
}

const mapToScheme = (rawData: any): Scheme => {
const mapToSearchResScheme = (rawData: any): SearchResScheme => {
return {
schemeType: rawData["Scheme Type"] || "",
schemeName: rawData["Scheme"] || "",
Expand Down Expand Up @@ -69,7 +69,7 @@ export default function SearchBar({ setSessionId }: SearchBarProps) {
const res = await response.json();
const sessionId: string = res["sessionID"];
setIsBotResponseGenerating(false);
const schemesRes: Scheme[] = res.data.map(mapToScheme);
const schemesRes: SearchResScheme[] = res.data.map(mapToSearchResScheme);
return { schemesRes, sessionId };
} catch (error) {
console.error("Error making POST request:", error);
Expand Down

0 comments on commit 7e87313

Please sign in to comment.