Skip to content

Commit 30e9429

Browse files
committed
Organisations are now editable
1 parent ad8be57 commit 30e9429

13 files changed

Lines changed: 214 additions & 24 deletions

File tree

.DS_Store

0 Bytes
Binary file not shown.
-22 Bytes
Binary file not shown.
-22 Bytes
Binary file not shown.
-22 Bytes
Binary file not shown.
-22 Bytes
Binary file not shown.
-22 Bytes
Binary file not shown.

src/main/.DS_Store

0 Bytes
Binary file not shown.

src/main/frontend/src/api.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,17 @@ async function request(path, options = {}) {
1111
}
1212

1313
export const api = {
14-
getServiceInfo: () => request('/service-info'),
15-
getStandards: () => request('/standards'),
16-
getServices: () => request('/services'),
17-
getDeployments: () => request('/deployments'),
18-
getOrganisations: () => request('/organisations'),
14+
getServiceInfo: () => request('/service-info'),
15+
getStandards: () => request('/standards'),
16+
getServices: () => request('/services'),
17+
getDeployments: () => request('/deployments'),
18+
getOrganisations: () => request('/organisations'),
1919

20-
createService: (body) => request('/services', { method: 'POST', body: JSON.stringify(body) }),
21-
createDeployment: (body) => request('/deployments', { method: 'POST', body: JSON.stringify(body) }),
22-
createOrganisation:(body) => request('/organisations', { method: 'POST', body: JSON.stringify(body) }),
20+
createService: (body) => request('/services', { method: 'POST', body: JSON.stringify(body) }),
21+
createDeployment: (body) => request('/deployments', { method: 'POST', body: JSON.stringify(body) }),
22+
createOrganisation: (body) => request('/organisations', { method: 'POST', body: JSON.stringify(body) }),
2323

24-
updateService: (id, body) => request(`/services/${id}`, { method: 'PUT', body: JSON.stringify(body) }),
25-
updateDeployment: (id, body) => request(`/deployments/${id}`, { method: 'PUT', body: JSON.stringify(body) }),
24+
updateService: (id, body) => request(`/services/${id}`, { method: 'PUT', body: JSON.stringify(body) }),
25+
updateDeployment: (id, body) => request(`/deployments/${id}`, { method: 'PUT', body: JSON.stringify(body) }),
26+
updateOrganisation: (id, body) => request(`/organisations/${id}`, { method: 'PUT', body: JSON.stringify(body) }),
2627
}

src/main/frontend/src/pages/Organisations.jsx

Lines changed: 106 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,43 @@ function matches(org, query) {
1414
)
1515
}
1616

