mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-30 12:55:02 +00:00
Merge pull request #27 from Hestia-Homes/main
Dev deployment with new pieces of ui for valuation improvement
This commit is contained in:
commit
d781671e97
15 changed files with 477 additions and 19 deletions
|
|
@ -29,6 +29,7 @@ export async function GET(
|
|||
currentEpcRating: true,
|
||||
currentSapPoints: true,
|
||||
updatedAt: true,
|
||||
currentValuation: true,
|
||||
},
|
||||
where: eq(property.id, BigInt(propertyId)),
|
||||
with: {
|
||||
|
|
|
|||
|
|
@ -26,19 +26,25 @@ export function BrandButton({
|
|||
}: {
|
||||
label: string;
|
||||
onClick: Dispatch<SetStateAction<any>>;
|
||||
backgroundColor: "brandblue" | "brandgold"; // Restrict backgroundColor to these two options
|
||||
backgroundColor:
|
||||
| "brandblue"
|
||||
| "brandgold"
|
||||
| "brandmidblue"
|
||||
| "brandlightblue"; // Restrict backgroundColor to these two options
|
||||
}) {
|
||||
// Dictionary to map background colors to hover colors
|
||||
const hoverColors = {
|
||||
brandblue: "hover:bg-hoverblue",
|
||||
brandgold: "hover:bg-hovergold",
|
||||
brandmidblue: "hover:bg-hoverblue",
|
||||
brandlightblue: "hover:bg-brandmidblue",
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={`inline-flex justify-center rounded-md border border-transparent px-4 py-2 text-sm font-medium text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2
|
||||
${backgroundColor === "brandblue" ? "bg-brandblue" : "bg-brandgold"}
|
||||
bg-${backgroundColor}
|
||||
${hoverColors[backgroundColor]}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ const TitleMap = {
|
|||
internal_wall_insulation: "Internal Wall Insulation",
|
||||
external_wall_insulation: "External Wall Insulation",
|
||||
cavity_wall_insulation: "Cavity Wall Insulation",
|
||||
extension_cavity_wall_insulation: "Extension Cavity Wall Insulation",
|
||||
// Roof
|
||||
loft_insulation: "Loft Insulation",
|
||||
room_roof_insulation: "Room Roof Insulation",
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@
|
|||
import {
|
||||
Recommendation,
|
||||
RecommendationType,
|
||||
Plan,
|
||||
} from "@/app/db/schema/recommendations";
|
||||
import RecommendationCard from "./RecommendationCard";
|
||||
import WorksPackageCard from "./WorksPackageCard";
|
||||
import ValuationImpactComponent from "./ValuationImpactComponent";
|
||||
import { Separator } from "@/app/shadcn_components/ui/separator";
|
||||
import { PropertyMeta } from "@/app/db/schema/property";
|
||||
import { sapToEpc } from "@/app/utils";
|
||||
|
|
@ -20,6 +22,7 @@ import {
|
|||
interface RecommendationContainerProps {
|
||||
recommendations: Recommendation[];
|
||||
propertyMeta: PropertyMeta;
|
||||
planMeta: Plan;
|
||||
}
|
||||
|
||||
const typeToCategoryMap: { [key in RecommendationType]?: RecommendationType } =
|
||||
|
|
@ -27,6 +30,7 @@ const typeToCategoryMap: { [key in RecommendationType]?: RecommendationType } =
|
|||
internal_wall_insulation: "wall_insulation",
|
||||
external_wall_insulation: "wall_insulation",
|
||||
cavity_wall_insulation: "wall_insulation",
|
||||
extension_cavity_wall_insulation: "extension_cavity_wall_insulation",
|
||||
loft_insulation: "roof_insulation",
|
||||
room_roof_insulation: "roof_insulation",
|
||||
flat_roof_insulation: "roof_insulation",
|
||||
|
|
@ -49,6 +53,7 @@ const emptyImpactState = {
|
|||
export default function RecommendationContainer({
|
||||
recommendations,
|
||||
propertyMeta,
|
||||
planMeta,
|
||||
}: RecommendationContainerProps) {
|
||||
const categorizedRecommendations = recommendations.reduce((acc, curr) => {
|
||||
const typeKey = curr.type as RecommendationType;
|
||||
|
|
@ -336,6 +341,12 @@ export default function RecommendationContainer({
|
|||
totalEnergyCostSavings={totalEnergyCostSavings}
|
||||
totalKwhSavings={totalKwhSavings}
|
||||
/>
|
||||
|
||||
<ValuationImpactComponent
|
||||
currentValuation={propertyMeta.currentValuation}
|
||||
valuationIncreaseLowerBound={planMeta.valuationIncreaseLowerBound}
|
||||
valuationIncreaseUpperBound={planMeta.valuationIncreaseUpperBound}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Separator className="mb-4 bg-brandblue" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
"use client";
|
||||
|
||||
import { BrandButton } from "../Buttons";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function ValuationImpactComponent({
|
||||
currentValuation,
|
||||
valuationIncreaseLowerBound,
|
||||
valuationIncreaseUpperBound,
|
||||
}: {
|
||||
currentValuation: number | null;
|
||||
valuationIncreaseLowerBound: number | null;
|
||||
valuationIncreaseUpperBound: number | null;
|
||||
}) {
|
||||
const [fundingModalIsOpen, setFundingModalIsOpen] = useState(false);
|
||||
// If we have no current valuation, we return no component
|
||||
if (!currentValuation) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const lowerBoundValuation =
|
||||
currentValuation + (valuationIncreaseLowerBound ?? 0);
|
||||
const upperBoundValuation =
|
||||
currentValuation + (valuationIncreaseUpperBound ?? 0);
|
||||
|
||||
function openFundingModal() {
|
||||
setFundingModalIsOpen(true);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="col-span-1 md:col-span-2 lg:col-span-3 w-full p-4 bg-brandblue rounded-lg shadow-md grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<span className="text-gray-100 text-lg">Current Value</span>
|
||||
<span className="text-3xl font-bold text-brandgold mt-1">
|
||||
£{currentValuation.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<span className="text-gray-100 text-lg">After Retrofit Valuation</span>
|
||||
<div className="text-2xl text-brandgold mt-1">
|
||||
£{lowerBoundValuation.toLocaleString()} - £
|
||||
{upperBoundValuation.toLocaleString()}
|
||||
</div>
|
||||
<span className="text-sm text-gray-100 mt-1">
|
||||
Estimated improvement: £
|
||||
{valuationIncreaseLowerBound?.toLocaleString()} - £
|
||||
{valuationIncreaseUpperBound?.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<span className="text-gray-100 text-lg mb-2">Funding Options</span>
|
||||
<BrandButton
|
||||
label="See More"
|
||||
onClick={openFundingModal}
|
||||
backgroundColor="brandlightblue"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import {
|
||||
Cog6ToothIcon,
|
||||
CalculatorIcon,
|
||||
BuildingOfficeIcon,
|
||||
ChartBarIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
|
|
@ -29,11 +28,7 @@ export function Toolbar({ portfolioId }: ToolbarProps) {
|
|||
const router = useRouter();
|
||||
|
||||
function handleClickSettings() {
|
||||
console.log("Settings were clicked, implement me");
|
||||
}
|
||||
|
||||
function handleClickPortfolioPlan() {
|
||||
router.push(`/portfolio/${portfolioId}/plan`);
|
||||
router.push(`/portfolio/${portfolioId}/settings`);
|
||||
}
|
||||
|
||||
function handleClickPortfolio() {
|
||||
|
|
@ -65,14 +60,6 @@ export function Toolbar({ portfolioId }: ToolbarProps) {
|
|||
Summary
|
||||
</NavigationMenuItem>
|
||||
|
||||
{/* <NavigationMenuItem
|
||||
className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
|
||||
onClick={handleClickPortfolioPlan}
|
||||
>
|
||||
<CalculatorIcon className="h-4 w-4 mr-2" />
|
||||
Portfolio Plan
|
||||
</NavigationMenuItem> */}
|
||||
|
||||
<NavigationMenuItem
|
||||
className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
|
||||
onClick={handleClickSettings}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ export interface PropertyMeta {
|
|||
currentEpcRating: string;
|
||||
currentSapPoints: number;
|
||||
updatedAt: string;
|
||||
currentValuation: number | null;
|
||||
detailsEpc: {
|
||||
currentEnergyDemand: number | null;
|
||||
co2Emissions: number | null;
|
||||
|
|
|
|||
|
|
@ -189,7 +189,8 @@ export type RecommendationType =
|
|||
| "cylinder_thermostat"
|
||||
| "trickle_vents"
|
||||
| "mixed_glazing"
|
||||
| "draught_proofing";
|
||||
| "draught_proofing"
|
||||
| "extension_cavity_wall_insulation";
|
||||
|
||||
export type UnnestedRecommendation = {
|
||||
quantity: number;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,283 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { PortfolioSettingsType } from "../../utils";
|
||||
import { Button } from "@/app/shadcn_components/ui/button";
|
||||
import { Input } from "@/app/shadcn_components/ui/input";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { handleNumericKeyDown } from "@/app/utils";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/app/shadcn_components/ui/select";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from "@/app/shadcn_components/ui/dialog";
|
||||
import { PortfolioStatus as PortfolioStatusOptions } from "@/app/db/schema/portfolio";
|
||||
import { PortfolioGoal as PortfolioGoalOptions } from "@/app/db/schema/portfolio";
|
||||
|
||||
// dropdown selection component for both goal and status
|
||||
|
||||
export function SettingsDropdown({
|
||||
startingValue,
|
||||
options,
|
||||
setOption,
|
||||
}: {
|
||||
startingValue: string;
|
||||
options: string[];
|
||||
setOption: (option: string) => void;
|
||||
}) {
|
||||
function handleValueChange(newValue: string) {
|
||||
setOption(newValue);
|
||||
}
|
||||
|
||||
return (
|
||||
<Select onValueChange={(newValue) => handleValueChange(newValue)}>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder={startingValue} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{options.map((option, idx) => (
|
||||
<SelectItem value={option} key={idx}>
|
||||
{option}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PortfolioSettings({
|
||||
portfolioId,
|
||||
portfolioSettingsData,
|
||||
}: {
|
||||
portfolioId: string;
|
||||
portfolioSettingsData: PortfolioSettingsType;
|
||||
}) {
|
||||
// Running in the client
|
||||
const router = useRouter();
|
||||
|
||||
// Set up state for portfolioName, portfolioBudget, portfolioGoal and portfolioStatus
|
||||
|
||||
// Syntax const [variable, function whos only job is to update the value of variable] = useState(initial value)
|
||||
const [portfolioName, setPortfolioName] = useState(
|
||||
portfolioSettingsData.name
|
||||
);
|
||||
|
||||
const [portfolioBudget, setPortfolioBudget] = useState<
|
||||
number | string | null
|
||||
>(portfolioSettingsData.budget);
|
||||
|
||||
const [portfolioGoal, setPortfolioGoal] = useState(
|
||||
portfolioSettingsData.goal
|
||||
);
|
||||
|
||||
const [portfolioStatus, setPortfolioStatus] = useState(
|
||||
portfolioSettingsData.status
|
||||
);
|
||||
|
||||
// Set up state for deleteModal and deleteConfirmation
|
||||
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
|
||||
const [deleteConfirmationByName, setDeleteConfirmationByName] = useState("");
|
||||
|
||||
function handleOpenDeleteModal() {
|
||||
setDeleteConfirmationByName("");
|
||||
setIsDeleteModalOpen(true);
|
||||
}
|
||||
|
||||
function handleDeleteConfirmation() {
|
||||
console.log("we be deletin stuff");
|
||||
// if (deleteConfirmationByName === portfolioName) {
|
||||
// //apiDeletePortfolio(portfolioId)
|
||||
// router.refresh();
|
||||
// setIsDeleteModalOpen(false);
|
||||
// } else {
|
||||
// // Error if the names don't match
|
||||
// console.log("Portfolio name does not match");
|
||||
// }
|
||||
router.push("/home");
|
||||
}
|
||||
|
||||
// RENAMING FUNCTIONS
|
||||
|
||||
// Change NAME functionality - changing state
|
||||
|
||||
function handlePortfolioNameChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
setPortfolioName(e.target.value);
|
||||
}
|
||||
|
||||
// The onClick function called to update the NAME in the DB
|
||||
|
||||
function handleRenameDb() {
|
||||
// apiRanameFunction(portfolioSettingsData.name)
|
||||
// Update portfolioName
|
||||
router.refresh();
|
||||
}
|
||||
|
||||
// BUDGET CHANGING FUNCTIONS
|
||||
|
||||
// Change BUDGET functionality - changing state
|
||||
|
||||
function handlePortfolioBudgetUpdate(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
setPortfolioBudget(Number(e.target.value));
|
||||
}
|
||||
|
||||
// The onClick function called to update the BUDGET in the DB
|
||||
|
||||
function handleBudgetUpdateDb() {
|
||||
// apiBudgetChangeFunction(portfolioSettingsData.budget)
|
||||
// Update portfolioBudget
|
||||
router.refresh();
|
||||
}
|
||||
|
||||
// CHANGING GOAL AND STATUS FUNCTIONALITY
|
||||
|
||||
// The onClick function called to update the GOAL in the DB
|
||||
|
||||
function handleGoalUpdateDb() {
|
||||
// apiGoalChangeFunction(portfolioSettingsData.goal)
|
||||
// Update portfolioGoal
|
||||
router.refresh();
|
||||
}
|
||||
|
||||
// The onClick function called to update the BUDGET in the DB
|
||||
|
||||
function handleStatusUpdateDb() {
|
||||
// apiStatusChangeFunction(portfolioSettingsData.status)
|
||||
// Update portfolioStatus
|
||||
router.refresh();
|
||||
}
|
||||
|
||||
// HTML to render the page
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="p-4 mt-5 flex justify-center max-w-8xl w-8xl pt-5 bg-gray-50 rounded-lg text-brandblue">
|
||||
<div className="grid grid-cols-[max-content_1fr_min-content] gap-x-[5px] gap-y-4">
|
||||
{/* Row 1: Name */}
|
||||
<div className="flex items-center">
|
||||
<span>Name:</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Input value={portfolioName} onChange={handlePortfolioNameChange} />
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Button className="w-full" onClick={handleRenameDb}>
|
||||
Rename
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Row 2: Budget */}
|
||||
<div className="flex items-center">
|
||||
<span>Budget:</span>
|
||||
</div>
|
||||
<div className="flex items-center max-w-8xl">
|
||||
<Input
|
||||
type="number"
|
||||
value={portfolioBudget ?? undefined}
|
||||
onChange={handlePortfolioBudgetUpdate}
|
||||
onKeyDown={(e) => handleNumericKeyDown(e)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Button className="w-full" onClick={handleBudgetUpdateDb}>
|
||||
Update
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Row 3: Goal */}
|
||||
<div className="flex items-center">
|
||||
<span>Goal:</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<SettingsDropdown
|
||||
startingValue={portfolioGoal}
|
||||
options={PortfolioGoalOptions}
|
||||
setOption={setPortfolioGoal}
|
||||
/>
|
||||
</div>
|
||||
<Button className="w-full" onClick={handleGoalUpdateDb}>
|
||||
Update
|
||||
</Button>
|
||||
|
||||
{/* Row 4: Status */}
|
||||
<div className="flex items-center">
|
||||
<span>Status:</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<SettingsDropdown
|
||||
startingValue={portfolioStatus}
|
||||
options={PortfolioStatusOptions}
|
||||
setOption={setPortfolioStatus}
|
||||
/>
|
||||
</div>
|
||||
<Button className="w-full" onClick={handleStatusUpdateDb}>
|
||||
Update
|
||||
</Button>
|
||||
|
||||
<div className="col-span-3"> Portfolio Name: {portfolioName}</div>
|
||||
<div className="col-span-3"> Portfolio Budget: {portfolioBudget}</div>
|
||||
<div className="col-span-3"> Goal value: {portfolioGoal}</div>
|
||||
<div> Status value: {portfolioStatus}</div>
|
||||
|
||||
{/* Row 5: Delete */}
|
||||
<div className="col-span-2"></div>
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
className="max-width: 100% bg-red-700"
|
||||
onClick={handleOpenDeleteModal}
|
||||
>
|
||||
Delete Portfolio
|
||||
</Button>
|
||||
</div>
|
||||
{/* Delete portfolio modal */}
|
||||
<Dialog open={isDeleteModalOpen} onOpenChange={setIsDeleteModalOpen}>
|
||||
<DialogContent>
|
||||
<DialogTitle>Are you sure?</DialogTitle>
|
||||
<p>
|
||||
To confirm, please type the name of the portfolio (
|
||||
<strong>{portfolioSettingsData.name}</strong>)
|
||||
</p>
|
||||
<input
|
||||
type="text"
|
||||
value={deleteConfirmationByName}
|
||||
onChange={(e) => setDeleteConfirmationByName(e.target.value)}
|
||||
placeholder="Type portfolio name"
|
||||
/>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
className="bg-green-600"
|
||||
onClick={() => setIsDeleteModalOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
className="bg-red-700"
|
||||
onClick={handleDeleteConfirmation}
|
||||
disabled={
|
||||
deleteConfirmationByName !== portfolioSettingsData.name
|
||||
}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
38
src/app/portfolio/[slug]/(portfolio)/settings/page.tsx
Normal file
38
src/app/portfolio/[slug]/(portfolio)/settings/page.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { getPortfolioSettings } from "../../utils";
|
||||
import PortfolioSettings from "./PortfolioSettings";
|
||||
|
||||
export default async function PortfolioSettingsPage({
|
||||
params,
|
||||
}: {
|
||||
params: { slug: string };
|
||||
}) {
|
||||
const portfolioId = params.slug;
|
||||
// fetch data securely on the server
|
||||
// Stef's page!!!!
|
||||
// 1) Rename
|
||||
// 2) Update budget, status, goal
|
||||
// 3) Delete - much harder
|
||||
|
||||
// fetch data in the server - name, budget, goal,
|
||||
// pass it to a client component to render and take user input
|
||||
|
||||
const portfolioSettingsData = await getPortfolioSettings(portfolioId);
|
||||
|
||||
// Get goal options and status options by importing something like this:
|
||||
// import {
|
||||
// PortfolioStatus,
|
||||
// PortfolioGoal,
|
||||
// } from "./../../db/schema/portfolio";
|
||||
// and then pass them to the portfoliosettings component
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex justify-center max-w-8xl w-8xl">
|
||||
<PortfolioSettings
|
||||
portfolioId={portfolioId}
|
||||
portfolioSettingsData={portfolioSettingsData}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import RecommendationContainer from "@/app/components/building-passport/RecommendationContainer";
|
||||
import { getPropertyMeta, getRecommendations } from "../../utils";
|
||||
import { getPropertyMeta, getRecommendations, getPlanMeta } from "../../utils";
|
||||
|
||||
export default async function Recommendations({
|
||||
params,
|
||||
|
|
@ -8,6 +8,7 @@ export default async function Recommendations({
|
|||
}) {
|
||||
const propertyMeta = await getPropertyMeta(params.propertyId);
|
||||
const recommendations = await getRecommendations(params.planId);
|
||||
const planMeta = await getPlanMeta(params.planId);
|
||||
|
||||
return (
|
||||
<div className="leading-loose tracking-wider">
|
||||
|
|
@ -15,6 +16,7 @@ export default async function Recommendations({
|
|||
<RecommendationContainer
|
||||
recommendations={recommendations}
|
||||
propertyMeta={propertyMeta}
|
||||
planMeta={planMeta}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import {
|
||||
Recommendation,
|
||||
planRecommendations,
|
||||
plan,
|
||||
Plan,
|
||||
} from "@/app/db/schema/recommendations";
|
||||
import { db } from "@/app/db/db";
|
||||
import {
|
||||
|
|
@ -14,7 +16,6 @@ import {
|
|||
NonIntrusiveSurveyData,
|
||||
nonInstrusiveSurvey,
|
||||
} from "@/app/db/schema/property";
|
||||
import { plan, Plan } from "@/app/db/schema/recommendations";
|
||||
import { getRating } from "@/app/utils";
|
||||
import { eq, desc } from "drizzle-orm";
|
||||
import {
|
||||
|
|
@ -85,6 +86,18 @@ export async function getRecommendations(
|
|||
return recommendations;
|
||||
}
|
||||
|
||||
export async function getPlanMeta(planId: string): Promise<Plan> {
|
||||
const data = await db.query.plan.findFirst({
|
||||
where: eq(plan.id, BigInt(planId)),
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
type PlanRelation = Plan & {
|
||||
planRecommendations: {
|
||||
recommendation: {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
import type {
|
||||
PortfolioStatus,
|
||||
PortfolioGoal,
|
||||
} from "./../../db/schema/portfolio";
|
||||
import { formatNumber } from "@/app/utils";
|
||||
import { and, eq, inArray } from "drizzle-orm";
|
||||
import { db } from "@/app/db/db";
|
||||
|
|
@ -15,6 +19,38 @@ import {
|
|||
ScenarioSelect,
|
||||
} from "@/app/db/schema/recommendations";
|
||||
|
||||
export interface PortfolioSettingsType {
|
||||
name: string;
|
||||
budget: number | null;
|
||||
goal: (typeof PortfolioGoal)[number];
|
||||
status: (typeof PortfolioStatus)[number];
|
||||
}
|
||||
|
||||
export async function getPortfolioSettings(
|
||||
portfolioId: string
|
||||
): Promise<PortfolioSettingsType> {
|
||||
//name, budget, goal, status
|
||||
const data = await db
|
||||
.select({
|
||||
name: portfolio.name,
|
||||
budget: portfolio.budget,
|
||||
goal: portfolio.goal,
|
||||
status: portfolio.status,
|
||||
})
|
||||
.from(portfolio)
|
||||
.where(eq(portfolio.id, BigInt(portfolioId)));
|
||||
|
||||
if (data.length === 0) {
|
||||
throw new Error("Portfolio not found");
|
||||
}
|
||||
|
||||
if (data.length > 1) {
|
||||
throw new Error("More than one portfolio found");
|
||||
}
|
||||
|
||||
return data[0];
|
||||
}
|
||||
|
||||
export async function getPortfolio(portfolioId: string): Promise<Portfolio> {
|
||||
const data = await db
|
||||
.select()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,16 @@
|
|||
import { Rating } from "./db/schema/property";
|
||||
import { KeyboardEvent} from "react";
|
||||
|
||||
export function handleNumericKeyDown(event: KeyboardEvent<HTMLInputElement>) {
|
||||
|
||||
/**
|
||||
* Allowing: Integers | Backspace | Tab | Delete | Left & Right arrow keys
|
||||
**/
|
||||
|
||||
const regex = new RegExp(/(^\d*$)|(Backspace|Tab|Delete|ArrowLeft|ArrowRight|ArrowUp|ArrowDown)/);
|
||||
|
||||
return !event.key.match(regex) && event.preventDefault();
|
||||
}
|
||||
|
||||
export function convertDaysToWorkingWeeks(days: number | null) {
|
||||
if (days === null) {
|
||||
|
|
@ -149,3 +161,5 @@ export function roundToDecimalPlaces(
|
|||
const factor = 10 ** decimalPlaces;
|
||||
return Math.round(number * factor) / factor;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ module.exports = {
|
|||
hovergold: "#c79d12",
|
||||
brandbrown: "#3d1e05",
|
||||
brandmidblue: "#3943b7",
|
||||
brandlightblue: "#00a9f4",
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
|
|
@ -145,6 +146,7 @@ module.exports = {
|
|||
hovertan: "#947750",
|
||||
brandbrown: "#3d1e05",
|
||||
brandmidblue: "#3943b7",
|
||||
brandlightblue: "#00a9f4",
|
||||
},
|
||||
borderRadius: {
|
||||
lg: `var(--radius)`,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue