Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ALS-6826] Add Required fields form values #378

Merged
merged 1 commit into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 15 additions & 29 deletions src/lib/components/admin/configuration/ConnectionForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,21 @@
import type { Connection } from '$lib/models/Connection';
import { addConnection, updateConnection } from '$lib/stores/Connections';

import RequiredFieldsList from './RequiredFieldsList.svelte';

export let connection: Connection | undefined = undefined;

let label: string = connection?.label || '';
let id: string = connection?.id || '';
let subPrefix: string = connection?.subPrefix || '';
let requiredFields: string = connection?.requiredFields || '';
let validationError: string = '';
let requiredFields: string = connection?.requiredFields || '[]';

async function saveConnection() {
try {
requiredFields !== '' && JSON.parse(requiredFields);
validationError = '';
} catch (_) {
validationError = 'The provided JSON has a syntax error.';
return;
}
type JSONEvent = { detail: { json: string } };
function updateRequiredFields(event: JSONEvent) {
requiredFields = event.detail.json;
}

async function saveConnection() {
let newConnection: Connection = {
id,
label,
Expand Down Expand Up @@ -90,31 +88,19 @@
/>
</label>

<label class="label">
<span>Required Fields:</span>
<textarea class="w-full input input-border" bind:value={requiredFields} />
</label>

{#if validationError}
<aside data-testid="validation-error" class="alert variant-ghost-error">
<div class="alert-message">
<p>{validationError}</p>
</div>
<div class="alert-actions">
<button on:click={() => (validationError = '')}>
<i class="fa-solid fa-xmark"></i>
<span class="sr-only">Close</span>
</button>
</div>
</aside>
{/if}
<RequiredFieldsList fields={requiredFields} on:update={updateRequiredFields} />

<div>
<button type="submit" class="btn variant-ghost-primary hover:variant-filled-primary">
<button
type="submit"
data-testid="connection-save-btn"
class="btn variant-ghost-primary hover:variant-filled-primary"
>
Save
</button>
<a
href="/admin/configuration"
data-testid="connection-cancel-btn"
class="btn variant-ghost-secondary hover:variant-filled-secondary"
>
Cancel
Expand Down
119 changes: 119 additions & 0 deletions src/lib/components/admin/configuration/RequiredFieldRow.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher<{
delete: RequiredField;
save: { previous: RequiredField; current: RequiredField };
}>();

import type { RequiredField } from '$lib/models/Connection';

export let field: RequiredField = { label: '', id: '' };
export let duplicate: boolean = false;

let label: string = field.label;
let id: string = field.id;

$: dirtyForm = field.id !== id || field.label !== label;
const newField: boolean = field.label === '' || field.id === '';
let edit: boolean = newField;

const saveField = () => {
const currentField = { label, id };
dispatch('save', { previous: field, current: currentField });
edit = false;
};
const resetField = () => {
label = field.label;
id = field.id;
};
const deleteField = () => dispatch('delete', { label, id });
const toggleEdit = () => (edit = !edit);
</script>

<div
class="flex gap-4 p-3 odd:bg-surface-100-800-token even:bg-surface-50-900-token"
data-testid={`required-field-row-${field.id ? field.id : 'new'}`}
>
<label class="label flex-1">
<span>Label:</span>
<input
type="text"
bind:value={label}
disabled={!edit}
class="input"
minlength="1"
maxlength="255"
/>
</label>
<label class="label flex-1">
<span>ID:</span>
<input
type="text"
bind:value={id}
disabled={!edit}
class="input {duplicate &&
'variant-ghost-warning border-warning-500-400-token focus:border-warning-700-200-token'}"
minlength="1"
maxlength="255"
/>
</label>
<div class="flex-none content-end py-2">
{#if edit}
<button
type="button"
title="Save"
data-testid="required-field-save-btn"
class="btn-icon-color"
disabled={!label || !id || !dirtyForm}
on:click={saveField}
>
<i class="fa-solid fa-floppy-disk fa-xl"></i>
<span class="sr-only">Save</span>
</button>
{#if dirtyForm}
<button
type="button"
title="Reset and cancel"
class="btn-icon-color"
data-testid="required-field-reset-btn"
on:click={resetField}
>
<i class="fa-solid fa-arrow-rotate-right fa-xl"></i>
<span class="sr-only">Reset and cancel</span>
</button>
{:else if !newField && !dirtyForm}
<button
type="button"
title="Cancel"
class="btn-icon-color"
data-testid="required-field-cancel-btn"
on:click={toggleEdit}
>
<i class="fa-solid fa-ban fa-xl"></i>
<span class="sr-only">Cancel</span>
</button>
{/if}
{:else}
<button
type="button"
title="Edit"
class="btn-icon-color"
data-testid="required-field-edit-btn"
on:click={toggleEdit}
>
<i class="fa-solid fa-pen fa-xl"></i>
<span class="sr-only">Edit</span>
</button>
{/if}
<button
type="button"
title="Delete"
class="btn-icon-color"
data-testid="required-field-delete-btn"
on:click={deleteField}
>
<i class="fa-solid fa-trash fa-xl"></i>
<span class="sr-only">Delete</span>
</button>
</div>
</div>
92 changes: 92 additions & 0 deletions src/lib/components/admin/configuration/RequiredFieldsList.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher<{ update: { json: string } }>();

import { type RequiredField, parseFieldsFromJSON } from '$lib/models/Connection';
import RequiredFieldRow from './RequiredFieldRow.svelte';

export let fields: string = '[]';

$: fieldList = parseFieldsFromJSON(fields);
$: enableNewField = fieldList.length === 0;
$: duplicates = fieldList.filter((checkField) => {
return (
fieldList
.filter((field) => field !== checkField)
.filter((field) => field.id === checkField.id).length > 0
);
});

type SaveRequiredFieldEvent = { detail: { previous: RequiredField; current: RequiredField } };
type RequiredFieldEvent = { detail: RequiredField };

function hasDuplicate(checkField: RequiredField) {
return !!duplicates.find((field) => field === checkField);
}

function updateFields(fields: RequiredField[]) {
dispatch('update', { json: JSON.stringify(fields) });
}

function saveField(event: SaveRequiredFieldEvent) {
const newFields = [...fieldList];
if (event.detail.previous.label === '' || event.detail.previous.id === '') {
newFields.push(event.detail.current);
enableNewField = false;
} else {
const fieldIndex: number = newFields.findIndex(
(f) => f.label === event.detail.previous.label && f.id === event.detail.previous.id,
);
if (fieldIndex > -1) {
newFields[fieldIndex] = event.detail.current;
}
}

updateFields(newFields);
}

function deleteField(event: RequiredFieldEvent) {
const newFields = fieldList.filter(
(f) => f.label !== event.detail.label || f.id !== event.detail.id,
);
updateFields(newFields);
}
</script>

<fieldset class="border border-surface-300-600-token">
<legend class="px-2 ml-2">
Required Fields:
<button
type="button"
class="text-primary-600-300-token hover:text-secondary-600-300-token"
title="Add New Field"
data-testid="required-field-new-btn"
disabled={enableNewField}
on:click={() => (enableNewField = !enableNewField)}
>
<i class="fa-solid fa-square-plus fa-xl"></i>
</button>
</legend>

{#if enableNewField}
<RequiredFieldRow on:save={saveField} on:delete={() => (enableNewField = false)} />
{/if}

{#each fieldList as field, index (`${index}-${field.label}-${field.id}`)}
<RequiredFieldRow
duplicate={hasDuplicate(field)}
{field}
on:save={saveField}
on:delete={deleteField}
/>
{/each}

{#if duplicates.length > 0}
<aside data-testid="validation-warn" class="alert variant-ghost-warning m-2">
<i class="fa-solid fa-triangle-exclamation"></i>
<div class="alert-message">
<p>Fields with the same ID may not function properly.</p>
</div>
</aside>
{/if}
</fieldset>
11 changes: 11 additions & 0 deletions src/lib/components/admin/configuration/cell/RequiredFields.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script lang="ts">
import { parseFieldsFromJSON } from '$lib/models/Connection';
export let data: { cell: string } = { cell: '[]' };
$: fields = parseFieldsFromJSON(data.cell);
</script>

{#each fields as field, index (index)}
<span class="badge variant-ghost-surface font-normal m-1"
><span class="font-bold">{field.label}</span>:{field.id}</span
>
{/each}
17 changes: 17 additions & 0 deletions src/lib/models/Connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,20 @@ export interface Connection {
subPrefix: string;
requiredFields: string;
}

export interface RequiredField {
label: string;
id: string;
}

export function parseFieldsFromJSON(json: string) {
if (json === '') return [];

try {
const requiredFields: RequiredField[] = JSON.parse(json);
return requiredFields;
} catch (e) {
console.error('Error parsing JSON required fields object.');
return [];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import PrivilegeActions from '$lib/components/admin/configuration/cell/PrivilegeActions.svelte';
import ConnectionActions from '$lib/components/admin/configuration/cell/ConnectionActions.svelte';
import Application from '$lib/components/admin/configuration/cell/Application.svelte';
import RequiredFields from '$lib/components/admin/configuration/cell/RequiredFields.svelte';

import { privileges, loadPrivileges } from '$lib/stores/Privileges';
import { roles, loadRoles } from '$lib/stores/Roles';
Expand Down Expand Up @@ -49,7 +50,10 @@
{ dataElement: 'requiredFields', label: 'Required fields', sort: true },
{ dataElement: 'uuid', label: 'Actions' },
],
overrides: { uuid: ConnectionActions },
overrides: {
uuid: ConnectionActions,
requiredFields: RequiredFields,
},
};

async function loadAppsAndPriv() {
Expand Down
Loading