Skip to content

Commit

Permalink
Adding debounce so saving isn't as agressive! Adding download of repo…
Browse files Browse the repository at this point in the history
…rt, closes #2
  • Loading branch information
davetaz committed Jul 24, 2024
1 parent 09bfb07 commit 98c891a
Show file tree
Hide file tree
Showing 11 changed files with 278 additions and 126 deletions.
1 change: 1 addition & 0 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"date-fns": "^2.28.0",
"jwt-decode": "^4.0.0",
"litepicker": "^2.0.12",
"lodash": "^4.17.21",
"react": "^17.0.2",
"react-date-range": "^1.4.0",
"react-dom": "^17.0.2",
Expand All @@ -30,8 +31,8 @@
"web-vitals": "^2.1.4"
},
"devDependencies": {
"react-scripts": "5.0.0",
"@svgr/webpack": "^6.2.1"
"@svgr/webpack": "^6.2.1",
"react-scripts": "5.0.0"
},
"overrides": {
"@svgr/webpack": "$@svgr/webpack"
Expand Down
57 changes: 21 additions & 36 deletions client/src/CheckpointCTAs.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,31 @@
import React from 'react';
import { useModal } from './context/modal-context'
import axios from 'axios';
import { useNavigate } from 'react-router-dom';
import { useSelector } from 'react-redux';

export default function CheckpointCTAs() {
const { setModal } = useModal()
const navigate = useNavigate();
const assessmentId = useSelector((state) => state.checkpoints.activeAssessment.id); // Get assessmentId from Redux state

// if ( !activeCheckpoint || activeCheckpoint.length === 0) return <p></p>;
const handleViewReport = () => {
navigate(`/assessment/${assessmentId}/report`);
};

return (
<>
<div className="checkpoint-ctas" >
<button className="view-report-cta" onClick={() => {
axios.get('/json/report.json').then(res => {
const modalData = {
type: "report",
content: res.data[0]
};
setModal(modalData);
});
}}
>
<div className="cta-inner">
<div className="cta-title">View your report</div>
<div className="cta-content">

<div className="cta-text">You can review your report at anytime. However, the more checkpoints you tackle, the more useful the report will be.</div>

</div>

<div className="button button-white">
Read the report
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<line x1="10.1424" y1="0.431885" x2="10.1425" y2="19.5679" stroke="#2254F4" strokeWidth="2.2849"/>
<line x1="0.429688" y1="9.85706" x2="19.5657" y2="9.85706" stroke="#2254F4" strokeWidth="2.2849"/>
</svg>

</div>
<div className="viewReport">
<button className="view-report-cta" onClick={handleViewReport}>
<div className="cta-inner">
<div className="cta-title">View your report</div>
<div className="cta-content">
<div className="cta-image"></div>
<div className="cta-text">
<div className="text">View your report in many formats so you can easily reuse it.</div>
<div className="buttons-container">
<button className="button button-white">View Report</button>
</div>
</div>
</div>
</div>
</button>
</div>


</>
);
};
}
48 changes: 48 additions & 0 deletions client/src/DownloadReportButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// src/DownloadReportButton.js

import React from 'react';
import axiosInstance from './axiosInstance';

