Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate CIDER Resource Allocation Interface to XRAS Admin and implement relative_order drag and sort functionality #8

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0a848bc
feat(edit-resource): add placeholder for edit resource component
yomatters Sep 17, 2024
729c67b
xras ui admin React components for new interface
asimregmi Sep 24, 2024
1c04a17
added support for add/update Required Resources & utils, reducers
asimregmi Sep 27, 2024
3e0a6a9
minor formatting and tweaks in Allocation Grid
asimregmi Sep 27, 2024
966e4d5
New AddNewModal react component for allocation types & required resou…
asimregmi Oct 4, 2024
7b1db38
added new Resources component to drag and sort resources by relative_…
asimregmi Oct 15, 2024
9985536
updated styles for drag and ordering, code refactoring, and new scrol…
asimregmi Oct 16, 2024
8be91be
changed fetch URL to /resources instead of /resources/resource_id
asimregmi Oct 21, 2024
4f3b0f9
added relative_url_root
asimregmi Oct 22, 2024
14a58ed
updated error handling
asimregmi Oct 23, 2024
2b8f07c
fix: Changed resource_name to display_resource_name
rebeccaeve Nov 4, 2024
a61a6e6
custom hooksand updated React component
asimregmi Nov 13, 2024
479e9b6
handleChange function for coluumn level and cell level onChange handlers
asimregmi Nov 13, 2024
9229738
small fix to remove allocation id
asimregmi Nov 13, 2024
23bb732
exchange rates UI component, reducers, and useExchangeRates.js hooks
asimregmi Nov 21, 2024
faf9f2b
added DatePicker component and date validation before submission
asimregmi Jan 25, 2025
5e705df
added notes under ExchangeRate Grid, usesExchangeRates feature boolean
asimregmi Jan 27, 2025
6ca35db
enable/disable dates and rate fields and code improvement
asimregmi Jan 30, 2025
39ce2ad
bug fix for empty rate error
asimregmi Jan 31, 2025
f17e6bd
feat(grid): make scroll behavior optional
yomatters Jan 31, 2025
694d45d
removed useMemo for all data transformations and only kept useCallBac…
asimregmi Jan 31, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4,897 changes: 4,897 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

79 changes: 79 additions & 0 deletions src/edit-resource/AddNewModal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import PropTypes from "prop-types";

export const AddNewModal = ({ show, onClose, title, children, onSave }) => {
if (!show) {
return null;
}

return (
<div
style={{
position: "fixed",
top: 0,
left: 0,
width: "100%",
height: "100%",
backgroundColor: "rgba(0, 0, 0, 0.8)",
display: "flex",
alignItems: "center",
zIndex: 1050,
}}
>
<div
className="modal fade in"
tabIndex="-1"
role="dialog"
aria-hidden="true"
style={{
width: "90%",
maxWidth: "600px",
maxHeight: "90%",
overflow: "auto",
backgroundColor: "white",
position: "relative",
}}
>
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<button
type="button"
className="close"
data-dismiss="modal"
onClick={onClose}
aria-label="Close"
>
<h3 aria-hidden="true" className="text-danger">
&times;
</h3>
</button>
<h3 className="modal-title">{title}</h3>
</div>
<div className="modal-body">{children}</div>
<div
className="modal-footer"
style={{
padding: "15px",
textAlign: "right",
borderTop: "1px solid #e5e5e5",
backgroundColor: "#f8f9fa",
}}
>
<button className="btn btn-success" onClick={onSave}>
Save
</button>
</div>
</div>
</div>
</div>
</div>
);
};

AddNewModal.propTypes = {
show: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
onSave: PropTypes.func.isRequired,
};
40 changes: 40 additions & 0 deletions src/edit-resource/AllocationTypesGrid.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from "react";
import PropTypes from "prop-types";
import Grid from "../shared/Grid";
import style from "./AllocationTypesGrid.module.scss";

