Skip to content

Commit 7fa2ce0

Browse files
committed
Add Required fields form values.
Add badge style to connection table for label:id values.
1 parent e983482 commit 7fa2ce0

File tree

7 files changed

+435
-65
lines changed

7 files changed

+435
-65
lines changed

src/lib/components/admin/configuration/ConnectionForm.svelte

+15-29
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,21 @@
66
import type { Connection } from '$lib/models/Connection';
77
import { addConnection, updateConnection } from '$lib/stores/Connections';
88
9+
import RequiredFieldsList from './RequiredFieldsList.svelte';
10+
911
export let connection: Connection | undefined = undefined;
1012
1113
let label: string = connection?.label || '';
1214
let id: string = connection?.id || '';
1315
let subPrefix: string = connection?.subPrefix || '';
14-
let requiredFields: string = connection?.requiredFields || '';
15-
let validationError: string = '';
16+
let requiredFields: string = connection?.requiredFields || '[]';
1617
17-
async function saveConnection() {
18-
try {
19-
requiredFields !== '' && JSON.parse(requiredFields);
20-
validationError = '';
21-
} catch (_) {
22-
validationError = 'The provided JSON has a syntax error.';
23-
return;
24-
}
18+
type JSONEvent = { detail: { json: string } };
19+
function updateRequiredFields(event: JSONEvent) {
20+
requiredFields = event.detail.json;
21+
}
2522
23+
async function saveConnection() {
2624
let newConnection: Connection = {
2725
id,
2826
label,
@@ -90,31 +88,19 @@
9088
/>
9189
</label>
9290

93-
<label class="label">
94-
<span>Required Fields:</span>
95-
<textarea class="w-full input input-border" bind:value={requiredFields} />
96-
</label>
97-
98-
{#if validationError}
99-
<aside data-testid="validation-error" class="alert variant-ghost-error">
100-
<div class="alert-message">
101-
<p>{validationError}</p>
102-
</div>
103-
<div class="alert-actions">
104-
<button on:click={() => (validationError = '')}>
105-
<i class="fa-solid fa-xmark"></i>
106-
<span class="sr-only">Close</span>
107-
</button>
108-
</div>
109-
</aside>
110-
{/if}
91+
<RequiredFieldsList fields={requiredFields} on:update={updateRequiredFields} />
11192

11293
<div>
113-
<button type="submit" class="btn variant-ghost-primary hover:variant-filled-primary">
94+
<button
95+
type="submit"
96+
data-testid="connection-save-btn"
97+
class="btn variant-ghost-primary hover:variant-filled-primary"
98+
>
11499
Save
115100
</button>
116101
<a
117102
href="/admin/configuration"
103+
data-testid="connection-cancel-btn"
118104
class="btn variant-ghost-secondary hover:variant-filled-secondary"
119105
>
120106
Cancel
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<script lang="ts">
2+
import { createEventDispatcher } from 'svelte';
3+
const dispatch = createEventDispatcher<{
4+
delete: RequiredField;
5+
save: { previous: RequiredField; current: RequiredField };
6+
}>();
7+
8+
import type { RequiredField } from '$lib/models/Connection';
9+
10+
export let field: RequiredField = { label: '', id: '' };
11+
export let duplicate: boolean = false;
12+
13+
let label: string = field.label;
14+
let id: string = field.id;
15+
16+
$: dirtyForm = field.id !== id || field.label !== label;
17+
const newField: boolean = field.label === '' || field.id === '';
18+
let edit: boolean = newField;
19+
20+
const saveField = () => {
21+
const currentField = { label, id };
22+
dispatch('save', { previous: field, current: currentField });
23+
edit = false;
24+
};
25+
const resetField = () => {
26+
label = field.label;
27+
id = field.id;
28+
};
29+
const deleteField = () => dispatch('delete', { label, id });
30+
const toggleEdit = () => (edit = !edit);
31+
</script>
32+
33+
<div
34+
class="flex gap-4 p-3 odd:bg-surface-100-800-token even:bg-surface-50-900-token"
35+
data-testid={`required-field-row-${field.id ? field.id : 'new'}`}
36+
>
37+
<label class="label flex-1">
38+
<span>Label:</span>
39+
<input
40+
type="text"
41+
bind:value={label}
42+
disabled={!edit}
43+
class="input"
44+
minlength="1"
45+
maxlength="255"
46+
/>
47+
</label>
48+
<label class="label flex-1">
49+
<span>ID:</span>
50+
<input
51+
type="text"
52+
bind:value={id}
53+
disabled={!edit}
54+
class="input {duplicate &&
55+
'variant-ghost-warning border-warning-500-400-token focus:border-warning-700-200-token'}"
56+
minlength="1"
57+
maxlength="255"
58+
/>
59+
</label>
60+
<div class="flex-none content-end py-2">
61+
{#if edit}
62+
<button
63+
type="button"
64+
title="Save"
65+
data-testid="required-field-save-btn"
66+
class="btn-icon-color"
67+
disabled={!label || !id || !dirtyForm}
68+
on:click={saveField}
69+
>
70+
<i class="fa-solid fa-floppy-disk fa-xl"></i>
71+
<span class="sr-only">Save</span>
72+
</button>
73+
{#if dirtyForm}
74+
<button
75+
type="button"
76+
title="Reset and cancel"
77+
class="btn-icon-color"
78+
data-testid="required-field-reset-btn"
79+
on:click={resetField}
80+
>
81+
<i class="fa-solid fa-arrow-rotate-right fa-xl"></i>
82+
<span class="sr-only">Reset and cancel</span>
83+
</button>
84+
{:else if !newField && !dirtyForm}
85+
<button
86+
type="button"
87+
title="Cancel"
88+
class="btn-icon-color"
89+
data-testid="required-field-cancel-btn"
90+
on:click={toggleEdit}
91+
>
92+
<i class="fa-solid fa-ban fa-xl"></i>
93+
<span class="sr-only">Cancel</span>
94+
</button>
95+
{/if}
96+
{:else}
97+
<button
98+
type="button"
99+
title="Edit"
100+
class="btn-icon-color"
101+
data-testid="required-field-edit-btn"
102+
on:click={toggleEdit}
103+
>
104+
<i class="fa-solid fa-pen fa-xl"></i>
105+
<span class="sr-only">Edit</span>
106+
</button>
107+
{/if}
108+
<button
109+
type="button"
110+
title="Delete"
111+
class="btn-icon-color"
112+
data-testid="required-field-delete-btn"
113+
on:click={deleteField}
114+
>
115+
<i class="fa-solid fa-trash fa-xl"></i>
116+
<span class="sr-only">Delete</span>
117+
</button>
118+
</div>
119+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<script lang="ts">
2+
import { createEventDispatcher } from 'svelte';
3+
const dispatch = createEventDispatcher<{ update: { json: string } }>();
4+
5+
import { type RequiredField, parseFieldsFromJSON } from '$lib/models/Connection';
6+
import RequiredFieldRow from './RequiredFieldRow.svelte';
7+
8+
export let fields: string = '[]';
9+
10+
$: fieldList = parseFieldsFromJSON(fields);
11+
$: enableNewField = fieldList.length === 0;
12+
$: duplicates = fieldList.filter((checkField) => {
13+
return (
14+
fieldList
15+
.filter((field) => field !== checkField)
16+
.filter((field) => field.id === checkField.id).length > 0
17+
);
18+
});
19+
20+
type SaveRequiredField = { detail: { previous: RequiredField; current: RequiredField } };
21+
type RequiredFieldEvent = { detail: RequiredField };
22+
23+
function hasDuplicate(checkField: RequiredField) {
24+
return !!duplicates.find((field) => field === checkField);
25+
}
26+
27+
function updateFields(fields: RequiredField[]) {
28+
dispatch('update', { json: JSON.stringify(fields) });
29+
}
30+
31+
function saveField(event: SaveRequiredField) {
32+
const newFields = [...fieldList];
33+
if (event.detail.previous.label === '' || event.detail.previous.id === '') {
34+
newFields.push(event.detail.current);
35+
enableNewField = false;
36+
} else {
37+
const fieldIndex: number = newFields.findIndex(
38+
(f) => f.label === event.detail.previous.label && f.id === event.detail.previous.id,
39+
);
40+
if (fieldIndex > -1) {
41+
newFields[fieldIndex] = event.detail.current;
42+
}
43+
}
44+
45+
updateFields(newFields);
46+
}
47+
48+
function deleteField(event: RequiredFieldEvent) {
49+
const newFields = fieldList.filter(
50+
(f) => f.label !== event.detail.label || f.id !== event.detail.id,
51+
);
52+
updateFields(newFields);
53+
}
54+
</script>
55+
56+
<fieldset class="border border-surface-300-600-token">
57+
<legend class="px-2 ml-2">
58+
Required Fields:
59+
<button
60+
type="button"
61+
class="text-primary-600-300-token hover:text-secondary-600-300-token"
62+
title="Add New Field"
63+
data-testid="required-field-new-btn"
64+
disabled={enableNewField}
65+
on:click={() => (enableNewField = !enableNewField)}
66+
>
67+
<i class="fa-solid fa-square-plus fa-xl"></i>
68+
</button>
69+
</legend>
70+
71+
{#if enableNewField}
72+
<RequiredFieldRow on:save={saveField} on:delete={() => (enableNewField = false)} />
73+
{/if}
74+
75+
{#each fieldList as field, index (`${index}-${field.label}-${field.id}`)}
76+
<RequiredFieldRow
77+
duplicate={hasDuplicate(field)}
78+
{field}
79+
on:save={saveField}
80+
on:delete={deleteField}
81+
/>
82+
{/each}
83+
84+
{#if duplicates.length > 0}
85+
<aside data-testid="validation-warn" class="alert variant-ghost-warning m-2">
86+
<i class="fa-solid fa-triangle-exclamation"></i>
87+
<div class="alert-message">
88+
<p>Fields with the same ID may not function properly.</p>
89+
</div>
90+
</aside>
91+
{/if}
92+
</fieldset>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script lang="ts">
2+
import { parseFieldsFromJSON } from '$lib/models/Connection';
3+
export let data: { cell: string } = { cell: '[]' };
4+
$: fields = parseFieldsFromJSON(data.cell);
5+
</script>
6+
7+
{#each fields as field, index (index)}
8+
<span class="badge variant-ghost-surface font-normal m-1"
9+
><span class="font-bold">{field.label}</span>:{field.id}</span
10+
>
11+
{/each}

src/lib/models/Connection.ts

+17
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,20 @@ export interface Connection {
55
subPrefix: string;
66
requiredFields: string;
77
}
8+
9+
export interface RequiredField {
10+
label: string;
11+
id: string;
12+
}
13+
14+
export function parseFieldsFromJSON(json: string) {
15+
if (json === '') return [];
16+
17+
try {
18+
const requiredFields: RequiredField[] = JSON.parse(json);
19+
return requiredFields;
20+
} catch (e) {
21+
console.error('Error parsing JSON required fields object.');
22+
return [];
23+
}
24+
}

src/routes/(picsure)/(authorized)/(admin)/admin/configuration/+page.svelte

+5-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import PrivilegeActions from '$lib/components/admin/configuration/cell/PrivilegeActions.svelte';
1414
import ConnectionActions from '$lib/components/admin/configuration/cell/ConnectionActions.svelte';
1515
import Application from '$lib/components/admin/configuration/cell/Application.svelte';
16+
import RequiredFields from '$lib/components/admin/configuration/cell/RequiredFields.svelte';
1617
1718
import { privileges, loadPrivileges } from '$lib/stores/Privileges';
1819
import { roles, loadRoles } from '$lib/stores/Roles';
@@ -49,7 +50,10 @@
4950
{ dataElement: 'requiredFields', label: 'Required fields', sort: true },
5051
{ dataElement: 'uuid', label: 'Actions' },
5152
],
52-
overrides: { uuid: ConnectionActions },
53+
overrides: {
54+
uuid: ConnectionActions,
55+
requiredFields: RequiredFields,
56+
},
5357
};
5458
5559
async function loadAppsAndPriv() {

0 commit comments

Comments
 (0)