export default function DownloadReportButton({ assessmentId }) {
const handleDownloadReport = async (format) => {
try {
const response = await axiosInstance.get(`/assessments/${assessmentId}/report`, {
headers: {
Accept: format === 'csv' ? 'text/csv' : 'application/json'
},
responseType: 'blob' // Important for downloading files
});

// Create a link element to trigger the download
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', `assessment_report_${assessmentId}.${format}`);
document.body.appendChild(link);
link.click();
link.remove();
} catch (error) {
console.error(`Error downloading the ${format} report:`, error);
}
};

return (
<div className="checkpoint-ctas">
<button className="view-report-cta">
<div className="cta-inner">
<div className="cta-title">Download your report</div>
<div className="cta-content">
<div className="cta-image"></div>
<div className="cta-text">
<div className="text">Download your report in many formats so you can easily reuse it.</div>
<div className="buttons-container">
<button className="button button-white" onClick={() => handleDownloadReport('json')}>Download JSON</button>
<button className="button button-white" onClick={() => handleDownloadReport('csv')}>Download CSV</button>
</div>
</div>
</div>
</div>
</button>
</div>
);
}
100 changes: 47 additions & 53 deletions client/src/MitigateForm.js
Original file line number Diff line number Diff line change
@@ -1,83 +1,77 @@
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux'
import React, { useState, useEffect, useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { debounce } from 'lodash';
import SaveAndContinue from './SaveAndContinue';
import SaveAndContinueDisabled from './SaveAndContinueDisabled';


import {
updateCheckpointAnswers,
} from "./checkpointsSlice";

import { updateCheckpointAnswers } from './checkpointsSlice';

export default function MitigateForm(props) {
const dispatch = useDispatch();
const activeCheckpointAnswer = useSelector((state) => state.checkpoints.activeCheckpointAnswer);

const { fields } = props;

const form_data = {};

fields.forEach((field, i) => {
const field_name = field.name;
let val = "";
if (typeof(activeCheckpointAnswer.form_data) !== "undefined" && typeof(activeCheckpointAnswer.form_data[field_name]) !== "undefined") {
let val = '';
if (typeof activeCheckpointAnswer.form_data !== 'undefined' && typeof activeCheckpointAnswer.form_data[field_name] !== 'undefined') {
val = activeCheckpointAnswer.form_data[field_name];
}

form_data[field_name] = val;
});

const [formData, setformData] = useState(form_data);


const [formData, setFormData] = useState(form_data);

// Function to dispatch the updateCheckpointAnswers action
const debouncedDispatch = useCallback(
debounce((answer) => {
dispatch(updateCheckpointAnswers(answer));
}, 1000), // Adjust the debounce delay as needed (1000ms = 1 second)
[]
);

const handleChange = (name, value) => {

const new_form_data = {...formData};

const new_form_data = { ...formData };
new_form_data[name] = value;

const answer = {...activeCheckpointAnswer};
const answer = { ...activeCheckpointAnswer };
answer.form_data = new_form_data;
dispatch(updateCheckpointAnswers(answer))
setformData(new_form_data)

}

return (

<div>
// Update local state immediately
setFormData(new_form_data);

{fields.map((field, i) => {
return (
<div
key={i}
className="form-element"
>
<label>{field.label}</label>
<textarea
name={field.name}
value={formData[field.name]}
onChange={(e) => handleChange(e.target.name, e.target.value)}
></textarea>
</div>
// Dispatch the debounced action
debouncedDispatch(answer);
};

);
})}

{
(
!activeCheckpointAnswer.option.explain_risk
||
typeof(activeCheckpointAnswer.form_data) !== "undefined" && activeCheckpointAnswer.form_data.mitigating_actions.length
) ? <SaveAndContinue /> : <SaveAndContinueDisabled />
}
useEffect(() => {
// Cleanup function to cancel any pending debounced actions when the component unmounts
return () => {
debouncedDispatch.cancel();
};
}, [debouncedDispatch]);

return (
<div>
{fields.map((field, i) => (
<div key={i} className="form-element">
<label>{field.label}</label>
<textarea
name={field.name}
value={formData[field.name]}
onChange={(e) => handleChange(e.target.name, e.target.value)}
></textarea>
</div>
))}
{(!activeCheckpointAnswer.option.explain_risk ||
(typeof activeCheckpointAnswer.form_data !== 'undefined' &&
activeCheckpointAnswer.form_data.mitigating_actions.length)) ? (
<SaveAndContinue />
) : (
<SaveAndContinueDisabled />
)}
</div>
);




}
}
4 changes: 4 additions & 0 deletions client/src/Report.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import DownloadReportButton from './DownloadReportButton';

export default function Report() {
useParams();
Expand All @@ -20,6 +21,7 @@ export default function Report() {
};

return (
<>
<div className="template template-data-capture">
<div className="container">
<div className="report">
Expand All @@ -34,6 +36,8 @@ export default function Report() {
</div>
</div>
</div>
<DownloadReportButton assessmentId={activeAssessment.id} />
</>
);

function AnswersList(props) {
Expand Down
Loading

0 comments on commit 98c891a

Please sign in to comment.