feat: implement dynamic form and popup components
- Added DynamicField interface to define form field structure. - Created DynamicFormConfig interface for form configuration. - Developed DynamicFormComponent to handle dynamic form rendering and validation. - Implemented DynamicPopupComponent for displaying forms in a modal dialog. - Added HTML and SCSS for dynamic form and popup styling. - Integrated Material Design components for form inputs and buttons. - Implemented form submission logic with API integration. - Added tests for DynamicForm and DynamicPopup components. - Updated global styles for Material components in themed popups. - Included Material Icons in index.html for better UI representation.
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { tap } from 'rxjs';
|
||||
import { AdminStore, AdminSection } from './admin.store';
|
||||
import { AdminService } from '../services/admin.service';
|
||||
import { IAbout } from '../about/about.model';
|
||||
import { IHobby } from '../models/hobby.model';
|
||||
import { IContactModel } from '../contact/contact.model';
|
||||
import { IProjects } from '../projects/projects.model';
|
||||
import { IResume } from '../resume/resume.model';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AdminStateService {
|
||||
private store = inject(AdminStore);
|
||||
private api = inject(AdminService);
|
||||
|
||||
setCandidateId(id: number) {
|
||||
this.store.update({ candidateId: id });
|
||||
}
|
||||
|
||||
// ── About ──────────────────────────────────────────────
|
||||
|
||||
loadAbout(candidateId: number) {
|
||||
this.store.setSectionLoading('about', true);
|
||||
return this.api.getHobbies(candidateId).pipe(
|
||||
tap({
|
||||
next: (about: IAbout) => {
|
||||
this.store.update({ about });
|
||||
this.store.setSectionLoading('about', false);
|
||||
this.store.setSectionError('about', null);
|
||||
},
|
||||
error: (err) => {
|
||||
this.store.setSectionLoading('about', false);
|
||||
this.store.setSectionError('about', err.message);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
updateAbout(about: IAbout) {
|
||||
this.store.update({ about });
|
||||
}
|
||||
|
||||
upsertHobbies(resumeId: number, hobbies: IHobby[]) {
|
||||
this.store.setSectionLoading('about', true);
|
||||
return this.api.upsertHobbies(resumeId, hobbies).pipe(
|
||||
tap({
|
||||
next: (updatedHobbies: IHobby[]) => {
|
||||
this.store.update(state => ({
|
||||
about: state.about ? { ...state.about, hobbies: updatedHobbies } : state.about
|
||||
}));
|
||||
this.store.setSectionLoading('about', false);
|
||||
this.store.setSectionError('about', null);
|
||||
},
|
||||
error: (err) => {
|
||||
this.store.setSectionLoading('about', false);
|
||||
this.store.setSectionError('about', err.message);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// ── Contact ────────────────────────────────────────────
|
||||
|
||||
loadContact(candidateId: number) {
|
||||
this.store.setSectionLoading('contact', true);
|
||||
return this.api.getCandidateWithSocialLinks(candidateId).pipe(
|
||||
tap({
|
||||
next: (contact: IContactModel) => {
|
||||
this.store.update({ contact });
|
||||
this.store.setSectionLoading('contact', false);
|
||||
this.store.setSectionError('contact', null);
|
||||
},
|
||||
error: (err) => {
|
||||
this.store.setSectionLoading('contact', false);
|
||||
this.store.setSectionError('contact', err.message);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
updateContact(contact: IContactModel) {
|
||||
this.store.update({ contact });
|
||||
}
|
||||
|
||||
// ── Projects ───────────────────────────────────────────
|
||||
|
||||
loadProjects(candidateId: number) {
|
||||
this.store.setSectionLoading('projects', true);
|
||||
return this.api.getProjects(candidateId).pipe(
|
||||
tap({
|
||||
next: (projects: IProjects) => {
|
||||
this.store.update({ projects });
|
||||
this.store.setSectionLoading('projects', false);
|
||||
this.store.setSectionError('projects', null);
|
||||
},
|
||||
error: (err) => {
|
||||
this.store.setSectionLoading('projects', false);
|
||||
this.store.setSectionError('projects', err.message);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
updateProjects(projects: IProjects) {
|
||||
this.store.update({ projects });
|
||||
}
|
||||
|
||||
// ── Resume ─────────────────────────────────────────────
|
||||
|
||||
loadResume(candidateId: number) {
|
||||
this.store.setSectionLoading('resume', true);
|
||||
return this.api.getResume(candidateId).pipe(
|
||||
tap({
|
||||
next: (resume: IResume) => {
|
||||
this.store.update({ resume });
|
||||
this.store.setSectionLoading('resume', false);
|
||||
this.store.setSectionError('resume', null);
|
||||
},
|
||||
error: (err) => {
|
||||
this.store.setSectionLoading('resume', false);
|
||||
this.store.setSectionError('resume', err.message);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
updateResume(resume: IResume) {
|
||||
this.store.update({ resume });
|
||||
}
|
||||
|
||||
// ── Reset helpers ──────────────────────────────────────
|
||||
|
||||
resetSection(section: AdminSection) {
|
||||
this.store.update(state => ({
|
||||
[section]: null,
|
||||
loading: { ...state.loading, [section]: false },
|
||||
error: { ...state.error, [section]: null }
|
||||
}));
|
||||
}
|
||||
|
||||
resetAll() {
|
||||
this.store.reset();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user