export const AllocationGrid = React.memo(function AllocationGrid({
columns,
rows,
onAddRequiredResource,
onAddAllocationType,
}) {
return (
<div className={style["allocation-types-grid"]}>
<div className={style["header-container"]}>
<h2 className={style["header-title"]}>Allocation Types</h2>
<div className={style["header-buttons"]}>
<button className="btn btn-primary" onClick={onAddAllocationType}>
<i className="fa fa-plus"></i> Add Allocation Type
</button>
<button className="btn btn-primary" onClick={onAddRequiredResource}>
<i className="fa fa-plus"></i> Add Required Resource
</button>
</div>
</div>
<Grid
columns={columns}
rows={rows}
rowClasses={Array(rows.length).fill(style["vertical-align-center"])}
scroll={false}
/>
</div>
);
});

AllocationGrid.propTypes = {
columns: PropTypes.array.isRequired,
rows: PropTypes.array.isRequired,
onAddRequiredResource: PropTypes.func.isRequired,
onAddAllocationType: PropTypes.func.isRequired,
};
20 changes: 20 additions & 0 deletions src/edit-resource/AllocationTypesGrid.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}

.header-buttons {
display: flex;
gap: 1rem; /* Space between the buttons */
}

.allocation-types-grid {
margin-bottom: 0.8rem;
margin-top: 0.8rem;
}

.vertical-align-center td {
vertical-align: middle;
}
195 changes: 195 additions & 0 deletions src/edit-resource/EditResource.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import { useEffect } from "react";
import PropTypes from "prop-types";
import LoadingSpinner from "../shared/LoadingSpinner";
import { SelectInput } from "../shared/SelectInput/SelectInput";
import { ResourceForm } from "./ResourceForm";
import { AllocationGrid } from "./AllocationTypesGrid";
import { AddNewModal } from "./AddNewModal";
import { ExchangeRates } from "./ExchangeRates";
import Alert from "../shared/Alert";
import {
useResourceData,
useResourceOptions,
useAllocationGrid,
useResourceSubmit,
useAllocationRowsAndColumns,
} from "./helpers/hooks";
import { useExchangeRates } from "./helpers/useExchangeRates";
export default function EditResource({
resourceId,
relativeUrlRoot,
setExternalSubmit,
}) {
const { state, dispatch, handleError, fetchData } = useResourceData(
resourceId,
relativeUrlRoot
);
const { resourceData, loading, errors, successMessage } = state;
const resourceDetails = resourceData?.resource_details;
const usesExchangeRates = resourceData?.uses_exchange_rates;

const {
allowedActionsOptions,
resourceTypesOptions,
unitTypesOptions,
availableResources,
availableAllocationTypes,
} = useResourceOptions(resourceData);

const {
showAddResourceModal,
setShowAddResourceModal,
showAddAllocationTypeModal,
setShowAddAllocationTypeModal,
selectedNewResource,
handleSelectNewResource,
handleSaveResources,
selectedNewAllocationType,
handleSelectNewAllocationType,
handleSaveAllocationType,
handleAllowedActionChange,
handleCommentChange,
handleRequiredResourceChange,
} = useAllocationGrid(resourceData, resourceDetails, dispatch);

const {
exchangeRateColumns,
exchangeRateRows,
handleAddDiscountRate,
dateErrors,
} = useExchangeRates(resourceData, dispatch);

const handleSubmit = useResourceSubmit(
resourceDetails,
resourceId,
relativeUrlRoot,
fetchData,
handleError,
dispatch
);

// Expose handleSubmit to external Rails template script
useEffect(() => {
if (setExternalSubmit && dateErrors.length == 0) {
setExternalSubmit(handleSubmit);
} else {
setExternalSubmit(null);
}
}, [handleSubmit, setExternalSubmit, dateErrors]);

const { allocationColumns, allocationRows } = useAllocationRowsAndColumns(
resourceDetails,
availableResources,
selectedNewResource,
allowedActionsOptions,
handleAllowedActionChange,
handleCommentChange,
handleRequiredResourceChange
);

if (loading) return <LoadingSpinner />;
if (errors.length > 0) {
return (
<div>
{errors.map((error, index) => (
<Alert key={index} color="danger">
{error}
</Alert>
))}
</div>
);
}
if (!resourceData) return <div>No resource data available.</div>;

return (
<div className="edit-resource">
{successMessage.message && (
<Alert color={successMessage.color}>{successMessage.message}</Alert>
)}
<div>
<h2>Edit Resource</h2>
<ResourceForm
resourceDetails={resourceDetails}
resourceTypesOptions={resourceTypesOptions}
unitTypesOptions={unitTypesOptions}
dispatch={dispatch}
/>
</div>

<AllocationGrid
columns={allocationColumns}
rows={allocationRows}
onAddRequiredResource={() => setShowAddResourceModal(true)}
onAddAllocationType={() => setShowAddAllocationTypeModal(true)}
/>

<AddNewModal
show={showAddResourceModal}
onClose={() => setShowAddResourceModal(false)}
title="Add Required Resource"
onSave={handleSaveResources}
>
<div>
{availableResources.map((resource) => (
<label
key={resource.resource_id}
style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}
>
<input
type="checkbox"
style={{ margin: 0 }}
checked={selectedNewResource.includes(resource.resource_id)}
onChange={(e) =>
handleSelectNewResource(
resource.resource_id,
e.target.checked
)
}
/>
{resource.resource_name}
</label>
))}
</div>
</AddNewModal>

<AddNewModal
show={showAddAllocationTypeModal}
onClose={() => setShowAddAllocationTypeModal(false)}
title="Add Allocation Type"
onSave={handleSaveAllocationType}
>
<SelectInput
label="Select Allocation Type"
options={[
{
value: "",
label: "Select an allocation type to add",
disabled: true,
},
...availableAllocationTypes.map((at) => ({
value: at.allocation_type_id,
label: at.display_name,
})),
]}
value={selectedNewAllocationType}
onChange={handleSelectNewAllocationType}
/>
</AddNewModal>

{usesExchangeRates && (
<ExchangeRates
columns={exchangeRateColumns}
rows={exchangeRateRows}
onAddDiscountRate={handleAddDiscountRate}
dateErrors={dateErrors}
/>
)}
</div>
);
}

