Skip to content

Commit

Permalink
Merge pull request #8 from bcgov/feature/multi-scenario
Browse files Browse the repository at this point in the history
Feature/multi scenario
  • Loading branch information
brysonjbest authored Jul 4, 2024
2 parents 04cd00c + 4cd070d commit 59d57ad
Show file tree
Hide file tree
Showing 26 changed files with 1,545 additions and 78 deletions.
4 changes: 4 additions & 0 deletions app/components/InputOutputTable/InputOutputTable.module.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
.tableTitle {
display: flex;
gap: 20px;
justify-content: space-between;
align-items: center;
margin: 0;
background: #f9f9f9;
padding: 16px;
Expand Down
129 changes: 102 additions & 27 deletions app/components/InputOutputTable/InputOutputTable.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState, useEffect } from "react";
import { Table, Tag } from "antd";
import { useState, useEffect, FocusEvent } from "react";
import { Table, Tag, Input, Button } from "antd";
import { RuleMap } from "@/app/types/rulemap";
import styles from "./InputOutputTable.module.css";

const COLUMNS = [
Expand All @@ -15,54 +16,128 @@ const COLUMNS = [
},
];

const PROPERTIES_TO_IGNORE = ["submit", "lateEntry"];
const PROPERTIES_TO_IGNORE = ["submit", "lateEntry", "rulemap"];

interface rawDataProps {
[key: string]: any;
}

interface InputOutputTableProps {
title: string;
rawData: object;
rawData: rawDataProps | null | undefined;
setRawData?: (data: rawDataProps) => void;
submitButtonRef?: React.RefObject<HTMLButtonElement>;
editable?: boolean;
rulemap?: RuleMap;
}

