Skip to content

Commit 8ab7b55

Browse files
mihailkitrozoskicodechemMihail
andauthored
feat: add property edit and delete option (#249)
* feat: add property edit option * Add "No past dates" option to the edit dialog for Date/DateTime fields * Refactored the code to use Angular Material's proper dialog data injection pattern and the updateField action to use a typed interface instead of individual properties * Remove comments * Add custom exception * Refactor to reuse DomainModelEditor.GetProperty and keep Roslyn-specific logic out of controllers * Refactor: transfer the property extraction to the Model editor, add new GetModelFields in the DomainModelEditor * feat: Add property delete option, refactor: move redundant logic from ModelsController to the DomainModelEditor, introduce a common methods that finds the SyntaxTree root to reduce code duplication and improve reusability in the DomainModelEditor and DbContextEditor * refactor: remove redundant code * Refactor: fix the PropertyCheckUtils to use Roslyn check instead of Regex * Refactor * Refactor: remove redundant property to delete check, change the return type for the non-found properties * Change the Regex with Roslyn in the UpdatePropertyNameInOnModelCreating * Refactor the DbContextEditor Update and Delete methods to use common method to improve reusability * Refactor: make the PropertyNameExists an extension method and move it in ClassDeclarationSyntaxExtensions * fix: add required import * Remove Roslyn logic from the controller --------- Co-authored-by: Mihail <mihailkitrozoski@codechem.com>
1 parent 48b2a1f commit 8ab7b55

21 files changed

Lines changed: 946 additions & 149 deletions

CCUI.DAPPI/src/app/add-field-dialog/add-field-dialog.component.html

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,52 @@
11
<div class="add-field-dialog-container">
22
<div class="dialog-header">
3-
<h2>Add new field</h2>
3+
<h2>{{ isEditMode ? 'Edit Field' : 'Add new field' }}</h2>
44
<button mat-icon-button (click)="onClose()" aria-label="Close dialog">
55
<mat-icon>close</mat-icon>
66
</button>
77
</div>
88

99
<div class="dialog-content">
1010
<form [formGroup]="fieldForm">
11-
<h3 class="section-title">1. Select field type</h3>
12-
<div class="field-type-grid-wrapper">
13-
<div class="field-type-grid">
14-
@for (ft of fieldTypes; track ft; let i = $index) {
15-
<div
16-
class="box"
17-
[class.selected]="selectedFieldTypeId === ft.type"
18-
(click)="selectFieldType(ft.type)"
19-
[attr.aria-selected]="selectedFieldTypeId === ft.type"
20-
role="option"
21-
tabindex="0"
22-
(keydown.enter)="selectFieldType(ft.type)"
23-
>
24-
<mat-icon class="field-type-box">
25-
{{ ft.icon }}
26-
</mat-icon>
27-
<div class="label">{{ ft.label }}</div>
28-
<div class="description">{{ ft.description }}</div>
29-
</div>
30-
}
11+
@if (isEditMode) {
12+
<div class="field-info">
13+
<div class="field-type-badge">
14+
<mat-icon>category</mat-icon>
15+
<span>{{ data.fieldType }}</span>
16+
</div>
17+
<p class="info-text">Field type cannot be changed</p>
3118
</div>
32-
</div>
19+
}
20+
@if (!isEditMode) {
21+
<h3 class="section-title">1. Select field type</h3>
22+
<div class="field-type-grid-wrapper">
23+
<div class="field-type-grid">
24+
@for (ft of fieldTypes; track ft; let i = $index) {
25+
<div
26+
class="box"
27+
[class.selected]="selectedFieldTypeId === ft.type"
28+
(click)="selectFieldType(ft.type)"
29+
[attr.aria-selected]="selectedFieldTypeId === ft.type"
30+
role="option"
31+
tabindex="0"
32+
(keydown.enter)="selectFieldType(ft.type)"
33+
>
34+
<mat-icon class="field-type-box">
35+
{{ ft.icon }}
36+
</mat-icon>
37+
<div class="label">{{ ft.label }}</div>
38+
<div class="description">{{ ft.description }}</div>
39+
</div>
40+
}
41+
</div>
42+
</div>
43+
}
3344
@if (
3445
selectedFieldTypeId !== null &&
3546
selectedFieldTypeId !== fieldTypeEnum.Relation &&
3647
selectedFieldTypeId !== fieldTypeEnum.Dropdown
3748
) {
38-
<h3 class="section-title">2. Field settings</h3>
49+
<h3 class="section-title">{{ isEditMode ? 'Field settings' : '2. Field settings' }}</h3>
3950
<div class="field-settings-row">
4051
<mat-form-field appearance="outline" class="input-field">
4152
<mat-label>Field name</mat-label>
@@ -237,8 +248,9 @@ <h3 class="section-title">2. Configure Relationship with {{ selectedType }}</h3>
237248
</div>
238249

239250
<div class="dialog-footer">
251+
<button mat-stroked-button (click)="onClose()">Cancel</button>
240252
<button mat-flat-button class="add-field-button" [disabled]="!canSubmit" (click)="onAddField()">
241-
Add new field
253+
{{ isEditMode ? 'Save Changes' : 'Add new field' }}
242254
</button>
243255
</div>
244256
</div>

CCUI.DAPPI/src/app/add-field-dialog/add-field-dialog.component.scss

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,45 @@
6969
width: 100%;
7070
}
7171

72+
.field-info {
73+
margin-bottom: 24px;
74+
padding: 16px;
75+
background-color: vars.$background-paper-elevation-2;
76+
border-radius: 8px;
77+
78+
.field-type-badge {
79+
display: flex;
80+
align-items: center;
81+
gap: 8px;
82+
padding: 8px 12px;
83+
background-color: vars.$primary-selected;
84+
border: vars.$primary-outlined-border;
85+
border-radius: 6px;
86+
width: fit-content;
87+
margin-bottom: 8px;
88+
89+
mat-icon {
90+
color: vars.$primary-main;
91+
font-size: 20px;
92+
width: 20px;
93+
height: 20px;
94+
}
95+
96+
span {
97+
color: vars.$primary-main;
98+
font-weight: 500;
99+
font-size: 14px;
100+
}
101+
}
102+
103+
.info-text {
104+
margin: 0;
105+
font-size: 12px;
106+
color: vars.$text-secondary;
107+
padding-left: 12px;
108+
}
109+
}
110+
72111
.field-type-grid {
73112
display: grid;
74113
grid-template-columns: repeat(4, 1fr);
@@ -157,6 +196,7 @@
157196
.dialog-footer {
158197
display: flex;
159198
justify-content: flex-end;
199+
gap: 12px;
160200
padding: 16px 24px;
161201
border-top: vars.$divider;
162202

CCUI.DAPPI/src/app/add-field-dialog/add-field-dialog.component.ts

Lines changed: 112 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,17 @@ interface FieldType {
3737
netType: string;
3838
}
3939

40+
export interface AddFieldDialogData {
41+
selectedType: string;
42+
editMode?: boolean;
43+
fieldName?: string;
44+
fieldType?: string;
45+
isRequired?: boolean;
46+
hasIndex?: boolean;
47+
regex?: string;
48+
noPastDates?: boolean;
49+
}
50+
4051
@Component({
4152
selector: 'app-add-field-dialog',
4253
standalone: true,
@@ -59,6 +70,8 @@ export class AddFieldDialogComponent implements OnInit, OnDestroy {
5970
selectedType$ = this.store.select(selectSelectedType);
6071
selectedTypeFields$ = this.store.select(selectFields);
6172
selectedType = '';
73+
isEditMode = false;
74+
originalFieldName = '';
6275

6376
selectedRelationTypeIndex: number | null = null;
6477
selectedRelationType = 'ManyToMany';
@@ -179,12 +192,19 @@ export class AddFieldDialogComponent implements OnInit, OnDestroy {
179192
private fb: FormBuilder,
180193
private enumsService: EnumsService,
181194
private enumManagementService: EnumManagementService,
182-
@Inject(MAT_DIALOG_DATA) public data: { selectedType: string },
195+
@Inject(MAT_DIALOG_DATA) public data: AddFieldDialogData,
183196
private store: Store
184197
) {
198+
this.isEditMode = data.editMode || false;
199+
this.originalFieldName = data.fieldName || '';
200+
201+
const asyncValidators = this.isEditMode
202+
? [ModelValidators.fieldNameIsTaken(this.selectedTypeFields$, data.fieldName)]
203+
: [ModelValidators.fieldNameIsTaken(this.selectedTypeFields$)];
204+
185205
this.fieldForm = this.fb.group({
186206
fieldName: [
187-
'',
207+
data.fieldName || '',
188208
{
189209
validators: [
190210
Validators.required,
@@ -193,16 +213,24 @@ export class AddFieldDialogComponent implements OnInit, OnDestroy {
193213
ModelValidators.reservedKeyword,
194214
ModelValidators.collectionNameIsTaken
195215
],
196-
asyncValidators: [ModelValidators.fieldNameIsTaken(this.selectedTypeFields$)],
216+
asyncValidators: asyncValidators,
197217
},
198218
],
199-
requiredField: [false],
219+
requiredField: [data.isRequired || false],
200220
relatedModel: [''],
201221
relatedRelationName: [''],
202-
regex: ['', [ModelValidators.validRegex]],
203-
hasIndex: [false],
204-
noPastDates: [false]
222+
regex: [data.regex || '', [ModelValidators.validRegex]],
223+
hasIndex: [data.hasIndex || false],
224+
noPastDates: [data.noPastDates || false]
205225
});
226+
227+
if (this.isEditMode && data.fieldType) {
228+
this.preselectFieldType(data.fieldType);
229+
this.fieldForm.get('relatedModel')?.clearValidators();
230+
this.fieldForm.get('relatedRelationName')?.clearValidators();
231+
this.fieldForm.get('relatedModel')?.updateValueAndValidity();
232+
this.fieldForm.get('relatedRelationName')?.updateValueAndValidity();
233+
}
206234
}
207235

208236
relationTypes: {
@@ -272,7 +300,11 @@ export class AddFieldDialogComponent implements OnInit, OnDestroy {
272300
})
273301
);
274302
this.fieldForm.get('fieldName')?.addValidators(ModelValidators.fieldNameSameAsModel(this.selectedType));
275-
this.fieldForm.get('relatedModel')?.setValidators([Validators.required]);
303+
304+
if (!this.isEditMode) {
305+
this.fieldForm.get('relatedModel')?.setValidators([Validators.required]);
306+
}
307+
276308
this.updateRelationTypes();
277309
}
278310

@@ -333,6 +365,24 @@ export class AddFieldDialogComponent implements OnInit, OnDestroy {
333365
return;
334366
}
335367

368+
if (this.isEditMode) {
369+
if (!this.hasChanges()) {
370+
return;
371+
}
372+
373+
const result = {
374+
oldFieldName: this.originalFieldName,
375+
newFieldName: this.fieldForm.value.fieldName,
376+
isRequired: this.fieldForm.value.requiredField,
377+
hasIndex: this.fieldForm.value.hasIndex,
378+
regex: this.isTextType() ? this.fieldForm.value.regex : undefined,
379+
noPastDates: this.isDateType() ? this.fieldForm.value.noPastDates : undefined,
380+
};
381+
382+
this.dialogRef.close(result);
383+
return;
384+
}
385+
336386
const selectedFieldType = this.fieldTypes.find(fieldType => fieldType.type.toString() === this.selectedFieldTypeId?.toString());
337387

338388
if (!selectedFieldType) {
@@ -368,10 +418,63 @@ export class AddFieldDialogComponent implements OnInit, OnDestroy {
368418
}
369419

370420
get canSubmit(): boolean | undefined {
421+
if (this.isEditMode) {
422+
return this.fieldForm.valid && this.hasChanges();
423+
}
424+
425+
if (this.selectedFieldTypeId === null) {
426+
return false;
427+
}
428+
371429
if (this.selectedFieldTypeId === this.fieldTypeEnum.Relation) {
372-
return this.selectedRelationTypeIndex !== null;
430+
return this.selectedRelationTypeIndex !== null && this.fieldForm.get('fieldName')?.valid;
373431
} else {
374432
return this.fieldForm.get('fieldName')?.valid;
375433
}
376434
}
435+
436+
private preselectFieldType(fieldType: string): void {
437+
const typeMapping: Record<string, FieldTypeEnum> = {
438+
'string': FieldTypeEnum.String,
439+
'int': FieldTypeEnum.Number,
440+
'float': FieldTypeEnum.Number,
441+
'DateOnly': FieldTypeEnum.Date,
442+
'DateTime': FieldTypeEnum.DateTime,
443+
'bool': FieldTypeEnum.Checkbox,
444+
'MediaInfo': FieldTypeEnum.Media,
445+
};
446+
447+
const mappedType = typeMapping[fieldType];
448+
if (mappedType !== undefined) {
449+
this.selectedFieldTypeId = mappedType;
450+
} else {
451+
if (['ManyToMany', 'OneToMany', 'ManyToOne', 'OneToOne'].includes(fieldType)) {
452+
this.selectedFieldTypeId = FieldTypeEnum.Relation;
453+
} else {
454+
this.selectedFieldTypeId = FieldTypeEnum.Dropdown;
455+
this.selectedEnum = fieldType;
456+
}
457+
}
458+
}
459+
460+
private hasChanges(): boolean {
461+
if (!this.isEditMode) return true;
462+
463+
const formValue = this.fieldForm.value;
464+
return (
465+
formValue.fieldName !== this.originalFieldName ||
466+
formValue.requiredField !== this.data.isRequired ||
467+
formValue.hasIndex !== this.data.hasIndex ||
468+
(this.isTextType() && formValue.regex !== (this.data.regex || '')) ||
469+
(this.isDateType() && formValue.noPastDates !== (this.data.noPastDates || false))
470+
);
471+
}
472+
473+
private isTextType(): boolean {
474+
return this.selectedFieldTypeId === FieldTypeEnum.String;
475+
}
476+
477+
private isDateType(): boolean {
478+
return this.selectedFieldTypeId === FieldTypeEnum.Date || this.selectedFieldTypeId === FieldTypeEnum.DateTime;
479+
}
377480
}

CCUI.DAPPI/src/app/builder/builder.component.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ <h2>There are no collections created yet</h2>
4545
class="add-field-button"
4646
aria-label="Add another field"
4747
></app-button>
48-
<app-fields-list [fields]="fieldsData"></app-fields-list>
48+
<app-fields-list
49+
[fields]="fieldsData"
50+
(editField)="onEditField($event)"
51+
(deleteField)="onDeleteField($event)"
52+
></app-fields-list>
4953
</div>
5054
}
5155

0 commit comments

Comments
 (0)