Skip to content

Commit c79e48b

Browse files
authored
#139 Add practitioner role in mcsd admin (#201)
1 parent 764d823 commit c79e48b

File tree

14 files changed

+653
-76
lines changed

14 files changed

+653
-76
lines changed

component/mcsdadmin/component.go

Lines changed: 195 additions & 66 deletions
Large diffs are not rendered by default.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package formdata
2+
3+
import (
4+
"fmt"
5+
"net/url"
6+
"regexp"
7+
8+
"github.com/nuts-foundation/nuts-knooppunt/component/mcsdadmin/valuesets"
9+
"github.com/rs/zerolog/log"
10+
"github.com/zorgbijjou/golang-fhir-models/fhir-models/fhir"
11+
)
12+
13+
var keyExp regexp.Regexp
14+
15+
func init() {
16+
exp, err := regexp.Compile(`(\w+)\[(\d*)\]\[(\w+)\]`)
17+
if err != nil {
18+
log.Error().Err(err).Msg("could not parse regular expression")
19+
return
20+
}
21+
keyExp = *exp
22+
}
23+
24+
func ParseMaps(postform url.Values, fieldName string) []map[string]string {
25+
type index = string
26+
type key = string
27+
type value = string
28+
var partials = map[index]map[key]value{}
29+
30+
// Iterate over relevant keys and pull out the relevant data into partials
31+
for fk, val := range postform {
32+
matches := keyExp.FindStringSubmatch(fk)
33+
if len(matches) < 4 {
34+
continue
35+
}
36+
fieldNameMatch := matches[1]
37+
indexMatch := matches[2]
38+
propKeyMatch := matches[3]
39+
40+
if fieldNameMatch != fieldName {
41+
continue
42+
}
43+
44+
// Find if we already have some data from other keys...
45+
// ... if not create a new map
46+
partial, ok := partials[indexMatch]
47+
if !ok {
48+
partial = map[key]value{}
49+
partials[indexMatch] = partial
50+
}
51+
52+
if len(val) > 1 {
53+
log.Warn().Msg(fmt.Sprintf("conflicting values found for key: %s", fk))
54+
}
55+
partial[propKeyMatch] = val[0]
56+
}
57+
58+
// Now let's construct the return value
59+
partialsLen := len(partials)
60+
out := make([]map[key]value, 0, partialsLen)
61+
for _, part := range partials {
62+
out = append(out, part)
63+
}
64+
return out
65+
}
66+
67+
func CodablesFromForm(postform url.Values, set []fhir.Coding, key string) ([]fhir.CodeableConcept, bool) {
68+
nonEmpty := filterEmpty(postform[key])
69+
return valuesets.CodablesFrom(set, nonEmpty)
70+
}
71+
72+
func filterEmpty(multiStrings []string) []string {
73+
out := make([]string, 0, len(multiStrings))
74+
for _, str := range multiStrings {
75+
if str != "" {
76+
out = append(out, str)
77+
}
78+
}
79+
return out
80+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package formdata
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestPostForm(t *testing.T) {
10+
postform := map[string][]string{
11+
"telecom[0][System]": []string{"phone"},
12+
"telecom[0][value]": []string{"+31612345678"},
13+
"telecom[1][System]": []string{"email"},
14+
"telecom[1][value]": []string{"[email protected]"},
15+
"reference[0][system]": []string{"email"},
16+
"reference[0][value]": []string{"[email protected]"},
17+
}
18+
19+
result := ParseMaps(postform, "telecom")
20+
require.Equal(t, 2, len(result), "Expected two telecom entries")
21+
}

component/mcsdadmin/static/css/mcsdadmin.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@
3333
margin-bottom: 10px;
3434
}
3535

36+
fieldset {
37+
border: 1px solid lightgrey;
38+
padding: 5px;
39+
margin-bottom: 10px;
40+
}
41+
42+
legend {
43+
padding: 1px 10px;
44+
float: none;
45+
width: auto;
46+
font-size: 1rem;
47+
}
48+
3649
#edit-associations .card {
3750
padding: 10px;
3851
width: 700px;

component/mcsdadmin/static/js/lib.js

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,61 @@
11
function addOption(elementId) {
2-
var option = document.getElementById(elementId);
3-
var newOption = option.cloneNode(true);
4-
newOption.id = null;
5-
newOption.required = false;
2+
let option = document.getElementById(elementId);
3+
let newOption = option.cloneNode(true);
4+
5+
let complexOption = option.tagName === "DIV" || option.tagName === "FIELDSET";
6+
7+
if (complexOption) {
8+
// Update the names of the child nodes in case of a complex option
9+
let childNodes = Array.from(newOption.querySelectorAll("*"));
10+
childNodes.forEach(child => {
11+
let name = child.name;
12+
let incrName = incrementIndex(name);
13+
child.name = incrName
14+
child.id = incrName
15+
16+
if (child.tagName === "INPUT") {
17+
child.value = ""
18+
}
19+
})
20+
21+
// Move the ID over to the new node so that it is just for the next increment
22+
newOption.id = option.id
23+
}
24+
25+
option.removeAttribute('id')
626
option.parentElement.appendChild(newOption);
727
}
828

