Spaces:
Running
Running
// project-edit-dialog.component.ts | |
import { Component, Inject, OnInit } from '@angular/core'; | |
import { CommonModule } from '@angular/common'; | |
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule, FormArray } from '@angular/forms'; | |
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; | |
import { MatFormFieldModule } from '@angular/material/form-field'; | |
import { MatInputModule } from '@angular/material/input'; | |
import { MatSelectModule } from '@angular/material/select'; | |
import { MatCheckboxModule } from '@angular/material/checkbox'; | |
import { MatButtonModule } from '@angular/material/button'; | |
import { MatIconModule } from '@angular/material/icon'; | |
import { MatChipsModule } from '@angular/material/chips'; | |
import { MatDividerModule } from '@angular/material/divider'; | |
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; | |
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; | |
import { ApiService } from '../../services/api.service'; | |
import { LocaleManagerService, Locale } from '../../services/locale-manager.service'; | |
export interface ProjectDialogData { | |
mode: 'create' | 'edit'; | |
project?: any; | |
} | |
({ | |
selector: 'app-project-edit-dialog', | |
standalone: true, | |
imports: [ | |
CommonModule, | |
ReactiveFormsModule, | |
MatDialogModule, | |
MatFormFieldModule, | |
MatInputModule, | |
MatSelectModule, | |
MatCheckboxModule, | |
MatButtonModule, | |
MatIconModule, | |
MatChipsModule, | |
MatDividerModule, | |
MatSnackBarModule, | |
MatProgressSpinnerModule | |
], | |
template: ` | |
<h2 mat-dialog-title>{{ data.mode === 'create' ? 'Create New Project' : 'Edit Project' }}</h2> | |
<mat-dialog-content> | |
<form [formGroup]="form"> | |
<mat-form-field appearance="outline" class="full-width"> | |
<mat-label>Name*</mat-label> | |
<input matInput formControlName="name" | |
[readonly]="data.mode === 'edit'" | |
placeholder="e.g., airline_agent"> | |
<mat-hint>Use only letters, numbers, and underscores</mat-hint> | |
<mat-error *ngIf="form.get('name')?.hasError('required')">Name is required</mat-error> | |
<mat-error *ngIf="form.get('name')?.hasError('pattern')">Invalid characters in name</mat-error> | |
</mat-form-field> | |
<mat-form-field appearance="outline" class="full-width"> | |
<mat-label>Caption*</mat-label> | |
<input matInput formControlName="caption" | |
placeholder="e.g., Airline Customer Service Agent"> | |
<mat-error *ngIf="form.get('caption')?.hasError('required')">Caption is required</mat-error> | |
</mat-form-field> | |
<mat-form-field appearance="outline" class="full-width"> | |
<mat-label>Icon</mat-label> | |
<mat-select formControlName="icon"> | |
@for (icon of projectIcons; track icon) { | |
<mat-option [value]="icon"> | |
<mat-icon>{{ icon }}</mat-icon> | |
{{ icon }} | |
</mat-option> | |
} | |
</mat-select> | |
</mat-form-field> | |
<mat-form-field appearance="outline" class="full-width"> | |
<mat-label>Description</mat-label> | |
<textarea matInput formControlName="description" rows="3"></textarea> | |
</mat-form-field> | |
<!-- Default Language --> | |
<mat-form-field appearance="outline" class="full-width"> | |
<mat-label>Default Language</mat-label> | |
<mat-select | |
formControlName="defaultLanguage" | |
(selectionChange)="onDefaultLanguageChange()" | |
[disabled]="loadingLocales"> | |
@if (loadingLocales) { | |
<mat-option disabled> | |
<mat-spinner diameter="20"></mat-spinner> | |
Loading languages... | |
</mat-option> | |
} | |
@for (locale of availableLocales; track locale.code) { | |
<mat-option [value]="locale.name"> | |
{{ locale.name }} | |
<span class="locale-code">{{ locale.code }}</span> | |
</mat-option> | |
} | |
</mat-select> | |
<mat-icon matPrefix>translate</mat-icon> | |
<mat-hint>Primary language for this project</mat-hint> | |
</mat-form-field> | |
<!-- Supported Languages --> | |
<mat-form-field appearance="outline" class="full-width"> | |
<mat-label>Supported Languages</mat-label> | |
<mat-select | |
formControlName="supportedLanguages" | |
(selectionChange)="onSupportedLanguagesChange()" | |
[disabled]="loadingLocales" | |
multiple> | |
<mat-select-trigger> | |
<div class="selected-languages"> | |
@for (lang of form.get('supportedLanguages')?.value || []; track lang; let last = $last) { | |
<span>{{ getLocaleName(lang) }}@if (!last) {, }</span> | |
} | |
</div> | |
</mat-select-trigger> | |
@for (locale of availableLocales; track locale.code) { | |
<mat-option [value]="locale.code"> | |
{{ locale.name }} | |
<span class="locale-code">{{ locale.code }}</span> | |
</mat-option> | |
} | |
</mat-select> | |
<mat-icon matPrefix>language</mat-icon> | |
<mat-hint>Languages available in this project</mat-hint> | |
</mat-form-field> | |
<mat-form-field appearance="outline" class="full-width"> | |
<mat-label>Timezone</mat-label> | |
<mat-select formControlName="timezone"> | |
@for (tz of timezones; track tz) { | |
<mat-option [value]="tz">{{ tz }}</mat-option> | |
} | |
</mat-select> | |
</mat-form-field> | |
<mat-form-field appearance="outline" class="full-width"> | |
<mat-label>Region</mat-label> | |
<input matInput formControlName="region" placeholder="e.g., tr-TR"> | |
</mat-form-field> | |
</form> | |
</mat-dialog-content> | |
<mat-dialog-actions align="end"> | |
<button mat-button (click)="close()">Cancel</button> | |
<button mat-raised-button color="primary" | |
(click)="save()" | |
[disabled]="form.invalid || saving"> | |
{{ saving ? 'Saving...' : (data.mode === 'create' ? 'Create' : 'Save') }} | |
</button> | |
</mat-dialog-actions> | |
`, | |
styleUrls: ['./project-edit-dialog.component.scss'] | |
}) | |
export default class ProjectEditDialogComponent implements OnInit { | |
form!: FormGroup; | |
saving = false; | |
loadingLocales = true; | |
availableLocales: Locale[] = []; | |
projectIcons = ['folder', 'work', 'shopping_cart', 'school', 'local_hospital', 'restaurant', 'home', 'business']; | |
timezones = [ | |
'Europe/Istanbul', | |
'Europe/London', | |
'Europe/Berlin', | |
'America/New_York', | |
'America/Los_Angeles', | |
'Asia/Tokyo' | |
]; | |
constructor( | |
private fb: FormBuilder, | |
private apiService: ApiService, | |
private localeManager: LocaleManagerService, | |
private snackBar: MatSnackBar, | |
public dialogRef: MatDialogRef<ProjectEditDialogComponent>, | |
public data: ProjectDialogData (MAT_DIALOG_DATA) | |
) {} | |
ngOnInit() { | |
this.initializeForm(); | |
this.loadAvailableLocales(); | |
} | |
initializeForm() { | |
const defaultValues = this.data.mode === 'edit' && this.data.project ? { | |
name: this.data.project.name, | |
caption: this.data.project.caption || '', | |
icon: this.data.project.icon || 'folder', | |
description: this.data.project.description || '', | |
defaultLanguage: this.data.project.default_language || 'Türkçe', | |
supportedLanguages: this.data.project.supported_languages || ['tr-TR'], | |
timezone: this.data.project.timezone || 'Europe/Istanbul', | |
region: this.data.project.region || 'tr-TR' | |
} : { | |
name: '', | |
caption: '', | |
icon: 'folder', | |
description: '', | |
defaultLanguage: 'Türkçe', | |
supportedLanguages: ['tr-TR'], | |
timezone: 'Europe/Istanbul', | |
region: 'tr-TR' | |
}; | |
this.form = this.fb.group({ | |
name: [defaultValues.name, [Validators.required, Validators.pattern(/^[a-z0-9_]+$/)]], | |
caption: [defaultValues.caption, Validators.required], | |
icon: [defaultValues.icon], | |
description: [defaultValues.description], | |
defaultLanguage: [defaultValues.defaultLanguage], | |
supportedLanguages: [defaultValues.supportedLanguages], | |
timezone: [defaultValues.timezone], | |
region: [defaultValues.region] | |
}); | |
// Disable name field in edit mode | |
if (this.data.mode === 'edit') { | |
this.form.get('name')?.disable(); | |
} | |
} | |
loadAvailableLocales() { | |
this.loadingLocales = true; | |
this.localeManager.getAvailableLocales().subscribe({ | |
next: (locales) => { | |
this.availableLocales = locales; | |
this.loadingLocales = false; | |
this.validateSelectedLanguages(); | |
}, | |
error: (err) => { | |
this.showMessage('Failed to load available languages', 'error'); | |
this.loadingLocales = false; | |
// Use fallback locales | |
this.availableLocales = [ | |
{ code: 'tr-TR', name: 'Türkçe', english_name: 'Turkish' }, | |
{ code: 'en-US', name: 'English', english_name: 'English (US)' } | |
]; | |
} | |
}); | |
} | |
validateSelectedLanguages() { | |
const availableCodes = this.availableLocales.map(l => l.code); | |
const currentSupported = this.form.get('supportedLanguages')?.value || []; | |
const currentDefault = this.form.get('defaultLanguage')?.value; | |
// Filter out any unsupported languages | |
const validSupported = currentSupported.filter((lang: string) => | |
availableCodes.includes(lang) | |
); | |
// Update form if any languages were removed | |
if (validSupported.length !== currentSupported.length) { | |
this.form.patchValue({ supportedLanguages: validSupported }); | |
} | |
// Ensure default language is valid | |
if (!availableCodes.includes(currentDefault)) { | |
const newDefault = availableCodes[0] || 'tr-TR'; | |
this.form.patchValue({ | |
defaultLanguage: newDefault, | |
supportedLanguages: [...validSupported, newDefault] | |
}); | |
} | |
} | |
onDefaultLanguageChange() { | |
// Default language değiştiğinde bir şey yapmaya gerek yok | |
// Çünkü default_language (Türkçe) ve supported_languages (tr-TR) farklı tipte | |
} | |
onSupportedLanguagesChange() { | |
// Supported languages değiştiğinde de bir şey yapmaya gerek yok | |
// En az bir dil seçili olduğu sürece sorun yok | |
const supportedLangs = this.form.get('supportedLanguages')?.value || []; | |
if (supportedLangs.length === 0) { | |
// En az bir dil seçilmeli | |
this.form.patchValue({ | |
supportedLanguages: ['tr-TR'] | |
}); | |
} | |
} | |
getLocaleName(code: string): string { | |
const locale = this.availableLocales.find(l => l.code === code); | |
return locale ? locale.name : code; | |
} | |
async save() { | |
if (this.form.invalid) { | |
this.form.markAllAsTouched(); | |
return; | |
} | |
this.saving = true; | |
try { | |
const formValue = this.form.getRawValue(); // getRawValue to include disabled fields | |
// Project data format matching backend expectations | |
const projectData = { | |
name: formValue.name, | |
caption: formValue.caption, | |
icon: formValue.icon, | |
description: formValue.description, | |
default_language: formValue.defaultLanguage, | |
supported_languages: formValue.supportedLanguages, | |
timezone: formValue.timezone, | |
region: formValue.region | |
}; | |
let result; | |
if (this.data.mode === 'create') { | |
result = await this.apiService.createProject(projectData).toPromise(); | |
this.showMessage('Project created successfully!'); | |
} else { | |
// Add last_update_date for edit mode | |
const updateData = { | |
...projectData, | |
last_update_date: this.data.project.last_update_date || '' | |
}; | |
result = await this.apiService.updateProject(this.data.project.id, updateData).toPromise(); | |
this.showMessage('Project updated successfully!'); | |
} | |
this.dialogRef.close(result); | |
} catch (error: any) { | |
this.showMessage(error.error?.detail || 'Operation failed', 'error'); | |
} finally { | |
this.saving = false; | |
} | |
} | |
close() { | |
this.dialogRef.close(); | |
} | |
private showMessage(message: string, type: 'success' | 'error' = 'success') { | |
this.snackBar.open(message, 'Close', { | |
duration: 5000, | |
panelClass: type === 'error' ? 'error-snackbar' : 'success-snackbar', | |
horizontalPosition: 'right', | |
verticalPosition: 'top' | |
}); | |
} | |
} |