From c747b0da79c9ca069b19c327f4b0968143d7156c Mon Sep 17 00:00:00 2001 From: Andrii Kostenko Date: Sat, 2 Aug 2025 19:00:31 +0000 Subject: [PATCH 1/7] Fix dark theme gauge display and sort widgets alphabetically MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix dark theme text visibility for range/gauge widget (black on black issue) - Fix Material progress bar display with proper CSS variables - Sort all widget configurations alphabetically in: - table-display-types.ts - record-edit-types.ts - filter-types.ts - db-table-widgets.component.ts defaultParams - Add standalone: true to range component for Angular 19 compatibility - Ensure progress value is bounded between 0-100 πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- backend/src/enums/widget-type.enum.ts | 3 +- .../db-table-widgets.component.ts | 167 +++++++++--------- .../range/range.component.css | 108 +++++++++++ .../range/range.component.html | 17 ++ .../range/range.component.ts | 56 ++++++ .../range/range.component.css | 63 +++++++ .../range/range.component.html | 8 + .../range/range.component.ts | 63 +++++++ frontend/src/app/consts/filter-types.ts | 14 +- frontend/src/app/consts/record-edit-types.ts | 28 +-- .../src/app/consts/table-display-types.ts | 28 +-- .../shared/enums/table-widget-type.enum.ts | 1 + 12 files changed, 442 insertions(+), 114 deletions(-) create mode 100644 frontend/src/app/components/ui-components/record-edit-fields/range/range.component.css create mode 100644 frontend/src/app/components/ui-components/record-edit-fields/range/range.component.html create mode 100644 frontend/src/app/components/ui-components/record-edit-fields/range/range.component.ts create mode 100644 frontend/src/app/components/ui-components/table-display-fields/range/range.component.css create mode 100644 frontend/src/app/components/ui-components/table-display-fields/range/range.component.html create mode 100644 frontend/src/app/components/ui-components/table-display-fields/range/range.component.ts diff --git a/backend/src/enums/widget-type.enum.ts b/backend/src/enums/widget-type.enum.ts index 7af6c010e..a02e8989b 100644 --- a/backend/src/enums/widget-type.enum.ts +++ b/backend/src/enums/widget-type.enum.ts @@ -19,5 +19,6 @@ export enum WidgetTypeEnum { Code = 'Code', Phone = 'Phone', Country = 'Country', - Color = 'Color' + Color = 'Color', + Range = 'Range' } diff --git a/frontend/src/app/components/dashboard/db-table-widgets/db-table-widgets.component.ts b/frontend/src/app/components/dashboard/db-table-widgets/db-table-widgets.component.ts index 76308ef37..572e74c15 100644 --- a/frontend/src/app/components/dashboard/db-table-widgets/db-table-widgets.component.ts +++ b/frontend/src/app/components/dashboard/db-table-widgets/db-table-widgets.component.ts @@ -95,56 +95,36 @@ export class DbTableWidgetsComponent implements OnInit { "allow_null": false } }`, - Date: `// No settings required`, - Default: `// No settings required`, - Time: `// No settings required`, - DateTime: `// No settings required`, - JSON: `// No settings required`, - Textarea: `// provide number of strings to show. -{ - "rows": 5 -}`, - String: `// No settings required`, - Readonly: `// No settings required`, - Number: `// Configure number display with unit conversion -// Example units: "bytes", "meters", "seconds", "grams" + Code: +`// provide language of code to highlight: 'html', 'css', 'typescript', 'yaml', 'markdown' +// example: { - "unit": null -}`, - Select: -`// provide array of options to map database value (key 'value') in human readable value (key 'label'); -// for example: -// AK => Alaska, -// CA => California + "language": "html" +} +`, + Color: `// Optional: Specify output format for color values +// Supported formats: "hex", "hex_hash" (default), "rgb", "hsl" +// Example configuration: { - "allow_null": true, - "options": [ - { - "value": "UA", - "label": "πŸ‡ΊπŸ‡¦ Ukraine" - }, - { - "value": "PL", - "label": "πŸ‡΅πŸ‡± Poland" - }, - { - "value": "US", - "label": "πŸ‡ΊπŸ‡Έ United States" - } - ] -}`, - Password: -`// provide algorithm to encrypt your password, one of: -//sha1, sha3, sha224, sha256, sha512, sha384, bcrypt, scrypt, argon2, pbkdf2. -// example: + "format": "hex_hash" // Will display colors as "#FF5733" +} +// Format options: +// - "hex": Display as "FF5733" (no hash) +// - "hex_hash": Display as "#FF5733" (default) +// - "rgb": Display as "rgb(255, 87, 51)" +// - "hsl": Display as "hsl(9, 100%, 60%)"`, + Country: `// Configure country display options +// Example: { - "encrypt": true, - "algorithm": "sha256" + "show_flag": true, + "allow_null": false } - `, + Date: `// No settings required`, + DateTime: `// No settings required`, + Default: `// No settings required`, File: `// provide type of file: 'hex', 'base64' or 'file' // example: @@ -152,11 +132,11 @@ export class DbTableWidgetsComponent implements OnInit { "type": "hex" } `, - Code: -`// provide language of code to highlight: 'html', 'css', 'typescript', 'yaml', 'markdown' -// example: + Foreign_key: `// Provide settings for foreign key widget { - "language": "html" + "column_name": "", // copy the name of the column you selected + "referenced_column_name": "", + "referenced_table_name": "" } `, Image: @@ -166,53 +146,80 @@ export class DbTableWidgetsComponent implements OnInit { "height": 100 } `, - URL: `// No settings required`, - Phone: -`// Configure international phone number widget + JSON: `// No settings required`, + Money: `// Configure money widget settings // example: { - "preferred_countries": ["US", "GB", "CA"], - "enable_placeholder": true, - "phone_validation": true + "default_currency": "USD", + "show_currency_selector": false, + "decimal_places": 2, + "allow_negative": true } `, - Country: `// Configure country display options -// Example: + Number: `// Configure number display with unit conversion +// Example units: "bytes", "meters", "seconds", "grams" { - "show_flag": true, - "allow_null": false -} -`, - Foreign_key: `// Provide settings for foreign key widget + "unit": null +}`, + Password: +`// provide algorithm to encrypt your password, one of: +//sha1, sha3, sha224, sha256, sha512, sha384, bcrypt, scrypt, argon2, pbkdf2. +// example: + { - "column_name": "", // copy the name of the column you selected - "referenced_column_name": "", - "referenced_table_name": "" + "encrypt": true, + "algorithm": "sha256" } + `, - Money: `// Configure money widget settings + Phone: +`// Configure international phone number widget // example: { - "default_currency": "USD", - "show_currency_selector": false, - "decimal_places": 2, - "allow_negative": true + "preferred_countries": ["US", "GB", "CA"], + "enable_placeholder": true, + "phone_validation": true } `, - Color: `// Optional: Specify output format for color values -// Supported formats: "hex", "hex_hash" (default), "rgb", "hsl" -// Example configuration: - + Range: `// Configure the minimum and maximum values for the range +// Default: min = 0, max = 100 { - "format": "hex_hash" // Will display colors as "#FF5733" + "min": 0, + "max": 100 } +`, + Readonly: `// No settings required`, + Select: +`// provide array of options to map database value (key 'value') in human readable value (key 'label'); +// for example: +// AK => Alaska, +// CA => California -// Format options: -// - "hex": Display as "FF5733" (no hash) -// - "hex_hash": Display as "#FF5733" (default) -// - "rgb": Display as "rgb(255, 87, 51)" -// - "hsl": Display as "hsl(9, 100%, 60%)"`, - UUID: `// Configure UUID generation version and parameters +{ + "allow_null": true, + "options": [ + { + "value": "UA", + "label": "πŸ‡ΊπŸ‡¦ Ukraine" + }, + { + "value": "PL", + "label": "πŸ‡΅πŸ‡± Poland" + }, + { + "value": "US", + "label": "πŸ‡ΊπŸ‡Έ United States" + } + ] +}`, + String: `// No settings required`, + Textarea: `// provide number of strings to show. +{ + "rows": 5 +}`, + Time: `// No settings required`, + URL: `// No settings required`, + UUID: `// Configure UUID generation version and parameters // Available versions: "v1", "v3", "v4" (default), "v5", "v7" // For v3/v5: provide namespace and optionally name { @@ -220,7 +227,7 @@ export class DbTableWidgetsComponent implements OnInit { "namespace": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "name": "" } -` +`, } constructor( diff --git a/frontend/src/app/components/ui-components/record-edit-fields/range/range.component.css b/frontend/src/app/components/ui-components/record-edit-fields/range/range.component.css new file mode 100644 index 000000000..cf8328c45 --- /dev/null +++ b/frontend/src/app/components/ui-components/record-edit-fields/range/range.component.css @@ -0,0 +1,108 @@ +.range-edit-container { + width: 100%; + display: flex; + flex-direction: column; + gap: 4px; +} + +.range-labels { + display: flex; + justify-content: space-between; + font-size: 12px; + color: rgba(0, 0, 0, 0.6); + padding: 0 2px; +} + +.range-current { + font-weight: 600; + color: rgba(0, 0, 0, 0.87); +} + +.range-input { + width: 100%; + height: 6px; + -webkit-appearance: none; + appearance: none; + background: transparent; + outline: none; + cursor: pointer; +} + +.range-input::-webkit-slider-track { + width: 100%; + height: 6px; + background: #e0e0e0; + border-radius: 3px; +} + +.range-input::-moz-range-track { + width: 100%; + height: 6px; + background: #e0e0e0; + border-radius: 3px; +} + +.range-input::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 18px; + height: 18px; + background: #1976d2; + border-radius: 50%; + cursor: pointer; + margin-top: -6px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + transition: all 0.2s ease; +} + +.range-input::-moz-range-thumb { + width: 18px; + height: 18px; + background: #1976d2; + border-radius: 50%; + cursor: pointer; + border: none; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + transition: all 0.2s ease; +} + +.range-input::-webkit-slider-thumb:hover { + transform: scale(1.1); + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.3); +} + +.range-input::-moz-range-thumb:hover { + transform: scale(1.1); + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.3); +} + +.range-input:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.range-input:disabled::-webkit-slider-thumb { + cursor: not-allowed; +} + +.range-input:disabled::-moz-range-thumb { + cursor: not-allowed; +} + +@media (prefers-color-scheme: dark) { + .range-labels { + color: rgba(255, 255, 255, 0.6); + } + + .range-current { + color: rgba(255, 255, 255, 0.87); + } + + .range-input::-webkit-slider-track { + background: #424242; + } + + .range-input::-moz-range-track { + background: #424242; + } +} \ No newline at end of file diff --git a/frontend/src/app/components/ui-components/record-edit-fields/range/range.component.html b/frontend/src/app/components/ui-components/record-edit-fields/range/range.component.html new file mode 100644 index 000000000..7a67fb30a --- /dev/null +++ b/frontend/src/app/components/ui-components/record-edit-fields/range/range.component.html @@ -0,0 +1,17 @@ +
+
+ {{ min }} + {{ value || min }} + {{ max }} +
+ +
\ No newline at end of file diff --git a/frontend/src/app/components/ui-components/record-edit-fields/range/range.component.ts b/frontend/src/app/components/ui-components/record-edit-fields/range/range.component.ts new file mode 100644 index 000000000..40084b692 --- /dev/null +++ b/frontend/src/app/components/ui-components/record-edit-fields/range/range.component.ts @@ -0,0 +1,56 @@ +import { Component, ElementRef, Input, ViewChild } 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'; + +@Component({ + selector: 'app-range-edit', + templateUrl: './range.component.html', + styleUrls: ['./range.component.css'], + imports: [ + CommonModule, + FormsModule, + MatFormFieldModule, + MatInputModule + ], +}) +export class RangeEditComponent extends BaseEditFieldComponent { + @ViewChild('rangeInput') rangeInput: ElementRef; + @Input() value: number; + static type = 'range'; + + public min: number = 0; + public max: number = 100; + + override ngOnInit(): void { + super.ngOnInit(); + this._parseWidgetParams(); + } + + ngOnChanges(): void { + this._parseWidgetParams(); + } + + public onValueChange(newValue: number): void { + this.value = newValue; + this.onFieldChange.emit(this.value); + } + + private _parseWidgetParams(): void { + if (this.widgetStructure?.widget_params) { + try { + const params = this.widgetStructure.widget_params; + if (params.min !== undefined) { + this.min = Number(params.min) || 0; + } + if (params.max !== undefined) { + this.max = Number(params.max) || 100; + } + } catch (error) { + console.error('Failed to parse widget params:', error); + } + } + } +} \ No newline at end of file diff --git a/frontend/src/app/components/ui-components/table-display-fields/range/range.component.css b/frontend/src/app/components/ui-components/table-display-fields/range/range.component.css new file mode 100644 index 000000000..136f0fa09 --- /dev/null +++ b/frontend/src/app/components/ui-components/table-display-fields/range/range.component.css @@ -0,0 +1,63 @@ +.range-display-container { + display: flex; + flex-direction: column; + gap: 4px; + padding: 4px 0; +} + +.range-value-label { + font-size: 14px; + color: rgba(0, 0, 0, 0.87); + font-weight: 500; +} + +.range-progress-bar { + height: 8px; + border-radius: 4px; +} + +/* Material progress bar specific styles */ +.range-progress-bar.mat-mdc-progress-bar { + height: 8px; +} + +/* Light theme progress bar styles */ +.range-progress-bar { + --mdc-linear-progress-active-indicator-color: #1976d2; + --mdc-linear-progress-track-color: #e0e0e0; + --mdc-linear-progress-active-indicator-height: 8px; + --mdc-linear-progress-track-height: 8px; +} + +/* Override Material default styles */ +.range-progress-bar ::ng-deep .mdc-linear-progress { + height: 8px !important; +} + +.range-progress-bar ::ng-deep .mdc-linear-progress__bar { + height: 8px !important; +} + +.range-progress-bar ::ng-deep .mdc-linear-progress__bar-inner { + border-top-width: 8px !important; +} + +.range-progress-bar ::ng-deep .mdc-linear-progress__buffer { + height: 8px !important; +} + +.range-progress-bar ::ng-deep .mdc-linear-progress__buffer-bar { + height: 8px !important; +} + +/* Dark theme styles */ +@media (prefers-color-scheme: dark) { + .range-value-label { + color: rgba(255, 255, 255, 0.87); + } + + .range-progress-bar { + --mdc-linear-progress-active-indicator-color: #90caf9; + --mdc-linear-progress-track-color: rgba(255, 255, 255, 0.12); + } +} \ No newline at end of file diff --git a/frontend/src/app/components/ui-components/table-display-fields/range/range.component.html b/frontend/src/app/components/ui-components/table-display-fields/range/range.component.html new file mode 100644 index 000000000..673c6a710 --- /dev/null +++ b/frontend/src/app/components/ui-components/table-display-fields/range/range.component.html @@ -0,0 +1,8 @@ +
+
{{ displayValue }}
+ + +
\ No newline at end of file diff --git a/frontend/src/app/components/ui-components/table-display-fields/range/range.component.ts b/frontend/src/app/components/ui-components/table-display-fields/range/range.component.ts new file mode 100644 index 000000000..91d685e7b --- /dev/null +++ b/frontend/src/app/components/ui-components/table-display-fields/range/range.component.ts @@ -0,0 +1,63 @@ +import { Component, Input, OnInit, OnChanges } from '@angular/core'; +import { BaseTableDisplayFieldComponent } from '../base-table-display-field/base-table-display-field.component'; +import { CommonModule } from '@angular/common'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; + +@Component({ + selector: 'app-range-display', + standalone: true, + templateUrl: './range.component.html', + styleUrls: ['./range.component.css'], + imports: [ + CommonModule, + MatProgressBarModule + ], +}) +export class RangeDisplayComponent extends BaseTableDisplayFieldComponent implements OnInit, OnChanges { + @Input() declare value: number; + static type = 'range'; + + public min: number = 0; + public max: number = 100; + public displayValue: string = ''; + + ngOnInit(): void { + this._parseWidgetParams(); + this._updateDisplayValue(); + } + + ngOnChanges(): void { + this._parseWidgetParams(); + this._updateDisplayValue(); + } + + public getProgressValue(): number { + const numValue = Number(this.value) || 0; + const range = this.max - this.min; + if (range === 0) return 0; + const progress = ((numValue - this.min) / range) * 100; + // Ensure progress is between 0 and 100 + return Math.max(0, Math.min(100, progress)); + } + + private _parseWidgetParams(): void { + if (this.widgetStructure?.widget_params) { + try { + const params = this.widgetStructure.widget_params; + if (params.min !== undefined) { + this.min = Number(params.min) || 0; + } + if (params.max !== undefined) { + this.max = Number(params.max) || 100; + } + } catch (error) { + console.error('Failed to parse widget params:', error); + } + } + } + + private _updateDisplayValue(): void { + const numValue = Number(this.value) || 0; + this.displayValue = `${numValue} / ${this.max}`; + } +} \ No newline at end of file diff --git a/frontend/src/app/consts/filter-types.ts b/frontend/src/app/consts/filter-types.ts index 42ceaa095..6e31ede37 100644 --- a/frontend/src/app/consts/filter-types.ts +++ b/frontend/src/app/consts/filter-types.ts @@ -20,18 +20,18 @@ import { TimeIntervalFilterComponent } from '../components/ui-components/filter- export const UIwidgets = { Default: '', Boolean: BooleanFilterComponent, + Country: CountryFilterComponent, Date: DateFilterComponent, - Time: TimeFilterComponent, DateTime: DateTimeFilterComponent, + File: FileFilterComponent, JSON: JsonEditorFilterComponent, - Textarea: LongTextFilterComponent, - String: TextFilterComponent, - Readonly: StaticTextFilterComponent, Number: NumberFilterComponent, - Select: SelectFilterComponent, Password: PasswordFilterComponent, - File: FileFilterComponent, - Country: CountryFilterComponent + Readonly: StaticTextFilterComponent, + Select: SelectFilterComponent, + String: TextFilterComponent, + Textarea: LongTextFilterComponent, + Time: TimeFilterComponent, } export const filterTypes = { diff --git a/frontend/src/app/consts/record-edit-types.ts b/frontend/src/app/consts/record-edit-types.ts index 71d01dba7..5b6973dbd 100644 --- a/frontend/src/app/consts/record-edit-types.ts +++ b/frontend/src/app/consts/record-edit-types.ts @@ -16,6 +16,7 @@ import { NumberEditComponent } from 'src/app/components/ui-components/record-edi import { PasswordEditComponent } from '../components/ui-components/record-edit-fields/password/password.component'; import { PhoneEditComponent } from '../components/ui-components/record-edit-fields/phone/phone.component'; import { PointEditComponent } from 'src/app/components/ui-components/record-edit-fields/point/point.component'; +import { RangeEditComponent } from '../components/ui-components/record-edit-fields/range/range.component'; import { SelectEditComponent } from '../components/ui-components/record-edit-fields/select/select.component'; import { StaticTextEditComponent } from '../components/ui-components/record-edit-fields/static-text/static-text.component'; import { TextEditComponent } from 'src/app/components/ui-components/record-edit-fields/text/text.component'; @@ -33,25 +34,26 @@ export const defaultTimestampValues = { export const UIwidgets = { Default: '', Boolean: BooleanEditComponent, + Code: CodeEditComponent, + Color: ColorEditComponent, + Country: CountryEditComponent, Date: DateEditComponent, - Time: TimeEditComponent, DateTime: DateTimeEditComponent, + File: FileEditComponent, + Foreign_key: ForeignKeyEditComponent, + Image: ImageEditComponent, JSON: JsonEditorEditComponent, - Textarea: LongTextEditComponent, - String: TextEditComponent, - Readonly: StaticTextEditComponent, + Money: MoneyEditComponent, Number: NumberEditComponent, - Select: SelectEditComponent, Password: PasswordEditComponent, - File: FileEditComponent, - Code: CodeEditComponent, - Image: ImageEditComponent, - URL: UrlEditComponent, - Country: CountryEditComponent, Phone: PhoneEditComponent, - Money: MoneyEditComponent, - Foreign_key: ForeignKeyEditComponent, - Color: ColorEditComponent, + Range: RangeEditComponent, + Readonly: StaticTextEditComponent, + Select: SelectEditComponent, + String: TextEditComponent, + Textarea: LongTextEditComponent, + Time: TimeEditComponent, + URL: UrlEditComponent, UUID: UuidEditComponent, } diff --git a/frontend/src/app/consts/table-display-types.ts b/frontend/src/app/consts/table-display-types.ts index 804a40f75..5a4642b9c 100644 --- a/frontend/src/app/consts/table-display-types.ts +++ b/frontend/src/app/consts/table-display-types.ts @@ -15,6 +15,7 @@ import { NumberDisplayComponent } from '../components/ui-components/table-displa import { PasswordDisplayComponent } from '../components/ui-components/table-display-fields/password/password.component'; import { PhoneDisplayComponent } from '../components/ui-components/table-display-fields/phone/phone.component'; import { PointDisplayComponent } from '../components/ui-components/table-display-fields/point/point.component'; +import { RangeDisplayComponent } from '../components/ui-components/table-display-fields/range/range.component'; import { SelectDisplayComponent } from '../components/ui-components/table-display-fields/select/select.component'; import { StaticTextDisplayComponent } from '../components/ui-components/table-display-fields/static-text/static-text.component'; import { TextDisplayComponent } from 'src/app/components/ui-components/table-display-fields/text/text.component'; @@ -26,25 +27,26 @@ import { UuidDisplayComponent } from '../components/ui-components/table-display- export const UIwidgets = { Default: '', Boolean: BooleanDisplayComponent, + Code: CodeDisplayComponent, + Color: ColorDisplayComponent, + Country: CountryDisplayComponent, Date: DateDisplayComponent, - Time: TimeDisplayComponent, DateTime: DateTimeDisplayComponent, + File: FileDisplayComponent, + Foreign_key: ForeignKeyDisplayComponent, + Image: ImageDisplayComponent, JSON: JsonEditorDisplayComponent, - Textarea: LongTextDisplayComponent, - String: TextDisplayComponent, - Readonly: StaticTextDisplayComponent, + Money: MoneyDisplayComponent, Number: NumberDisplayComponent, - Select: SelectDisplayComponent, Password: PasswordDisplayComponent, - File: FileDisplayComponent, - Code: CodeDisplayComponent, - Image: ImageDisplayComponent, - URL: UrlDisplayComponent, - Country: CountryDisplayComponent, Phone: PhoneDisplayComponent, - Money: MoneyDisplayComponent, - Foreign_key: ForeignKeyDisplayComponent, - Color: ColorDisplayComponent, + Range: RangeDisplayComponent, + Readonly: StaticTextDisplayComponent, + Select: SelectDisplayComponent, + String: TextDisplayComponent, + Textarea: LongTextDisplayComponent, + Time: TimeDisplayComponent, + URL: UrlDisplayComponent, UUID: UuidDisplayComponent, } diff --git a/shared-code/src/data-access-layer/shared/enums/table-widget-type.enum.ts b/shared-code/src/data-access-layer/shared/enums/table-widget-type.enum.ts index eac6aabeb..71db0bfcd 100644 --- a/shared-code/src/data-access-layer/shared/enums/table-widget-type.enum.ts +++ b/shared-code/src/data-access-layer/shared/enums/table-widget-type.enum.ts @@ -20,4 +20,5 @@ export enum TableWidgetTypeEnum { Phone = 'Phone', Country = 'Country', Color = 'Color', + Range = 'Range', } From 56fc738fdaa7831646210b186a4a65e2a39e52d9 Mon Sep 17 00:00:00 2001 From: Andrii Kostenko Date: Fri, 8 Aug 2025 12:18:49 +0000 Subject: [PATCH 2/7] padding for range, country ordering --- .../record-edit-fields/country/country.component.ts | 5 +++-- .../record-edit-fields/range/range.component.css | 12 ++++++++++++ .../record-edit-fields/range/range.component.html | 1 + frontend/tsconfig.json | 2 +- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/components/ui-components/record-edit-fields/country/country.component.ts b/frontend/src/app/components/ui-components/record-edit-fields/country/country.component.ts index d95584b9d..5224b6709 100644 --- a/frontend/src/app/components/ui-components/record-edit-fields/country/country.component.ts +++ b/frontend/src/app/components/ui-components/record-edit-fields/country/country.component.ts @@ -85,7 +85,8 @@ export class CountryEditComponent extends BaseEditFieldComponent { } displayFn(country: any): string { - return country ? country.label : ''; + if (!country) return ''; + return this.showFlag && country.flag ? `${country.flag} ${country.label}` : country.label; } private loadCountries(): void { @@ -93,7 +94,7 @@ export class CountryEditComponent extends BaseEditFieldComponent { value: country.code, label: country.name, flag: getCountryFlag(country.code) - })); + })).toSorted((a, b) => a.label.localeCompare(b.label)); if (this.widgetStructure?.widget_params?.allow_null || this.structure?.allow_null) { this.countries = [{ value: null, label: '', flag: '' }, ...this.countries]; diff --git a/frontend/src/app/components/ui-components/record-edit-fields/range/range.component.css b/frontend/src/app/components/ui-components/record-edit-fields/range/range.component.css index cf8328c45..80d15c997 100644 --- a/frontend/src/app/components/ui-components/record-edit-fields/range/range.component.css +++ b/frontend/src/app/components/ui-components/record-edit-fields/range/range.component.css @@ -3,6 +3,14 @@ display: flex; flex-direction: column; gap: 4px; + padding-bottom: 16px; +} + +.range-label { + font-size: 14px; + color: rgba(0, 0, 0, 0.87); + margin-bottom: 8px; + display: block; } .range-labels { @@ -90,6 +98,10 @@ } @media (prefers-color-scheme: dark) { + .range-label { + color: rgba(255, 255, 255, 0.87); + } + .range-labels { color: rgba(255, 255, 255, 0.6); } diff --git a/frontend/src/app/components/ui-components/record-edit-fields/range/range.component.html b/frontend/src/app/components/ui-components/record-edit-fields/range/range.component.html index 7a67fb30a..8c3116ee8 100644 --- a/frontend/src/app/components/ui-components/record-edit-fields/range/range.component.html +++ b/frontend/src/app/components/ui-components/record-edit-fields/range/range.component.html @@ -1,4 +1,5 @@
+ {{ normalizedLabel }} {{ required ? '*' : '' }}
{{ min }} {{ value || min }} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index b0d4a4149..85ae6e11f 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -17,7 +17,7 @@ "node_modules/@types" ], "lib": [ - "es2019", + "es2023", "dom" ], "skipLibCheck": true From d664832a2b3ee50c7c9ec71ed726a42706707f40 Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Sun, 10 Aug 2025 12:30:44 +0300 Subject: [PATCH 3/7] range widget: increase bottom spacing --- .../record-edit-fields/range/range.component.css | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/components/ui-components/record-edit-fields/range/range.component.css b/frontend/src/app/components/ui-components/record-edit-fields/range/range.component.css index 80d15c997..74aa047b1 100644 --- a/frontend/src/app/components/ui-components/record-edit-fields/range/range.component.css +++ b/frontend/src/app/components/ui-components/record-edit-fields/range/range.component.css @@ -3,7 +3,7 @@ display: flex; flex-direction: column; gap: 4px; - padding-bottom: 16px; + padding-bottom: 28px; } .range-label { @@ -101,19 +101,19 @@ .range-label { color: rgba(255, 255, 255, 0.87); } - + .range-labels { color: rgba(255, 255, 255, 0.6); } - + .range-current { color: rgba(255, 255, 255, 0.87); } - + .range-input::-webkit-slider-track { background: #424242; } - + .range-input::-moz-range-track { background: #424242; } From bf542c1993766e049c30eb1698cdd93ac455e73e Mon Sep 17 00:00:00 2001 From: Andrii Kostenko Date: Sun, 10 Aug 2025 09:57:29 +0000 Subject: [PATCH 4/7] various widget improvements --- .../db-table-widgets.component.ts | 24 +++++++++++-- .../country/country.component.css | 19 +++++++++++ .../country/country.component.html | 14 +++++--- .../country/country.component.ts | 17 ++++++++-- .../date-time/date-time.component.ts | 34 +++++++++++++++++-- .../date/date.component.ts | 34 +++++++++++++++++-- .../image/image.component.html | 2 +- .../image/image.component.ts | 9 ++++- .../url/url.component.html | 2 +- .../table-display-fields/url/url.component.ts | 9 ++++- .../uuid/uuid.component.css | 3 -- 11 files changed, 146 insertions(+), 21 deletions(-) diff --git a/frontend/src/app/components/dashboard/db-table-widgets/db-table-widgets.component.ts b/frontend/src/app/components/dashboard/db-table-widgets/db-table-widgets.component.ts index 572e74c15..c00565e5d 100644 --- a/frontend/src/app/components/dashboard/db-table-widgets/db-table-widgets.component.ts +++ b/frontend/src/app/components/dashboard/db-table-widgets/db-table-widgets.component.ts @@ -122,8 +122,16 @@ export class DbTableWidgetsComponent implements OnInit { "allow_null": false } `, - Date: `// No settings required`, - DateTime: `// No settings required`, + Date: `// Configure date display options +// formatDistance: Shows relative time (e.g., "2 hours ago") for dates within the last 24 hours +{ + "formatDistance": true +}`, + DateTime: `// Configure datetime display options +// formatDistance: Shows relative time (e.g., "2 hours ago") for dates within the last 24 hours +{ + "formatDistance": true +}`, Default: `// No settings required`, File: `// provide type of file: 'hex', 'base64' or 'file' @@ -141,7 +149,12 @@ export class DbTableWidgetsComponent implements OnInit { `, Image: `// provide image height in px to dispaly in table +// prefix: optional URL prefix to prepend to image source // example: +// { +// "height": 100, +// "prefix": "https://example.com/images/" +// } { "height": 100 } @@ -218,7 +231,12 @@ export class DbTableWidgetsComponent implements OnInit { "rows": 5 }`, Time: `// No settings required`, - URL: `// No settings required`, + URL: `// prefix: optional URL prefix to prepend to the href +// example: +// { +// "prefix": "https://example.com/" +// } +{}`, UUID: `// Configure UUID generation version and parameters // Available versions: "v1", "v3", "v4" (default), "v5", "v7" // For v3/v5: provide namespace and optionally name diff --git a/frontend/src/app/components/ui-components/record-edit-fields/country/country.component.css b/frontend/src/app/components/ui-components/record-edit-fields/country/country.component.css index 8301b28e1..4a8991d98 100644 --- a/frontend/src/app/components/ui-components/record-edit-fields/country/country.component.css +++ b/frontend/src/app/components/ui-components/record-edit-fields/country/country.component.css @@ -2,6 +2,25 @@ width: 100%; } +.country-input-container { + display: flex; + align-items: center; + width: 100%; +} + +.country-flag-prefix { + font-size: 20px; + margin-right: 8px; + line-height: 1; +} + +.country-input { + flex: 1; + border: none !important; + outline: none !important; + background: transparent !important; +} + .country-flag { margin-right: 8px; font-size: 16px; diff --git a/frontend/src/app/components/ui-components/record-edit-fields/country/country.component.html b/frontend/src/app/components/ui-components/record-edit-fields/country/country.component.html index 0ed6862ae..abe05600e 100644 --- a/frontend/src/app/components/ui-components/record-edit-fields/country/country.component.html +++ b/frontend/src/app/components/ui-components/record-edit-fields/country/country.component.html @@ -1,10 +1,14 @@ {{normalizedLabel}} - +
+ {{selectedCountryFlag}} + +
(''); public filteredCountries: Observable<{value: string | null, label: string, flag: string}[]>; public showFlag: boolean = true; + public selectedCountryFlag: string = ''; originalOrder = () => { return 0; } @@ -58,7 +59,16 @@ export class CountryEditComponent extends BaseEditFieldComponent { private setupAutocomplete(): void { this.filteredCountries = this.countryControl.valueChanges.pipe( startWith(''), - map(value => this._filter(typeof value === 'string' ? value : (value?.label || ''))) + map(value => { + // Update flag when value changes + if (typeof value === 'object' && value !== null) { + this.selectedCountryFlag = value.flag; + } else if (typeof value === 'string') { + // Clear flag if user is typing + this.selectedCountryFlag = ''; + } + return this._filter(typeof value === 'string' ? value : (value?.label || '')); + }) ); } @@ -67,6 +77,7 @@ export class CountryEditComponent extends BaseEditFieldComponent { const country = this.countries.find(c => c.value === this.value); if (country) { this.countryControl.setValue(country); + this.selectedCountryFlag = country.flag; } } } @@ -81,12 +92,14 @@ export class CountryEditComponent extends BaseEditFieldComponent { onCountrySelected(selectedCountry: {value: string | null, label: string, flag: string}): void { this.value = selectedCountry.value; + this.selectedCountryFlag = selectedCountry.flag; this.onFieldChange.emit(this.value); } displayFn(country: any): string { if (!country) return ''; - return this.showFlag && country.flag ? `${country.flag} ${country.label}` : country.label; + // Only return the country label, flag is shown separately + return typeof country === 'string' ? country : country.label; } private loadCountries(): void { diff --git a/frontend/src/app/components/ui-components/table-display-fields/date-time/date-time.component.ts b/frontend/src/app/components/ui-components/table-display-fields/date-time/date-time.component.ts index 08a32c1d2..a08a0f5ce 100644 --- a/frontend/src/app/components/ui-components/table-display-fields/date-time/date-time.component.ts +++ b/frontend/src/app/components/ui-components/table-display-fields/date-time/date-time.component.ts @@ -5,7 +5,7 @@ import { ClipboardModule } from '@angular/cdk/clipboard'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatTooltipModule } from '@angular/material/tooltip'; -import { format } from 'date-fns'; +import { format, formatDistanceToNow, differenceInHours } from 'date-fns'; @Component({ selector: 'app-date-time-display', @@ -17,13 +17,21 @@ export class DateTimeDisplayComponent extends BaseTableDisplayFieldComponent imp static type = 'datetime'; public formattedDateTime: string; + public formatDistance: boolean = false; ngOnInit(): void { + this.parseWidgetParams(); + if (this.value) { try { const date = new Date(this.value); if (!isNaN(date.getTime())) { - this.formattedDateTime = format(date, "P p"); + // Check if formatDistance is enabled and date is within 24 hours from now + if (this.formatDistance && this.isWithin24Hours(date)) { + this.formattedDateTime = formatDistanceToNow(date, { addSuffix: true }); + } else { + this.formattedDateTime = format(date, "P p"); + } } else { this.formattedDateTime = this.value; } @@ -32,4 +40,26 @@ export class DateTimeDisplayComponent extends BaseTableDisplayFieldComponent imp } } } + + private parseWidgetParams(): void { + if (this.widgetStructure?.widget_params) { + try { + const params = typeof this.widgetStructure.widget_params === 'string' + ? JSON.parse(this.widgetStructure.widget_params) + : this.widgetStructure.widget_params; + + if (params.formatDistance !== undefined) { + this.formatDistance = params.formatDistance; + } + } catch (e) { + console.error('Error parsing datetime widget params:', e); + } + } + } + + private isWithin24Hours(date: Date): boolean { + const now = new Date(); + const hoursDifference = Math.abs(differenceInHours(date, now)); + return hoursDifference <= 24; + } } diff --git a/frontend/src/app/components/ui-components/table-display-fields/date/date.component.ts b/frontend/src/app/components/ui-components/table-display-fields/date/date.component.ts index 28eaabab7..00d8bd104 100644 --- a/frontend/src/app/components/ui-components/table-display-fields/date/date.component.ts +++ b/frontend/src/app/components/ui-components/table-display-fields/date/date.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { format, parseISO } from 'date-fns'; +import { format, parseISO, formatDistanceToNow, differenceInHours } from 'date-fns'; import { BaseTableDisplayFieldComponent } from '../base-table-display-field/base-table-display-field.component'; import { ClipboardModule } from '@angular/cdk/clipboard'; @@ -17,13 +17,21 @@ export class DateDisplayComponent extends BaseTableDisplayFieldComponent impleme static type = 'date'; public formattedDate: string; + public formatDistance: boolean = false; ngOnInit(): void { + this.parseWidgetParams(); + if (this.value) { try { const date = new Date(this.value); if (!isNaN(date.getTime())) { - this.formattedDate = format(date, "P"); + // Check if formatDistance is enabled and date is within 24 hours from now + if (this.formatDistance && this.isWithin24Hours(date)) { + this.formattedDate = formatDistanceToNow(date, { addSuffix: true }); + } else { + this.formattedDate = format(date, "P"); + } } else { this.formattedDate = this.value; } @@ -32,4 +40,26 @@ export class DateDisplayComponent extends BaseTableDisplayFieldComponent impleme } } } + + private parseWidgetParams(): void { + if (this.widgetStructure?.widget_params) { + try { + const params = typeof this.widgetStructure.widget_params === 'string' + ? JSON.parse(this.widgetStructure.widget_params) + : this.widgetStructure.widget_params; + + if (params.formatDistance !== undefined) { + this.formatDistance = params.formatDistance; + } + } catch (e) { + console.error('Error parsing date widget params:', e); + } + } + } + + private isWithin24Hours(date: Date): boolean { + const now = new Date(); + const hoursDifference = Math.abs(differenceInHours(date, now)); + return hoursDifference <= 24; + } } diff --git a/frontend/src/app/components/ui-components/table-display-fields/image/image.component.html b/frontend/src/app/components/ui-components/table-display-fields/image/image.component.html index dd64c06b4..99d87778a 100644 --- a/frontend/src/app/components/ui-components/table-display-fields/image/image.component.html +++ b/frontend/src/app/components/ui-components/table-display-fields/image/image.component.html @@ -2,7 +2,7 @@ [Image URL] Image