29+
const indexRe = /.+\[(\d+)\].+/;
30+
31+
function incrementIndex (name) {
32+
if (typeof name === "string") {
33+
let match = name.match(indexRe)
34+
if (match.length > 1) {
35+
let target = `[${match[1]}]`
36+
let replacementInt = parseInt(match[1]) +1
37+
let replacement = `[${replacementInt}]`
38+
return name.replace(target, replacement)
39+
} else {
40+
console.warn(`unknow name format for complex option: ${name}`)
41+
return name;
42+
}
43+
} else {
44+
return name;
45+
}
46+
}
47+
948
function removeOption(elementId) {
10-
var optionDiv = document.getElementById(elementId);
11-
var childrenCount = optionDiv.children.length;
49+
let optionDiv = document.getElementById(elementId);
50+
let childrenCount = optionDiv.children.length;
1251
if (childrenCount > 1) {
1352
optionDiv.removeChild(optionDiv.lastChild);
1453
}
1554
}
1655

1756
function dismissAlert(elementId) {
18-
var elm = document.getElementById(elementId);
19-
elm.hidden = true
57+
let elm = document.getElementById(elementId);
58+
elm.hidden = true;
2059
}
2160

2261
window.onload = function(){

component/mcsdadmin/templates/base.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636
<a class="nav-link" href="/mcsdadmin/location">
3737
<i class="fas fa-map-marker-alt"></i> Locations
3838
</a>
39+
<a class="nav-link" href="/mcsdadmin/practitionerrole">
40+
<i class="fas fa-user-nurse"></i> Practitioner Role
41+
</a>
3942
</div>
4043
</nav>
4144

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{{define "main"}}
2+
{{ template "_alert_error" . }}
3+
{{end}}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
{{define "main"}}
2+
<div class="card">
3+
<div class="card-header">
4+
<h4>Edit Practitioner Role</h4>
5+
</div>
6+
<div class="card-body">
7+
<form hx-boost="true" method="post" enctype="application/x-www-form-urlencoded">
8+
{{ if .OrgsExist }}
9+
<div class="mb-3">
10+
<label for="uzi-number" class="form-label">UZI-number:</label>
11+
<input id="uzi-number" type="text" name="uzi-number" class="form-control" placeholder="Enter code here"
12+
required>
13+
</div>
14+
<div class="mb-3">
15+
<label for="organization-id" class="form-label">Part of Organization:</label>
16+
<select name="organization-id" id="organization-id" class="form-select">
17+
<option value="">--Please choose an option--</option>
18+
{{range .Organizations}}
19+
<option value="{{ .Id }}">{{ .Name }}</option>
20+
{{ end }}
21+
</select>
22+
</div>
23+
<div class="mb-3">
24+
<label for="code-select" class="form-label">Choose a code:</label>
25+
<div id="code-options" class="options">
26+
<select name="codes" id="code-select" class="form-select" required>
27+
<option value="">--Please choose an option--</option>
28+
{{ range .Codes }}
29+
<option value="{{ .Code }}">{{ .Display }}</option>
30+
{{ end }}
31+
</select>
32+
</div>
33+
<div>
34+
<button onclick='addOption("code-select");' type="button" class="btn btn-secondary btn-sm">
35+
Add code
36+
</button>
37+
</div>
38+
</div>
39+
<div class="mb-3">
40+
<div>
41+
<fieldset id="telecom-options">
42+
<legend>Contact details</legend>
43+
<div class="options">
44+
<label class="form-label">Choose a method:</label>
45+
<select name="telecom[0][System]" id="telecom[0][System]" class="form-select" required>
46+
<option value="">--Please choose an option--</option>
47+
{{ range .TelecomCodes }}
48+
<option value="{{ .Code }}">{{ .Display }}</option>
49+
{{ end }}
50+
</select>
51+
<label class="form-label">Value:</label>
52+
<input id="telecom[0][Value]" type="text" name="telecom[0][Value]" class="form-control"
53+
placeholder="Enter here" required>
54+
</div>
55+
</fieldset>
56+
</div>
57+
<div>
58+
<button onclick='addOption("telecom-options");' type="button" class="btn btn-secondary btn-sm">
59+
Add contact details
60+
</button>
61+
</div>
62+
</div>
63+
{{ else }}
64+
<p>Please add an organization before adding roles</p>
65+
{{ end }}
66+
<div class="mb-3">
67+
<button type="submit" class="btn btn-primary">Submit</button>
68+
</div>
69+
</form>
70+
</div>
71+
</div>
72+
{{end}}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{{define "main"}}
2+
<div class="d-flex justify-content-between align-items-center mb-3">
3+
<h2>Practitioner Role</h2>
4+
<a href="/mcsdadmin/practitionerrole/new" class="btn btn-primary">New Practitioner Role</a>
5+
</div>
6+
<div class="card">
7+
<div class="card-body">
8+
<table class="table table-striped table-hover">
9+
<thead class="table-light">
10+
<tr>
11+
<th scope="col">UZI-Number</th>
12+
<th scope="col">Organization</th>
13+
<th scope="col">Code</th>
14+
<th scope="col">Telecom</th>
15+
<th scope="col">Actions</th>
16+
</tr>
17+
</thead>
18+
<tbody>
19+
{{range .Items }}
20+
<tr id="row-{{.Id}}">
21+
<th scope="row">{{ .Uzi }}</th>
22+
<td>{{ .Organization }}</td>
23+
<td>{{ .Code }}</td>
24+
<td>{{ .Telecom }}</td>
25+
<td>
26+
<button class="btn btn-outline-dark btn-sm"
27+
hx-delete="/mcsdadmin/practitionerrole/{{ .Id }}"
28+
hx-target="#row-{{.Id}}"
29+
hx-swap="delete"
30+
>
31+
Delete
32+
</button>
33+
</td>
34+
</tr>
35+
{{end}}
36+
</tbody>
37+
</table>
38+
</div>
39+
</div>
40+
{{end}}

0 commit comments

Comments
 (0)