Skip to content

Commit 8c67e6d

Browse files
feat(myopencre): add CSV upload UI and wire to existing import endpoint
1 parent e9d84f2 commit 8c67e6d

File tree

2 files changed

+136
-16
lines changed

2 files changed

+136
-16
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.myopencre-section {
2+
margin-top: 2rem;
3+
}
4+
5+
.myopencre-upload {
6+
margin-top: 1.5rem;
7+
}
8+
9+
.myopencre-disabled {
10+
opacity: 0.7;
11+
}

application/frontend/src/pages/MyOpenCRE/MyOpenCRE.tsx

Lines changed: 125 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
1-
import React from 'react';
2-
import { Button, Container, Header } from 'semantic-ui-react';
1+
import './MyOpenCRE.scss';
2+
3+
import React, { useState } from 'react';
4+
import { Button, Container, Form, Header, Message } from 'semantic-ui-react';
35

46
import { useEnvironment } from '../../hooks';
57

68
export const MyOpenCRE = () => {
79
const { apiUrl } = useEnvironment();
8-
// console.log('API URL:', apiUrl);
910

10-
const downloadCreCsv = async () => {
11-
try {
12-
const baseUrl = apiUrl || window.location.origin;
11+
// Upload enabled only for local/dev
12+
const isUploadEnabled = apiUrl !== '/rest/v1';
1313

14-
const backendUrl = baseUrl.includes('localhost') ? 'http://127.0.0.1:5000' : baseUrl;
14+
const [selectedFile, setSelectedFile] = useState<File | null>(null);
15+
const [loading, setLoading] = useState(false);
16+
const [error, setError] = useState<string | null>(null);
17+
const [success, setSuccess] = useState<any | null>(null);
1518

16-
const response = await fetch(`${backendUrl}/cre_csv`, {
19+
/* ------------------ CSV DOWNLOAD ------------------ */
20+
21+
const downloadCreCsv = async () => {
22+
try {
23+
const response = await fetch(`${apiUrl}/cre_csv`, {
1724
method: 'GET',
18-
headers: {
19-
Accept: 'text/csv',
20-
},
25+
headers: { Accept: 'text/csv' },
2126
});
2227

2328
if (!response.ok) {
@@ -41,6 +46,67 @@ export const MyOpenCRE = () => {
4146
}
4247
};
4348

49+
/* ------------------ FILE SELECTION ------------------ */
50+
51+
const onFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
52+
setError(null);
53+
setSuccess(null);
54+
55+
if (!e.target.files || e.target.files.length === 0) return;
56+
57+
const file = e.target.files[0];
58+
59+
if (!file.name.toLowerCase().endsWith('.csv')) {
60+
setError('Please upload a valid CSV file.');
61+
e.target.value = '';
62+
setSelectedFile(null);
63+
return;
64+
}
65+
66+
setSelectedFile(file);
67+
};
68+
69+
/* ------------------ CSV UPLOAD ------------------ */
70+
71+
const uploadCsv = async () => {
72+
if (!selectedFile) return;
73+
74+
setLoading(true);
75+
setError(null);
76+
setSuccess(null);
77+
78+
const formData = new FormData();
79+
formData.append('cre_csv', selectedFile);
80+
81+
try {
82+
const response = await fetch(`${apiUrl}/cre_csv_import`, {
83+
method: 'POST',
84+
body: formData,
85+
});
86+
87+
if (response.status === 403) {
88+
throw new Error(
89+
'CSV import is disabled on hosted environments. Run OpenCRE locally with CRE_ALLOW_IMPORT=true.'
90+
);
91+
}
92+
93+
if (!response.ok) {
94+
const text = await response.text();
95+
throw new Error(text || 'CSV import failed');
96+
}
97+
98+
const result = await response.json();
99+
setSuccess(result);
100+
setSelectedFile(null);
101+
} catch (err: any) {
102+
setError(err.message || 'Unexpected error during import');
103+
} finally {
104+
setLoading(false);
105+
}
106+
};
107+
108+
/* ------------------ UI ------------------ */
109+
44110
return (
45111
<Container style={{ marginTop: '3rem' }}>
46112
<Header as="h1">MyOpenCRE</Header>
@@ -51,13 +117,56 @@ export const MyOpenCRE = () => {
51117
</p>
52118

53119
<p>
54-
Start by downloading the mapping template below, fill it with your standard’s controls, and map them
55-
to CRE IDs.
120+
Start by downloading the CRE catalogue below, then map your standard’s controls or sections to CRE IDs
121+
in the spreadsheet.
56122
</p>
57123

58-
<Button primary onClick={downloadCreCsv}>
59-
Download CRE Catalogue (CSV)
60-
</Button>
124+
<div className="myopencre-section">
125+
<Button primary onClick={downloadCreCsv}>
126+
Download CRE Catalogue (CSV)
127+
</Button>
128+
</div>
129+
130+
<div className="myopencre-section myopencre-upload">
131+
<Header as="h3">Upload Mapping CSV</Header>
132+
133+
<p>Upload your completed mapping spreadsheet to import your standard into OpenCRE.</p>
134+
135+
{!isUploadEnabled && (
136+
<Message info className="myopencre-disabled">
137+
CSV upload is disabled on hosted environments due to resource constraints.
138+
<br />
139+
Please run OpenCRE locally to enable standard imports.
140+
</Message>
141+
)}
142+
143+
{error && <Message negative>{error}</Message>}
144+
145+
{success && (
146+
<Message positive>
147+
<strong>Import successful</strong>
148+
<ul>
149+
<li>New CREs added: {success.new_cres?.length ?? 0}</li>
150+
<li>Standards imported: {success.new_standards}</li>
151+
</ul>
152+
</Message>
153+
)}
154+
155+
<Form>
156+
<Form.Field>
157+
<input type="file" accept=".csv" disabled={!isUploadEnabled || loading} onChange={onFileChange} />
158+
</Form.Field>
159+
160+
<Button
161+
primary
162+
loading={loading}
163+
disabled={!isUploadEnabled || !selectedFile || loading}
164+
onClick={uploadCsv}
165+
>
166+
Upload CSV
167+
</Button>
168+
</Form>
169+
</div>
61170
</Container>
62171
);
63172
};

0 commit comments

Comments
 (0)