Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,25 @@
}

.ai-error-message {
background-color: var(--color-warnPalette-100);
color: var(--color-warnPalette-100-contrast);
border-radius: 8px;
padding: 8px 8px 0;
margin: 4px 0;
}

@media (prefers-color-scheme: light) {
.ai-error-message {
background-color: var(--color-warnPalette-100);
color: var(--color-warnPalette-100-contrast);
}
}

@media (prefers-color-scheme: dark) {
.ai-error-message {
background-color: var(--color-warnDarkPalette-200);
color: var(--color-warnDarkPalette-200-contrast);
}
}

.ai-message ::ng-deep ol,
.ai-message ::ng-deep ul {
padding-left: 28px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,18 @@
}

.form__warning {
color: var(--color-warnPalette-500);
margin-top: -12px;
margin-bottom: 20px;
}

@media (prefers-color-scheme: light) {
.form__warning {
color: var(--color-warnPalette-500);
}
}

@media (prefers-color-scheme: dark) {
.form__warning {
color: var(--color-warnDarkPalette-500);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,17 +84,18 @@ export class DbTableWidgetsComponent implements OnInit {
<p class="post-paragraph">
A well-designed <strong>admin panel</strong> isn’t just about managing data — it’s about making that data easier to understand and interact with. By customizing how each field is displayed, you can turn raw database values into meaningful, user-friendly interfaces that save time and reduce errors.
</p>`;
// JSON5-formatted default params
public defaultParams = {
Boolean:
`// Display "Yes/No" buttons and specify "allow_null" in field structure:
// Use "false" to require that one of the buttons is selected;
// Use "true" if the field might be left unspecified.
`// Display "Yes/No" buttons with configurable options:
// - allow_null: Use "false" to require selection, "true" if field can be left unspecified
// - invert_colors: Swap the color scheme (typically green=Yes, red=No becomes red=Yes, green=No)

{
"structure": {
"allow_null": false
}
}`,
"allow_null": false,
"invert_colors": false
}
`,
Code:
`// provide language of code to highlight: 'html', 'css', 'typescript', 'yaml', 'markdown'
// example:
Expand Down Expand Up @@ -167,10 +168,13 @@ export class DbTableWidgetsComponent implements OnInit {
"allow_negative": true
}
`,
Number: `// Configure number display with unit conversion
Number: `// Configure number display with unit conversion and threshold validation
// Example units: "bytes", "meters", "seconds", "grams"
// threshold_min/threshold_max: Values outside these limits will be highlighted in red
{
"unit": null
"unit": null,
"threshold_min": null,
"threshold_max": null
}`,
Password:
`// provide algorithm to encrypt your password, one of:
Expand Down Expand Up @@ -224,7 +228,19 @@ export class DbTableWidgetsComponent implements OnInit {
}
]
}`,
String: `// No settings required`,
String: `// Optional validation for string values
// validate: Any validator.js method (e.g., "isEmail", "isURL", "isUUID", "isJSON", "isAlpha", "isNumeric")
// Full list: isEmail, isURL, isMACAddress, isIP, isIPRange, isFQDN, isBoolean, isIBAN, isBIC,
// isAlpha, isAlphanumeric, isNumeric, isPort, isLowercase, isUppercase, isAscii, isBase64,
// isHexadecimal, isHexColor, isRgbColor, isHSL, isMD5, isHash, isJWT, isJSON, isUUID,
// isMongoId, isCreditCard, isISBN, isISSN, isMobilePhone, isPostalCode, isEthereumAddress,
// isCurrency, isBtcAddress, isISO8601, isISO31661Alpha2, isISO31661Alpha3, isISO4217,
// isDataURI, isMagnetURI, isMimeType, isLatLong, isSlug, isStrongPassword, isTaxID, isVAT
// OR use "regex" with a regex parameter for custom pattern matching
{
"validate": null,
"regex": null
}`,
Textarea: `// provide number of strings to show.
{
"rows": 5
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@
padding: 8px 4px;
}

/* .widget-type__docs-link {
color: var(--color-accentedDarkPalette-500);
} */

.code-editor-box {
display: block;
border: 1px solid rgba(0, 0, 0, 0.38);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,29 @@
}

.saved-filters-list__first-time-button {
background: var(--color-accentedPalette-50);
border: 1px dashed var(--color-accentedPalette-300);
transition: background 0.3s ease;
}

.saved-filters-list__first-time-button:hover {
background: var(--color-accentedPalette-100) !important;
@media (prefers-color-scheme: light) {
.saved-filters-list__first-time-button {
background: var(--color-accentedPalette-50);
border: 1px dashed var(--color-accentedPalette-300);
}

.saved-filters-list__first-time-button:hover {
background: var(--color-accentedPalette-100) !important;
}
}

@media (prefers-color-scheme: dark) {
.saved-filters-list__first-time-button {
background: var(--color-accentedPalette-900);
border: 1px dashed var(--color-accentedPalette-600);
}

.saved-filters-list__first-time-button:hover {
background: var(--color-accentedPalette-800) !important;
}
}

/* .saved-filters-tabs {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,17 @@ export class BooleanFilterComponent extends BaseFilterFieldComponent {
super.ngOnInit();
this.connectionType = this._connections.currentConnection.type;
this.setBooleanValue();
this.isRadiogroup = (this.structure?.allow_null) || !!(this.widgetStructure?.widget_params?.structure?.allow_null);

// Parse widget parameters if available
let parsedParams = null;
if (this.widgetStructure?.widget_params) {
parsedParams = typeof this.widgetStructure.widget_params === 'string'
? JSON.parse(this.widgetStructure.widget_params)
: this.widgetStructure.widget_params;
}

// Check allow_null from either structure or widget params
this.isRadiogroup = (this.structure?.allow_null) || !!(parsedParams?.allow_null);
}

setBooleanValue() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,16 @@ export class BooleanEditComponent extends BaseEditFieldComponent {

this.onFieldChange.emit(this.value);

this.isRadiogroup = (this.structure?.allow_null) || !!(this.widgetStructure?.widget_params?.structure?.allow_null);
// Parse widget parameters if available
let parsedParams = null;
if (this.widgetStructure?.widget_params) {
parsedParams = typeof this.widgetStructure.widget_params === 'string'
? JSON.parse(this.widgetStructure.widget_params)
: this.widgetStructure.widget_params;
}

// Check allow_null from either structure or widget params
this.isRadiogroup = (this.structure?.allow_null) || !!(parsedParams?.allow_null);
}

onToggleChange(optionValue: boolean): void {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
.form-field {
position: relative;
width: 100%;
}

.long-textarea {
margin-bottom: 20px;
}

.counter {
position: absolute;
bottom: 8px;
font-size: 12px;
width: 90%;
background: #fff;
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
<mat-form-field class="form-field" appearance="outline">
<mat-label>{{normalizedLabel}}</mat-label>
<textarea matInput name="{{label}}-{{key}}"
#textareaField="ngModel"
class="long-textarea"
[rows]="rowsCount"
[maxlength]="maxLength"
textValidator
[validateType]="validateType"
[regexPattern]="regexPattern"
attr.data-testid="record-{{label}}-long-text"
[required]="required" [disabled]="disabled" [readonly]="readonly"
[(ngModel)]="value" (ngModelChange)="onFieldChange.emit($event)">
</textarea>
<div *ngIf="maxLength && maxLength > 0 && value && (maxLength - value.length) < 100" class="counter">{{value.length}} / {{maxLength}}</div>
<mat-error *ngIf="textareaField.errors?.['required']">This field is required.</mat-error>
<mat-error *ngIf="textareaField.errors?.['maxlength']">Maximum length is {{maxLength}} characters.</mat-error>
<mat-error *ngIf="textareaField.errors?.['invalidPattern'] || textareaField.errors?.[('invalid' + validateType)]">{{getValidationErrorMessage()}}</mat-error>
</mat-form-field>
Original file line number Diff line number Diff line change
@@ -1,29 +1,78 @@
import { Component, Input } from '@angular/core';
import { Component, Input, OnInit } from '@angular/core';

import { BaseEditFieldComponent } from '../base-row-field/base-row-field.component';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { TextValidatorDirective } from 'src/app/directives/text-validator.directive';

@Component({
selector: 'app-edit-long-text',
templateUrl: './long-text.component.html',
styleUrls: ['./long-text.component.css'],
imports: [MatFormFieldModule, MatInputModule, FormsModule]
imports: [CommonModule, MatFormFieldModule, MatInputModule, FormsModule, TextValidatorDirective]
})
export class LongTextEditComponent extends BaseEditFieldComponent {
export class LongTextEditComponent extends BaseEditFieldComponent implements OnInit {
@Input() value: string;

static type = 'text';
public rowsCount: string;
maxLength: number | null = null;
validateType: string | null = null;
regexPattern: string | null = null;

ngOnInit(): void {
override ngOnInit(): void {
super.ngOnInit();

// Use character_maximum_length from the field structure if available
if (this.structure && this.structure.character_maximum_length) {
this.maxLength = this.structure.character_maximum_length;
}

// Parse widget parameters
if (this.widgetStructure && this.widgetStructure.widget_params) {
this.rowsCount = this.widgetStructure.widget_params.rows
const params = typeof this.widgetStructure.widget_params === 'string'
? JSON.parse(this.widgetStructure.widget_params)
: this.widgetStructure.widget_params;

this.rowsCount = params.rows || '4';
this.validateType = params.validate || null;
this.regexPattern = params.regex || null;
} else {
this.rowsCount = '4'
};
this.rowsCount = '4';
}
}

getValidationErrorMessage(): string {
if (!this.validateType) {
return '';
}

if (this.validateType === 'regex') {
return 'Value doesn\'t match the required pattern';
}

// Create user-friendly messages for common validators
const messages = {
isEmail: 'Invalid email address',
isURL: 'Invalid URL',
isIP: 'Invalid IP address',
isUUID: 'Invalid UUID',
isJSON: 'Invalid JSON',
isCreditCard: 'Invalid credit card number',
isISBN: 'Invalid ISBN',
isAlpha: 'Should contain only letters',
isNumeric: 'Should contain only numbers',
isAlphanumeric: 'Should contain only letters and numbers',
isHexColor: 'Invalid hex color',
isBase64: 'Invalid Base64 string',
isMobilePhone: 'Invalid mobile phone number',
isMACAddress: 'Invalid MAC address',
isPostalCode: 'Invalid postal code',
isCurrency: 'Invalid currency format'
};

return messages[this.validateType] || `Invalid ${this.validateType}`;
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.text-form-field {
width: 100%;
}

.counter {
margin-right: 16px;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
<mat-form-field class="text-form-field" appearance="outline">
<mat-label>{{normalizedLabel}}</mat-label>
<input matInput type="text" name="{{label}}-{{key}}"
#textField="ngModel"
[required]="required" [disabled]="disabled" [readonly]="readonly"
[maxlength]="maxLength"
textValidator
[validateType]="validateType"
[regexPattern]="regexPattern"
attr.data-testid="record-{{label}}-text"
[(ngModel)]="value" (ngModelChange)="onFieldChange.emit($event)">
<div matSuffix *ngIf="maxLength && maxLength > 0 && value && (maxLength - value.length) < 100" class="counter">{{value.length}} / {{maxLength}}</div>
<mat-error *ngIf="textField.errors?.['required']">This field is required.</mat-error>
<mat-error *ngIf="textField.errors?.['maxlength']">Maximum length is {{maxLength}} characters.</mat-error>
<mat-error *ngIf="textField.errors?.['invalidPattern'] || textField.errors?.[('invalid' + validateType)]">{{getValidationErrorMessage()}}</mat-error>
</mat-form-field>
Loading