Untitled
unknown
plain_text
7 months ago
19 kB
5
Indexable
import { FC, useState, useEffect } from "react";
import { useSelector } from "react-redux";
import { FaEdit } from "react-icons/fa";
import {
Col,
Row,
Form,
FormGroup,
Label,
Input,
InputGroup,
InputGroupText,
Button,
} from "reactstrap";
import { RootState } from "@/Redux/Store";
interface LivingExpensesTabContentsProps {
updateField: (field: string, value: any) => void;
}
const LivingExpensesTabContents: FC<LivingExpensesTabContentsProps> = ({
updateField,
}) => {
const budgetPlannerData = useSelector(
(state: RootState) => state.budgetPlanner ?? {
current_living_cost: {},
post_living_cost: {},
current_insurance: {},
post_insurance: {},
}
);
const [visibleNotes, setVisibleNotes] = useState<{ [key: string]: boolean }>(
{}
);
const [currentValues, setCurrentValues] = useState<Record<string, string>>(
{}
);
const [postValues, setPostValues] = useState<Record<string, string>>({});
const livingCostFieldMappings = {
Electricity: "electricity",
Gas: "gas",
Water: "water",
"Landline/Mobile Phones": "landline_mobile_phone",
"TV Licence": "tv_license",
"Council Tax": "council_tax",
"Ground Rent & Service Charges": "ground_rent_service_charges",
"Buildings & Contents": "buildings_contents",
"Mortgage Payment Protection": "mortgage_payment_protection",
Endowment: "endowment",
"Pension Contribution": "pension_contribution",
Childcare: "childcare",
Maintenance: "maintenance",
Food: "food",
"Car Maintenance": "car_maintenance",
Fuel: "fuel",
"Public Transport": "public_transport",
"TV Broadband": "tv_broadband",
"Recreation/Holidays": "recreation_holidays",
Clothing: "clothing",
"Medical Expenses": "medical_expenses",
Education: "education",
"Other Living Costs": "other_living_costs",
};
const insuranceFieldMappings = {
"Motor Insurance": "motor_insurance",
"Health Insurance": "health_insurance",
"Payment Protection": "payment_protection",
"Life Insurance": "life_insurance",
"Dental Insurance": "dental_insurance",
"Other Insurance": "other_insurance",
};
// Initialize local state with Redux data
useEffect(() => {
if (!budgetPlannerData) return;
const initialCurrentValues: Record<string, string> = {};
const initialPostValues: Record<string, string> = {};
// Current Living Costs
Object.entries(livingCostFieldMappings).forEach(([field, key]) => {
const value =
budgetPlannerData?.current_living_cost?.[
key as keyof typeof budgetPlannerData.current_living_cost
] ?? 0;
initialCurrentValues[
`CurrentBudgetPlanner.${field.replace(/[\s/&]/g, "")}`
] = value !== 0 ? String(value) : "";
});
// Post Living Costs
Object.entries(livingCostFieldMappings).forEach(([field, key]) => {
const value =
budgetPlannerData?.post_living_cost?.[
key as keyof typeof budgetPlannerData.post_living_cost
] ?? 0;
initialPostValues[
`PostCompletionBudgetPlanner.${field.replace(/[\s/&]/g, "")}`
] = value !== 0 ? String(value) : "";
});
// Current Insurance
Object.entries(insuranceFieldMappings).forEach(([field, key]) => {
const value =
budgetPlannerData?.current_insurance?.[
key as keyof typeof budgetPlannerData.current_insurance
] ?? 0;
initialCurrentValues[
`CurrentBudgetPlanner.${field.replace(/[\s/&]/g, "")}`
] = value !== 0 ? String(value) : "";
});
// Post Insurance
Object.entries(insuranceFieldMappings).forEach(([field, key]) => {
const value =
budgetPlannerData?.post_insurance?.[
key as keyof typeof budgetPlannerData.post_insurance
] ?? 0;
initialPostValues[
`PostCompletionBudgetPlanner.${field.replace(/[\s/&]/g, "")}`
] = value !== 0 ? String(value) : "";
});
setCurrentValues((prev) => ({ ...prev, ...initialCurrentValues }));
setPostValues((prev) => ({ ...prev, ...initialPostValues }));
}, [budgetPlannerData]);
// Update parent modal with current values
useEffect(() => {
const formatValues = (
values: Record<string, string>,
mappings: Record<string, string>,
section: string
) => {
const formatted = Object.entries(values)
.filter(
([key]) =>
key.startsWith("CurrentBudgetPlanner") && !key.includes("_Notes")
)
.reduce((acc, [key, value]) => {
const fieldName = key.split(".")[1];
const reduxFieldName = mappings[fieldName as keyof typeof mappings];
if (reduxFieldName) {
acc[reduxFieldName] = value === "" ? 0 : parseFloat(value);
}
return acc;
}, {} as Record<string, number | 0>);
formatted.total_living_expenses =
parseFloat(
calculateTotal(values, Object.keys(mappings), "CurrentBudgetPlanner")
) || 0;
updateField(section, formatted);
};
formatValues(currentValues, livingCostFieldMappings, "current_living_cost");
formatValues(currentValues, insuranceFieldMappings, "current_insurance");
}, [currentValues, updateField]);
// Update parent modal with post values
useEffect(() => {
const formatValues = (
values: Record<string, string>,
mappings: Record<string, string>,
section: string
) => {
const formatted = Object.entries(values)
.filter(
([key]) =>
key.startsWith("PostCompletionBudgetPlanner") &&
!key.includes("_Notes")
)
.reduce((acc, [key, value]) => {
const fieldName = key.split(".")[1];
const reduxFieldName = mappings[fieldName as keyof typeof mappings];
if (reduxFieldName) {
acc[reduxFieldName] = value === "" ? 0 : parseFloat(value);
}
return acc;
}, {} as Record<string, number | 0>);
formatted.total_living_expenses =
parseFloat(
calculateTotal(
values,
Object.keys(mappings),
"PostCompletionBudgetPlanner"
)
) || 0;
updateField(section, formatted);
};
formatValues(postValues, livingCostFieldMappings, "post_living_cost");
formatValues(postValues, insuranceFieldMappings, "post_insurance");
}, [postValues, updateField]);
const toggleNotes = (event: React.MouseEvent, noteId: string) => {
event.preventDefault();
setVisibleNotes((prev) => ({ ...prev, [noteId]: !prev[noteId] }));
};
const renderFields = (
prefix: string,
fields: string[],
mappings: Record<string, string>
) => (
<Form>
{fields.map((field) => {
const id = field.replace(/[\s/&]/g, "");
const fieldName = `${prefix}.${id}`;
const reduxFieldName = mappings[field];
return (
<div key={id}>
<FormGroup row className="mb-2">
<Label
for={`${prefix}_${reduxFieldName}`}
sm={6}
style={{ fontSize: "0.9rem" }}
>
{field}
</Label>
<Col sm={6}>
<InputGroup>
<InputGroupText>£</InputGroupText>
<Input
type="number"
name={fieldName}
id={`${prefix}_${reduxFieldName}`}
className="numeric-decimal living-cost"
placeholder="0.00"
step="0.01"
value={
prefix === "CurrentBudgetPlanner"
? currentValues[fieldName] || ""
: postValues[fieldName] || ""
}
onChange={(e) => {
const newValues =
prefix === "CurrentBudgetPlanner"
? { ...currentValues, [fieldName]: e.target.value }
: { ...postValues, [fieldName]: e.target.value };
prefix === "CurrentBudgetPlanner"
? setCurrentValues(newValues)
: setPostValues(newValues);
}}
/>
<InputGroupText
className="penNoteIcon_Holder"
onClick={(e) =>
toggleNotes(
e,
`${
prefix === "CurrentBudgetPlanner" ? "" : "Post_"
}${reduxFieldName}_Notes`
)
}
style={{ cursor: "pointer" }}
>
<FaEdit />
</InputGroupText>
</InputGroup>
</Col>
</FormGroup>
<FormGroup
className={`${reduxFieldName}_Notes_Holder mb-2`}
style={{
display: visibleNotes[
`${
prefix === "CurrentBudgetPlanner" ? "" : "Post_"
}${reduxFieldName}_Notes`
]
? "block"
: "none",
}}
>
<Label
for={`${prefix}_${reduxFieldName}_Notes`}
style={{ fontSize: "0.9rem" }}
>
Notes
</Label>
<Input
type="textarea"
name={`${prefix}.${reduxFieldName}_Notes`}
id={`${prefix}_${reduxFieldName}_Notes`}
className="textAreaRestrictions form-control"
value={
prefix === "CurrentBudgetPlanner"
? currentValues[`${prefix}.${reduxFieldName}_Notes`] || ""
: postValues[`${prefix}.${reduxFieldName}_Notes`] || ""
}
onChange={(e) => {
const newValues =
prefix === "CurrentBudgetPlanner"
? {
...currentValues,
[`${prefix}.${reduxFieldName}_Notes`]: e.target.value,
}
: {
...postValues,
[`${prefix}.${reduxFieldName}_Notes`]: e.target.value,
};
prefix === "CurrentBudgetPlanner"
? setCurrentValues(newValues)
: setPostValues(newValues);
}}
/>
</FormGroup>
</div>
);
})}
</Form>
);
const calculateTotal = (
values: Record<string, string>,
fields: string[],
prefix: string
) => {
return fields
.reduce((sum, field) => {
const fieldName = `${prefix}.${field.replace(/[\s/&]/g, "")}`;
return sum + (parseFloat(values[fieldName]) || 0);
}, 0)
.toFixed(2);
};
const handleCopyFromCurrent = () => {
const newPostValues: Record<string, string> = {};
Object.keys(currentValues).forEach((key) => {
const newKey = key.replace(
"CurrentBudgetPlanner",
"PostCompletionBudgetPlanner"
);
newPostValues[newKey] = currentValues[key];
});
setPostValues(newPostValues);
};
const renderSection = (
title: string,
prefix: string,
fields?: string[],
mappings?: Record<string, string>,
isTotal?: boolean
) => (
<div className="col-md-6">
{!isTotal && <h4 className="text-center mb-3">{title}</h4>}
<div
className={`border rounded-3 shadow-sm ${
isTotal ? "no-padding-vr no-border" : ""
}`}
>
{!isTotal && (
<div
className={`bg-light border-bottom p-3 ${
title === "Post Completion"
? "d-flex justify-content-between align-items-center"
: ""
}`}
>
<span className="fw-bold text-primary">
{fields && Object.keys(insuranceFieldMappings).includes(fields[0])
? "Insurances"
: "Living Costs"}
</span>
{title === "Post Completion" && (
<Button color="primary" size="sm" onClick={handleCopyFromCurrent}>
Copy from Current
</Button>
)}
</div>
)}
<div className={`p-3 ${isTotal ? "no-padding-vr no-border" : ""}`}>
{isTotal ? (
<FormGroup row className="mb-2">
<Label
for={`${prefix}_TotalHome`}
sm={6}
style={{ fontSize: "0.9rem" }}
>
Total Living Expenses
</Label>
<Col sm={6}>
<InputGroup>
<InputGroupText>£</InputGroupText>
<Input
type="number"
name={`${prefix}.TotalHome`}
id={`${prefix}_TotalHome`}
className="numeric-decimal fw-bold"
readOnly
placeholder="0.00"
step="0.01"
value={
prefix === "CurrentBudgetPlanner"
? (
parseFloat(
calculateTotal(
currentValues,
Object.keys(livingCostFieldMappings),
prefix
)
) +
parseFloat(
calculateTotal(
currentValues,
Object.keys(insuranceFieldMappings),
prefix
)
)
).toFixed(2)
: (
parseFloat(
calculateTotal(
postValues,
Object.keys(livingCostFieldMappings),
prefix
)
) +
parseFloat(
calculateTotal(
postValues,
Object.keys(insuranceFieldMappings),
prefix
)
)
).toFixed(2)
}
/>
<InputGroupText
className="penNoteIcon_Holder"
onClick={(e) =>
toggleNotes(
e,
`${
prefix === "CurrentBudgetPlanner" ? "" : "Post_"
}TotalHome_Notes`
)
}
style={{ cursor: "pointer" }}
>
<FaEdit />
</InputGroupText>
</InputGroup>
</Col>
</FormGroup>
) : (
renderFields(prefix, fields!, mappings!)
)}
{isTotal && (
<FormGroup
className="TotalHome_Notes_Holder mb-2"
style={{
display: visibleNotes[
`${
prefix === "CurrentBudgetPlanner" ? "" : "Post_"
}TotalHome_Notes`
]
? "block"
: "none",
}}
>
<Label
for={`${prefix}_TotalHome_Notes`}
style={{ fontSize: "0.9rem" }}
>
Notes
</Label>
<Input
type="textarea"
name={`${prefix}.TotalHome_Notes`}
id={`${prefix}_TotalHome_Notes`}
className="textAreaRestrictions form-control"
value={
prefix === "CurrentBudgetPlanner"
? currentValues[`${prefix}.TotalHome_Notes`] || ""
: postValues[`${prefix}.TotalHome_Notes`] || ""
}
onChange={(e) => {
const newValues =
prefix === "CurrentBudgetPlanner"
? {
...currentValues,
[`${prefix}.TotalHome_Notes`]: e.target.value,
}
: {
...postValues,
[`${prefix}.TotalHome_Notes`]: e.target.value,
};
prefix === "CurrentBudgetPlanner"
? setCurrentValues(newValues)
: setPostValues(newValues);
}}
/>
</FormGroup>
)}
</div>
</div>
</div>
);
return (
<div>
<p>
<small>
Please enter all monthly living costs accurately. Convert non-monthly
expenses: multiply weekly by 4.3, divide quarterly by 3, annual by 12.
</small>
</p>
<Row>
{renderSection(
"Current",
"CurrentBudgetPlanner",
Object.keys(livingCostFieldMappings),
livingCostFieldMappings
)}
{renderSection(
"Post Completion",
"PostCompletionBudgetPlanner",
Object.keys(livingCostFieldMappings),
livingCostFieldMappings
)}
</Row>
<Row className="mt-4">
{renderSection(
"Current",
"CurrentBudgetPlanner",
Object.keys(insuranceFieldMappings),
insuranceFieldMappings
)}
{renderSection(
"Post Completion",
"PostCompletionBudgetPlanner",
Object.keys(insuranceFieldMappings),
insuranceFieldMappings
)}
</Row>
<Row className="mt-4">
{renderSection("", "CurrentBudgetPlanner", [], {}, true)}
{renderSection("", "PostCompletionBudgetPlanner", [], {}, true)}
</Row>
</div>
);
};
export default LivingExpensesTabContents;
Editor is loading...
Leave a Comment