17+
const EMPTY = { orgId: '', name: '', shortName: '', url: '', description: '' }
18+
1719
export function Organisations() {
1820
const { data, loading, error, refresh } = useApi(api.getOrganisations)
1921
const showToast = useContext(ToastContext)
2022

21-
const [form, setForm] = useState({ orgId: '', name: '', shortName: '', url: '', description: '' })
23+
const [query, setQuery] = useState('')
24+
const [form, setForm] = useState(EMPTY)
2225
const [formError, setFormError] = useState(null)
2326
const [submitting, setSubmitting] = useState(false)
24-
const [query, setQuery] = useState('')
27+
28+
// Edit modal state
29+
const [editing, setEditing] = useState(null) // org object being edited
30+
const [editForm, setEditForm] = useState(EMPTY)
31+
const [editError, setEditError] = useState(null)
32+
const [editSaving, setEditSaving] = useState(false)
2533

2634
const filtered = data ? data.filter(o => matches(o, query)) : []
2735

2836
function set(field, value) { setForm(p => ({ ...p, [field]: value })) }
37+
function setEdit(field, value) { setEditForm(p => ({ ...p, [field]: value })) }
38+
39+
function openEdit(org) {
40+
setEditForm({
41+
orgId: org.orgId || '',
42+
name: org.name || '',
43+
shortName: org.shortName || '',
44+
url: org.url || '',
45+
description: org.description || ''
46+
})
47+
setEditError(null)
48+
setEditing(org)
49+
}
50+
51+
function closeEdit() { setEditing(null); setEditError(null) }
2952

30-
async function handleSubmit(e) {
53+
async function handleCreate(e) {
3154
e.preventDefault()
3255
setFormError(null)
3356
setSubmitting(true)
@@ -40,7 +63,7 @@ export function Organisations() {
4063
description: form.description || null
4164
})
4265
await refresh()
43-
setForm({ orgId: '', name: '', shortName: '', url: '', description: '' })
66+
setForm(EMPTY)
4467
showToast('Organisation registered successfully.')
4568
} catch (e) {
4669
setFormError(e.message)
@@ -49,11 +72,33 @@ export function Organisations() {
4972
}
5073
}
5174

75+
async function handleUpdate(e) {
76+
e.preventDefault()
77+
setEditError(null)
78+
setEditSaving(true)
79+
try {
80+
await api.updateOrganisation(editing.id, {
81+
orgId: editForm.orgId || null,
82+
name: editForm.name,
83+
shortName: editForm.shortName || null,
84+
url: editForm.url || null,
85+
description: editForm.description || null
86+
})
87+
await refresh()
88+
closeEdit()
89+
showToast('Organisation updated successfully.')
90+
} catch (e) {
91+
setEditError(e.message)
92+
} finally {
93+
setEditSaving(false)
94+
}
95+
}
96+
5297
return (
5398
<div>
5499
<h2>Organisations</h2>
55100

56-
{/* Search box */}
101+
{/* Search */}
57102
<div className={styles.searchRow}>
58103
<input
59104
className={styles.searchInput}
@@ -82,16 +127,18 @@ export function Organisations() {
82127
{o.orgId && <p><strong>Org ID:</strong> {o.orgId}</p>}
83128
{o.shortName && <p><strong>Short name:</strong> {o.shortName}</p>}
84129
{o.description && <p>{o.description}</p>}
85-
{o.url && <p><strong>URL:</strong> <a href={o.url} target="_blank" rel="noopener">{o.url}</a></p>}
130+
{o.url && <p><strong>URL:</strong> <a href={o.url} target="_blank" rel="noopener">{o.url}</a></p>}
131+
<button className={styles.editBtn} onClick={() => openEdit(o)}>Edit</button>
86132
</div>
87133
))}
88134
</div>
89135
)}
90136

137+
{/* Register form */}
91138
<div className={styles.formCard}>
92139
<h3>Register New Organisation</h3>
93140
{formError && <div className={styles.formError}>{formError}</div>}
94-
<form onSubmit={handleSubmit} className={styles.form}>
141+
<form onSubmit={handleCreate} className={styles.form}>
95142
<div className={styles.row}>
96143
<div>
97144
<label>Organisation ID *</label>
@@ -121,6 +168,58 @@ export function Organisations() {
121168
</button>
122169
</form>
123170
</div>
171+
172+
{/* Edit modal */}
173+
{editing && (
174+
<div
175+
className={styles.modalBackdrop}
176+
onMouseDown={e => { if (e.target === e.currentTarget) closeEdit() }}
177+
>
178+
<div className={styles.modal}>
179+
<div className={styles.modalHeader}>
180+
<h3>Edit Organisation</h3>
181+
<button className={styles.modalClose} onClick={closeEdit} title="Close">×</button>
182+
</div>
183+
<div className={styles.modalBody}>
184+
{editError && <div className={styles.formError}>{editError}</div>}
185+
<form onSubmit={handleUpdate} className={styles.form}>
186+
<div className={styles.row}>
187+
<div>
188+
<label>Organisation ID *</label>
189+
<input value={editForm.orgId} onChange={e => setEdit('orgId', e.target.value)} placeholder="e.g. org.ga4gh" required />
190+
</div>
191+
<div>
192+
<label>Name *</label>
193+
<input value={editForm.name} onChange={e => setEdit('name', e.target.value)} required />
194+
</div>
195+
</div>
196+
<div className={styles.row}>
197+
<div>
198+
<label>Short Name</label>
199+
<input value={editForm.shortName} onChange={e => setEdit('shortName', e.target.value)} />
200+
</div>
201+
<div>
202+
<label>Organisation URL</label>
203+
<input type="url" value={editForm.url} onChange={e => setEdit('url', e.target.value)} placeholder="https://..." />
204+
</div>
205+
</div>
206+
<div>
207+
<label>Description</label>
208+
<textarea value={editForm.description} onChange={e => setEdit('description', e.target.value)} />
209+
</div>
210+
<div className={styles.modalActions}>
211+
<button type="submit" className={styles.btnPrimary} disabled={editSaving}>
212+
{editSaving ? 'Saving…' : 'Save Changes'}
213+
</button>
214+
<button type="button" className={styles.btnSecondary} onClick={closeEdit}>
215+
Cancel
216+
</button>
217+
</div>
218+
</form>
219+
</div>
220+
</div>
221+
</div>
222+
)}
124223
</div>
125224
)
126225
}

