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:
2026-02-15 03:56:17 +05:30
parent 9e34de73ee
commit 55b436c7d2
43 changed files with 2043 additions and 186 deletions
+144
View File
@@ -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();
}
}