export default function InputOutputTable({ title, rawData }: InputOutputTableProps) {
export default function InputOutputTable({
title,
rawData,
setRawData,
submitButtonRef,
editable = false,
rulemap,
}: InputOutputTableProps) {
const [dataSource, setDataSource] = useState<object[]>([]);
const [columns, setColumns] = useState(COLUMNS);
const [showTable, setShowTable] = useState(true);

const toggleTableVisibility = () => {
setShowTable(!showTable);
};

const convertAndStyleValue = (value: any, property: string, editable: boolean) => {
if (editable) {
return (
<label className="labelsmall">
<Input
defaultValue={value}
onBlur={(e) => handleValueChange(e, property)}
onKeyDown={(e) => handleKeyDown(e)}
/>
<span className="label-text">{property}</span>
</label>
);
}

const convertAndStyleValue = (value: any, property: string) => {
// Handle booleans
if (typeof value === "boolean") {
return value ? <Tag color="green">TRUE</Tag> : <Tag color="red">FALSE</Tag>;
}
// Handle money amounts

if (typeof value === "number" && property.toLowerCase().includes("amount")) {
value = `$${value}`;
return `$${value}`;
}

return <b>{value}</b>;
};

const handleValueChange = (e: FocusEvent<HTMLInputElement, Element>, property: string) => {
if (!e.target) return;
const newValue = (e.target as HTMLInputElement).value;
let queryValue: any = newValue;

if (newValue.toLowerCase() === "true") {
queryValue = true;
} else if (newValue.toLowerCase() === "false") {
queryValue = false;
} else if (!isNaN(Number(newValue))) {
queryValue = Number(newValue);
}

const updatedData = { ...rawData, [property]: queryValue } || {};

if (typeof setRawData === "function") {
setRawData(updatedData);
} else {
console.error("setRawData is not a function or is undefined");
}
};

const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" && submitButtonRef) {
if (submitButtonRef.current) {
submitButtonRef.current.click();
}
}
};

useEffect(() => {
if (rawData) {
const newData: object[] = [];
Object.entries(rawData).forEach(([property, value], index) => {
if (!PROPERTIES_TO_IGNORE.includes(property)) {
newData.push({
property,
value: convertAndStyleValue(value, property),
key: index,
});
}
});
const propertyRuleMap = Object.values(rulemap || {}).flat();
const newData = Object.entries(rawData)
.filter(([property]) => !PROPERTIES_TO_IGNORE.includes(property))
.sort(([propertyA], [propertyB]) => propertyA.localeCompare(propertyB))
.map(([property, value], index) => ({
property: propertyRuleMap?.find((item) => item.property === property)?.name || property,
value: convertAndStyleValue(value, property, editable),
key: index,
}));
setDataSource(newData);
const newColumns = COLUMNS.filter((column) => showColumn(newData, column.dataIndex));
setColumns(newColumns);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [rawData]);

const showColumn = (data: any[], columnKey: string) => {
return data.some((item) => item[columnKey] !== null && item[columnKey] !== undefined);
};

return (
<div>
<h4 className={styles.tableTitle}>{title}</h4>
<Table
columns={COLUMNS}
showHeader={false}
dataSource={dataSource}
bordered
pagination={{ hideOnSinglePage: true }}
/>
<h4 className={styles.tableTitle}>
{title} {title === "Outputs" && <Button onClick={toggleTableVisibility}>{showTable ? "Hide" : "Show"}</Button>}
</h4>
{showTable && (
<Table
columns={columns}
showHeader={false}
dataSource={dataSource}
bordered
pagination={{ hideOnSinglePage: true }}
/>
)}
</div>
);
}
138 changes: 138 additions & 0 deletions app/components/InputStyler/InputStyler.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { Tag, Input, Radio, AutoComplete, InputNumber, Flex } from "antd";
import { Scenario } from "@/app/types/scenario";

export default function inputStyler(
value: any,
property: string,
editable: boolean,
scenarios: Scenario[] = [],
rawData: object = {},
setRawData: any
) {
const getAutoCompleteOptions = (property: string) => {
if (!scenarios) return [];
const optionsSet = new Set<string>();

scenarios.forEach((scenario) => {
scenario.variables
.filter((variable) => variable.name === property)
.forEach((variable) => optionsSet.add(variable.value));
});

return Array.from(optionsSet).map((value) => ({ value, type: typeof value }));
};

const handleValueChange = (value: any, property: string) => {
let queryValue: any = value;
if (typeof value === "string") {
if (value.toLowerCase() === "true") {
queryValue = true;
} else if (value.toLowerCase() === "false") {
queryValue = false;
} else if (!isNaN(Number(value))) {
queryValue = Number(value);
}
}

const updatedData = { ...rawData, [property]: queryValue } || {};

if (typeof setRawData === "function") {
setRawData(updatedData);
} else {
console.error("setRawData is not a function or is undefined");
}
};

const handleInputChange = (val: any, property: string) => {
const updatedData = { ...rawData, [property]: val };
if (typeof setRawData === "function") {
setRawData(updatedData);
}
};

const valuesArray = getAutoCompleteOptions(property);
let type = typeof value;
if (valuesArray.length > 0) {
type = typeof valuesArray[0].value;
}

if (editable) {
if (type === "boolean" || typeof value === "boolean") {
return (
<Flex gap={"small"} align="center" vertical>
<label className="labelsmall">
<Radio.Group onChange={(e) => handleInputChange(e.target.value, property)} value={value}>
<Radio value={true}>Yes</Radio>
<Radio value={false}>No</Radio>
</Radio.Group>
<span className="label-text">{property}</span>
</label>
</Flex>
);
}

if (type === "string" || typeof value === "string") {
return (
<label className="labelsmall">
<AutoComplete
options={valuesArray}
defaultValue={value}
onBlur={(e) => handleValueChange((e.target as HTMLInputElement).value, property)}
style={{ width: 200 }}
onChange={(val) => handleInputChange(val, property)}
/>
<span className="label-text">{property}</span>
</label>
);
}

if (type === "number" || typeof value === "number") {
return (
<label className="labelsmall">
<InputNumber
value={value}
onBlur={(e) => handleValueChange(e.target.value, property)}
onChange={(val) => handleInputChange(val, property)}
/>
<span className="label-text">{property}</span>
</label>
);
}

if (value === null || value === undefined) {
return (
<label className="labelsmall">
<Input onBlur={(e) => handleValueChange(e.target.value, property)} />
<span className="label-text">{property}</span>
</label>
);
}
} else {
if (type === "boolean" || typeof value === "boolean") {
return (
<Radio.Group onChange={() => null} value={value}>
<Radio value={true}>Yes</Radio>
<Radio value={false}>No</Radio>
</Radio.Group>
);
}

if (type === "string" || typeof value === "string") {
return <Tag color="blue">{value}</Tag>;
}

if (type === "number" || typeof value === "number") {
if (property.toLowerCase().includes("amount")) {
return <Tag color="green">${value}</Tag>;
} else {
return <Tag color="blue">{value}</Tag>;
}
}

if (value === null || value === undefined) {
return null;
}
}

return <b>{value}</b>;
}
Empty file.
Loading

0 comments on commit 59d57ad

Please sign in to comment.