ciyidogan commited on
Commit
81b5b06
·
verified ·
1 Parent(s): 8ae3120

Update flare-ui/src/app/dialogs/project-edit-dialog/project-edit-dialog.component.ts

Browse files
flare-ui/src/app/dialogs/project-edit-dialog/project-edit-dialog.component.ts CHANGED
@@ -1,5 +1,5 @@
1
  // project-edit-dialog.component.ts
2
- import { Component, Inject, OnInit } from '@angular/core';
3
  import { CommonModule } from '@angular/common';
4
  import { FormBuilder, FormGroup, Validators, ReactiveFormsModule, FormArray } from '@angular/forms';
5
  import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
@@ -15,6 +15,8 @@ import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
15
  import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
16
  import { ApiService } from '../../services/api.service';
17
  import { LocaleManagerService, Locale } from '../../services/locale-manager.service';
 
 
18
 
19
  export interface ProjectDialogData {
20
  mode: 'create' | 'edit';
@@ -50,15 +52,14 @@ export interface ProjectDialogData {
50
  [readonly]="data.mode === 'edit'"
51
  placeholder="e.g., airline_agent">
52
  <mat-hint>Use only letters, numbers, and underscores</mat-hint>
53
- <mat-error *ngIf="form.get('name')?.hasError('required')">Name is required</mat-error>
54
- <mat-error *ngIf="form.get('name')?.hasError('pattern')">Invalid characters in name</mat-error>
55
  </mat-form-field>
56
 
57
  <mat-form-field appearance="outline" class="full-width">
58
  <mat-label>Caption*</mat-label>
59
  <input matInput formControlName="caption"
60
  placeholder="e.g., Airline Customer Service Agent">
61
- <mat-error *ngIf="form.get('caption')?.hasError('required')">Caption is required</mat-error>
62
  </mat-form-field>
63
 
64
  <mat-form-field appearance="outline" class="full-width">
@@ -155,12 +156,15 @@ export interface ProjectDialogData {
155
  `,
156
  styleUrls: ['./project-edit-dialog.component.scss']
157
  })
158
- export default class ProjectEditDialogComponent implements OnInit {
159
  form!: FormGroup;
160
  saving = false;
161
  loadingLocales = true;
162
  availableLocales: Locale[] = [];
163
 
 
 
 
164
  projectIcons = ['folder', 'work', 'shopping_cart', 'school', 'local_hospital', 'restaurant', 'home', 'business'];
165
 
166
  timezones = [
@@ -186,6 +190,11 @@ export default class ProjectEditDialogComponent implements OnInit {
186
  this.loadAvailableLocales();
187
  }
188
 
 
 
 
 
 
189
  initializeForm() {
190
  const defaultValues = this.data.mode === 'edit' && this.data.project ? {
191
  name: this.data.project.name,
@@ -226,22 +235,24 @@ export default class ProjectEditDialogComponent implements OnInit {
226
 
227
  loadAvailableLocales() {
228
  this.loadingLocales = true;
229
- this.localeManager.getAvailableLocales().subscribe({
230
- next: (locales) => {
231
- this.availableLocales = locales;
232
- this.loadingLocales = false;
233
- this.validateSelectedLanguages();
234
- },
235
- error: (err) => {
236
- this.showMessage('Failed to load available languages', 'error');
237
- this.loadingLocales = false;
238
- // Use fallback locales
239
- this.availableLocales = [
240
- { code: 'tr-TR', name: 'Türkçe', english_name: 'Turkish' },
241
- { code: 'en-US', name: 'English', english_name: 'English (US)' }
242
- ];
243
- }
244
- });
 
 
245
  }
246
 
247
  validateSelectedLanguages() {
@@ -291,48 +302,131 @@ export default class ProjectEditDialogComponent implements OnInit {
291
  return locale ? locale.name : code;
292
  }
293
 
294
- async save() {
295
- if (this.form.invalid) {
296
- this.form.markAllAsTouched();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
  return;
298
  }
299
 
300
  this.saving = true;
301
- try {
302
- const formValue = this.form.getRawValue(); // getRawValue to include disabled fields
303
-
304
- // Project data format matching backend expectations
305
- const projectData = {
306
- name: formValue.name,
307
- caption: formValue.caption,
308
- icon: formValue.icon,
309
- description: formValue.description,
310
- default_language: formValue.defaultLanguage,
311
- supported_languages: formValue.supportedLanguages,
312
- timezone: formValue.timezone,
313
- region: formValue.region
314
- };
315
 
316
- let result;
317
- if (this.data.mode === 'create') {
318
- result = await this.apiService.createProject(projectData).toPromise();
319
- this.showMessage('Project created successfully!');
320
- } else {
321
- // Add last_update_date for edit mode
322
- const updateData = {
323
  ...projectData,
324
  last_update_date: this.data.project.last_update_date || ''
325
- };
326
- result = await this.apiService.updateProject(this.data.project.id, updateData).toPromise();
327
- this.showMessage('Project updated successfully!');
328
- }
329
-
330
- this.dialogRef.close(result);
331
- } catch (error: any) {
332
- this.showMessage(error.error?.detail || 'Operation failed', 'error');
333
- } finally {
334
- this.saving = false;
335
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
  }
337
 
338
  close() {
@@ -342,7 +436,7 @@ export default class ProjectEditDialogComponent implements OnInit {
342
  private showMessage(message: string, type: 'success' | 'error' = 'success') {
343
  this.snackBar.open(message, 'Close', {
344
  duration: 5000,
345
- panelClass: type === 'error' ? 'error-snackbar' : 'success-snackbar',
346
  horizontalPosition: 'right',
347
  verticalPosition: 'top'
348
  });
 
1
  // project-edit-dialog.component.ts
2
+ import { Component, Inject, OnInit, OnDestroy } from '@angular/core';
3
  import { CommonModule } from '@angular/common';
4
  import { FormBuilder, FormGroup, Validators, ReactiveFormsModule, FormArray } from '@angular/forms';
5
  import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
 
15
  import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
16
  import { ApiService } from '../../services/api.service';
17
  import { LocaleManagerService, Locale } from '../../services/locale-manager.service';
18
+ import { Subject, takeUntil } from 'rxjs';
19
+ import { HttpErrorResponse } from '@angular/common/http';
20
 
21
  export interface ProjectDialogData {
22
  mode: 'create' | 'edit';
 
52
  [readonly]="data.mode === 'edit'"
53
  placeholder="e.g., airline_agent">
54
  <mat-hint>Use only letters, numbers, and underscores</mat-hint>
55
+ <mat-error>{{ getErrorMessage('name') }}</mat-error>
 
56
  </mat-form-field>
57
 
58
  <mat-form-field appearance="outline" class="full-width">
59
  <mat-label>Caption*</mat-label>
60
  <input matInput formControlName="caption"
61
  placeholder="e.g., Airline Customer Service Agent">
62
+ <mat-error>{{ getErrorMessage('caption') }}</mat-error>
63
  </mat-form-field>
64
 
65
  <mat-form-field appearance="outline" class="full-width">
 
156
  `,
157
  styleUrls: ['./project-edit-dialog.component.scss']
158
  })
159
+ export default class ProjectEditDialogComponent implements OnInit, OnDestroy {
160
  form!: FormGroup;
161
  saving = false;
162
  loadingLocales = true;
163
  availableLocales: Locale[] = [];
164
 
165
+ // Memory leak prevention
166
+ private destroyed$ = new Subject<void>();
167
+
168
  projectIcons = ['folder', 'work', 'shopping_cart', 'school', 'local_hospital', 'restaurant', 'home', 'business'];
169
 
170
  timezones = [
 
190
  this.loadAvailableLocales();
191
  }
192
 
193
+ ngOnDestroy() {
194
+ this.destroyed$.next();
195
+ this.destroyed$.complete();
196
+ }
197
+
198
  initializeForm() {
199
  const defaultValues = this.data.mode === 'edit' && this.data.project ? {
200
  name: this.data.project.name,
 
235
 
236
  loadAvailableLocales() {
237
  this.loadingLocales = true;
238
+ this.localeManager.getAvailableLocales()
239
+ .pipe(takeUntil(this.destroyed$))
240
+ .subscribe({
241
+ next: (locales) => {
242
+ this.availableLocales = locales;
243
+ this.loadingLocales = false;
244
+ this.validateSelectedLanguages();
245
+ },
246
+ error: (err) => {
247
+ this.showMessage('Failed to load available languages', 'error');
248
+ this.loadingLocales = false;
249
+ // Use fallback locales
250
+ this.availableLocales = [
251
+ { code: 'tr-TR', name: 'Türkçe', english_name: 'Turkish' },
252
+ { code: 'en-US', name: 'English', english_name: 'English (US)' }
253
+ ];
254
+ }
255
+ });
256
  }
257
 
258
  validateSelectedLanguages() {
 
302
  return locale ? locale.name : code;
303
  }
304
 
305
+ getErrorMessage(fieldName: string): string {
306
+ const control = this.form.get(fieldName);
307
+ if (!control) return '';
308
+
309
+ if (control.hasError('required')) {
310
+ return `${this.getFieldLabel(fieldName)} is required`;
311
+ }
312
+ if (control.hasError('pattern')) {
313
+ return `${this.getFieldLabel(fieldName)} contains invalid characters`;
314
+ }
315
+ if (control.hasError('server')) {
316
+ return control.errors?.['server'];
317
+ }
318
+ return '';
319
+ }
320
+
321
+ private getFieldLabel(fieldName: string): string {
322
+ const labels: { [key: string]: string } = {
323
+ 'name': 'Project Name',
324
+ 'caption': 'Caption',
325
+ 'description': 'Description',
326
+ 'defaultLanguage': 'Default Language',
327
+ 'supportedLanguages': 'Supported Languages',
328
+ 'timezone': 'Timezone',
329
+ 'region': 'Region',
330
+ 'icon': 'Icon'
331
+ };
332
+ return labels[fieldName] || fieldName;
333
+ }
334
+
335
+ handleValidationError(error: HttpErrorResponse): void {
336
+ if (error.status === 422 && error.error?.details) {
337
+ // Show specific field errors
338
+ error.error.details.forEach((detail: any) => {
339
+ const control = this.form.get(detail.field);
340
+ if (control) {
341
+ control.setErrors({ server: detail.message });
342
+ control.markAsTouched();
343
+ }
344
+ });
345
+
346
+ this.snackBar.open(
347
+ 'Please fix the validation errors',
348
+ 'Close',
349
+ {
350
+ duration: 5000,
351
+ panelClass: ['error-snackbar']
352
+ }
353
+ );
354
+ } else {
355
+ // Generic error handling
356
+ this.showMessage(
357
+ error.error?.detail || error.message || 'Operation failed',
358
+ 'error'
359
+ );
360
+ }
361
+ }
362
+
363
+ save() {
364
+ if (this.form.invalid || this.saving) {
365
+ // Mark all fields as touched to show validation errors
366
+ Object.keys(this.form.controls).forEach(key => {
367
+ this.form.get(key)?.markAsTouched();
368
+ });
369
  return;
370
  }
371
 
372
  this.saving = true;
373
+
374
+ const formValue = this.form.getRawValue(); // getRawValue to include disabled fields
375
+
376
+ // Project data format matching backend expectations
377
+ const projectData = {
378
+ name: formValue.name,
379
+ caption: formValue.caption,
380
+ icon: formValue.icon,
381
+ description: formValue.description,
382
+ default_language: formValue.defaultLanguage,
383
+ supported_languages: formValue.supportedLanguages,
384
+ timezone: formValue.timezone,
385
+ region: formValue.region
386
+ };
387
 
388
+ const saveOperation = this.data.mode === 'create'
389
+ ? this.apiService.createProject(projectData)
390
+ : this.apiService.updateProject(this.data.project.id, {
 
 
 
 
391
  ...projectData,
392
  last_update_date: this.data.project.last_update_date || ''
393
+ });
394
+
395
+ saveOperation
396
+ .pipe(takeUntil(this.destroyed$))
397
+ .subscribe({
398
+ next: (result) => {
399
+ this.saving = false;
400
+ this.showMessage(
401
+ this.data.mode === 'create'
402
+ ? 'Project created successfully!'
403
+ : 'Project updated successfully!'
404
+ );
405
+ this.dialogRef.close(result);
406
+ },
407
+ error: (error: HttpErrorResponse) => {
408
+ this.saving = false;
409
+
410
+ // Race condition handling
411
+ if (error.status === 409) {
412
+ const details = error.error?.details || {};
413
+ this.snackBar.open(
414
+ `Project was modified by ${details.last_update_user || 'another user'}. Please reload.`,
415
+ 'Reload',
416
+ { duration: 0 }
417
+ ).onAction().subscribe(() => {
418
+ this.dialogRef.close('reload');
419
+ });
420
+ } else if (error.status === 422) {
421
+ this.handleValidationError(error);
422
+ } else {
423
+ this.showMessage(
424
+ error.error?.detail || 'Operation failed',
425
+ 'error'
426
+ );
427
+ }
428
+ }
429
+ });
430
  }
431
 
432
  close() {
 
436
  private showMessage(message: string, type: 'success' | 'error' = 'success') {
437
  this.snackBar.open(message, 'Close', {
438
  duration: 5000,
439
+ panelClass: type === 'error' ? ['error-snackbar'] : ['success-snackbar'],
440
  horizontalPosition: 'right',
441
  verticalPosition: 'top'
442
  });