Spaces:
Running
Running
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
|
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
|
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()
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
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 |
-
|
295 |
-
|
296 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
297 |
return;
|
298 |
}
|
299 |
|
300 |
this.saving = true;
|
301 |
-
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
|
316 |
-
|
317 |
-
|
318 |
-
|
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 |
-
|
327 |
-
|
328 |
-
|
329 |
-
|
330 |
-
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
-
|
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 |
});
|