EditResource.propTypes = {
resourceId: PropTypes.number.isRequired,
relativeUrlRoot: PropTypes.string.isRequired,
setExternalSubmit: PropTypes.func,
};
47 changes: 47 additions & 0 deletions src/edit-resource/ExchangeRates.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from "react";
import PropTypes from "prop-types";
import Grid from "../shared/Grid";
import style from "./ExchangeRatesGrid.module.scss";

export const ExchangeRates = React.memo(function ExchangeRatesGrid({
columns,
rows,
onAddDiscountRate,
dateErrors = [],
}) {
return (
<div className={style["exchange-rates-grid"]}>
<div className={style["header-container"]}>
<h2 className={style["header-title"]}>Exchange Rates</h2>
<div className={style["header-buttons"]}>
<button className="btn btn-primary" onClick={onAddDiscountRate}>
<i className="fa fa-plus"></i> Add Discount Rate
</button>
</div>
</div>
<Grid
columns={columns}
rows={rows}
rowClasses={Array(rows.length).fill(style["vertical-align-center"])}
scroll={false}
/>
{/* Error Summary */}
{dateErrors.length > 0 && (
<div className={style["error-summary"]}>
<ul>
{dateErrors.map((error, index) => (
<li key={index}>{error}</li>
))}
</ul>
</div>
)}
</div>
);
});

ExchangeRates.propTypes = {
columns: PropTypes.array.isRequired,
rows: PropTypes.array.isRequired,
onAddDiscountRate: PropTypes.func.isRequired,
dateErrors: PropTypes.array,
};
Loading