src/main/frontend/src/pages/Organisations.module.css

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,31 @@
4141
border: 1px solid #e0e7ef;
4242
border-radius: 7px;
4343
padding: 1rem 1.2rem;
44-
display: grid;
44+
display: flex;
45+
flex-direction: column;
4546
gap: 0.3rem;
4647
}
4748
.card h4 { font-size: 0.97rem; color: #1a3a5c; }
4849
.card p { font-size: 0.85rem; color: #555; }
4950
.card p strong { color: #1a3a5c; margin-right: 4px; }
5051
.card a { color: #22527a; font-size: 0.85rem; word-break: break-all; }
5152

52-
/* Registration form */
53+
.editBtn {
54+
margin-top: 0.5rem;
55+
background: #e8f5e9;
56+
color: #1b5e20;
57+
border: 1px solid #a5d6a7;
58+
padding: 0.3rem 0.8rem;
59+
font-size: 0.8rem;
60+
border-radius: 4px;
61+
cursor: pointer;
62+
font-weight: 600;
63+
align-self: flex-start;
64+
transition: background 0.2s;
65+
}
66+
.editBtn:hover { background: #c8e6c9; }
67+
68+
/* Registration form card */
5369
.formCard {
5470
background: #fff;
5571
border-radius: 8px;
@@ -79,6 +95,8 @@
7995
background: #fafdff;
8096
color: #2c3e50;
8197
transition: border 0.2s;
98+
font-family: inherit;
99+
font-size: 0.92rem;
82100
}
83101
.form input:focus,
84102
.form textarea:focus { outline: none; border-color: #4fc3f7; background: #fff; }
@@ -93,6 +111,7 @@
93111
font-size: 0.9rem;
94112
margin-bottom: 0.75rem;
95113
}
114+
96115
.status { color: #7f8c8d; padding: 1rem 0; }
97116
.error { color: #880e4f; padding: 1rem 0; }
98117

@@ -110,3 +129,56 @@
110129
}
111130
.btnPrimary:hover:not(:disabled) { background: #22527a; }
112131
.btnPrimary:disabled { opacity: 0.6; cursor: default; }
132+
133+
.btnSecondary {
134+
padding: 0.6rem 1.4rem;
135+
border: none;
136+
border-radius: 5px;
137+
font-size: 0.92rem;
138+
cursor: pointer;
139+
font-weight: 600;
140+
background: #e0e7ef;
141+
color: #1a3a5c;
142+
transition: background 0.2s;
143+
}
144+
.btnSecondary:hover { background: #c8d6e8; }
145+
146+
/* Modal */
147+
.modalBackdrop {
148+
position: fixed;
149+
inset: 0;
150+
background: rgba(0,0,0,0.45);
151+
z-index: 1000;
152+
display: flex;
153+
align-items: flex-start;
154+
justify-content: center;
155+
overflow-y: auto;
156+
padding: 2rem 1rem;
157+
}
158+
.modal {
159+
background: #fff;
160+
border-radius: 10px;
161+
width: 100%;
162+
max-width: 600px;
163+
box-shadow: 0 8px 32px rgba(0,0,0,0.18);
164+
}
165+
.modalHeader {
166+
display: flex;
167+
justify-content: space-between;
168+
align-items: center;
169+
padding: 1.25rem 1.5rem 0.75rem;
170+
border-bottom: 1px solid #e0e7ef;
171+
}
172+
.modalHeader h3 { font-size: 1.1rem; color: #1a3a5c; margin: 0; }
173+
.modalClose {
174+
background: none;
175+
border: none;
176+
font-size: 1.5rem;
177+
cursor: pointer;
178+
color: #7f8c8d;
179+
line-height: 1;
180+
padding: 0;
181+
}
182+
.modalClose:hover { color: #2c3e50; }
183+
.modalBody { padding: 1.25rem 1.5rem 1.5rem; }
184+
.modalActions { display: flex; gap: 0.75rem; margin-top: 0.5rem; }

0 commit comments

Comments
 (0)