|
|
import { Component, OnInit, HostListener, OnDestroy } from '@angular/core';
|
|
|
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
|
|
|
import { Router, ActivatedRoute } from '@angular/router';
|
|
|
import { CommonModule } from '@angular/common';
|
|
|
import { FormsModule } from '@angular/forms';
|
|
|
import { QAItem, QuestionAnswerService, CategoryMeta } from './question-answer-service.service';
|
|
|
import { AuthService } from '../services/auth.service';
|
|
|
|
|
|
|
|
|
import { Pipe, PipeTransform } from '@angular/core';
|
|
|
|
|
|
@Pipe({
|
|
|
name: 'truncate',
|
|
|
standalone: true
|
|
|
})
|
|
|
export class TruncatePipe implements PipeTransform {
|
|
|
transform(value: string, limit: number = 100, trail: string = '...'): string {
|
|
|
if (!value) return '';
|
|
|
return value.length > limit ? value.substring(0, limit) + trail : value;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@Component({
|
|
|
selector: 'app-question-answer',
|
|
|
templateUrl: './question-answer.component.html',
|
|
|
styleUrls: ['./question-answer.component.css'],
|
|
|
standalone: true,
|
|
|
imports: [CommonModule, FormsModule, TruncatePipe]
|
|
|
})
|
|
|
export class QuestionAnswerComponent implements OnInit, OnDestroy {
|
|
|
selectedOption: string = '';
|
|
|
displayMode: 'modal' | 'page' = 'page';
|
|
|
isLoading: boolean = true;
|
|
|
isModalOpen: boolean = false;
|
|
|
selectedQuestions: QAItem[] = [];
|
|
|
answers: Record<string, any> = {};
|
|
|
attemptedSubmit: boolean = false;
|
|
|
isSubmitting: boolean = false;
|
|
|
userId: number = 0;
|
|
|
currentTabIndex: number = 0;
|
|
|
selectedReviewCategory: string = '';
|
|
|
categoriesMeta: CategoryMeta[] = [];
|
|
|
selectedCategory: string = '';
|
|
|
allowedRoles = new Set(['marriage', 'interview', 'partnership']);
|
|
|
BIT_FIELDS = new Set(['smoking', 'drinking', 'vegetarian']);
|
|
|
|
|
|
showSubmissionPopup: boolean = false;
|
|
|
isSubmissionSuccess: boolean = false;
|
|
|
submissionMessage: string = '';
|
|
|
isEditingExistingProfile: boolean = false;
|
|
|
existingProfileData: any = null;
|
|
|
openDropdowns: { [key: string]: boolean } = {};
|
|
|
|
|
|
|
|
|
showTextInputPopup: boolean = false;
|
|
|
popupQuestion: QAItem | null = null;
|
|
|
popupTextValue: string = '';
|
|
|
|
|
|
constructor(
|
|
|
private qaService: QuestionAnswerService,
|
|
|
private router: Router,
|
|
|
private route: ActivatedRoute,
|
|
|
private authService: AuthService
|
|
|
) { }
|
|
|
|
|
|
|
|
|
isAdditionalRemarksField(question?: QAItem): boolean {
|
|
|
if (question) {
|
|
|
|
|
|
const label = question.label?.toLowerCase();
|
|
|
const columnKey = question.column_key?.toLowerCase();
|
|
|
|
|
|
|
|
|
const popupFields = [
|
|
|
'additional remarks',
|
|
|
'remark',
|
|
|
'remarks'
|
|
|
|
|
|
];
|
|
|
|
|
|
return popupFields.some(field =>
|
|
|
label?.includes(field) || columnKey?.includes(field)
|
|
|
);
|
|
|
}
|
|
|
|
|
|
|
|
|
const currentLabel = this.popupQuestion?.label?.toLowerCase();
|
|
|
const currentColumnKey = this.popupQuestion?.column_key?.toLowerCase();
|
|
|
|
|
|
const popupFields = [
|
|
|
'additional remarks',
|
|
|
'remark',
|
|
|
'remarks'
|
|
|
];
|
|
|
|
|
|
return popupFields.some(field =>
|
|
|
currentLabel?.includes(field) || currentColumnKey?.includes(field)
|
|
|
);
|
|
|
}
|
|
|
|
|
|
|
|
|
toggleDropdown(columnKey: string): void {
|
|
|
|
|
|
this.openDropdowns = {};
|
|
|
|
|
|
|
|
|
this.openDropdowns[columnKey] = !this.openDropdowns[columnKey];
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
const formGroup = document.querySelector(`[id="q-${columnKey}"]`);
|
|
|
if (formGroup) {
|
|
|
if (this.openDropdowns[columnKey]) {
|
|
|
formGroup.classList.add('dropdown-open');
|
|
|
} else {
|
|
|
formGroup.classList.remove('dropdown-open');
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
isDropdownOpen(columnKey: string): boolean {
|
|
|
return this.openDropdowns[columnKey] === true;
|
|
|
}
|
|
|
|
|
|
getSelectedOptionsText(columnKey: string): string {
|
|
|
const value = this.answers[columnKey];
|
|
|
if (!Array.isArray(value) || value.length === 0) {
|
|
|
return '';
|
|
|
}
|
|
|
|
|
|
if (value.length === 1) {
|
|
|
return value[0];
|
|
|
}
|
|
|
|
|
|
return `${value.length} options selected`;
|
|
|
}
|
|
|
|
|
|
areAllOptionsSelected(question: QAItem): boolean {
|
|
|
const value = this.answers[question.column_key];
|
|
|
const options = this.getMultiselectOptions(question);
|
|
|
return Array.isArray(value) && options.length > 0 && value.length === options.length;
|
|
|
}
|
|
|
|
|
|
toggleSelectAll(question: QAItem): void {
|
|
|
const options = this.getMultiselectOptions(question);
|
|
|
|
|
|
if (!this.areAllOptionsSelected(question)) {
|
|
|
|
|
|
this.answers[question.column_key] = [...options];
|
|
|
} else {
|
|
|
|
|
|
this.answers[question.column_key] = [];
|
|
|
}
|
|
|
|
|
|
this.onAnswerChange(question.column_key, this.answers[question.column_key]);
|
|
|
}
|
|
|
|
|
|
|
|
|
openTextInputPopup(question: QAItem): void {
|
|
|
|
|
|
if (this.isAdditionalRemarksField(question)) {
|
|
|
this.popupQuestion = question;
|
|
|
this.popupTextValue = this.answers[question.column_key] || '';
|
|
|
this.showTextInputPopup = true;
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
this.initializeTextareaHeight();
|
|
|
});
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
closeTextInputPopup(): void {
|
|
|
this.showTextInputPopup = false;
|
|
|
this.popupQuestion = null;
|
|
|
this.popupTextValue = '';
|
|
|
}
|
|
|
|
|
|
saveTextInputPopup(): void {
|
|
|
if (this.popupQuestion) {
|
|
|
this.answers[this.popupQuestion.column_key] = this.popupTextValue;
|
|
|
this.onAnswerChange(this.popupQuestion.column_key, this.popupTextValue);
|
|
|
}
|
|
|
this.closeTextInputPopup();
|
|
|
}
|
|
|
|
|
|
|
|
|
onTextareaInput(event: Event): void {
|
|
|
const textarea = event.target as HTMLTextAreaElement;
|
|
|
this.adjustTextareaHeight(textarea);
|
|
|
}
|
|
|
|
|
|
initializeTextareaHeight(): void {
|
|
|
const textarea = document.querySelector('.text-input-popup-field') as HTMLTextAreaElement;
|
|
|
if (textarea) {
|
|
|
this.adjustTextareaHeight(textarea);
|
|
|
textarea.focus();
|
|
|
|
|
|
|
|
|
const length = textarea.value.length;
|
|
|
textarea.setSelectionRange(length, length);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
adjustTextareaHeight(textarea: HTMLTextAreaElement): void {
|
|
|
|
|
|
textarea.style.height = 'auto';
|
|
|
|
|
|
|
|
|
const maxHeight = 400;
|
|
|
const newHeight = Math.min(textarea.scrollHeight, maxHeight);
|
|
|
textarea.style.height = newHeight + 'px';
|
|
|
|
|
|
|
|
|
textarea.style.overflowY = textarea.scrollHeight > maxHeight ? 'auto' : 'hidden';
|
|
|
}
|
|
|
|
|
|
private enhanceUserExperience(): void {
|
|
|
if (this.selectedCategory !== 'review') {
|
|
|
setTimeout(() => {
|
|
|
const formContainer = document.querySelector('.form-container');
|
|
|
if (formContainer) {
|
|
|
formContainer.scrollTop = 0;
|
|
|
}
|
|
|
}, 150);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
getProgressBarWidth(): number {
|
|
|
if (this.selectedCategory === 'review') {
|
|
|
return 94;
|
|
|
}
|
|
|
|
|
|
const currentIndex = this.categoriesMeta.findIndex(cat => cat.key === this.selectedCategory);
|
|
|
if (currentIndex >= 0) {
|
|
|
return ((currentIndex + 1) / (this.categoriesMeta.length + 1)) * 100;
|
|
|
}
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
getStepLabel(title: string): string {
|
|
|
const words = title.split(' ');
|
|
|
return words.length > 2 ? words[0] : title.split(' ')[0];
|
|
|
}
|
|
|
|
|
|
@HostListener('window:resize')
|
|
|
onWindowResize() {
|
|
|
|
|
|
}
|
|
|
|
|
|
get totalRequired(): number {
|
|
|
return this.selectedQuestions.filter(q => q.required).length;
|
|
|
}
|
|
|
|
|
|
get answeredRequired(): number {
|
|
|
return this.selectedQuestions.filter(q => q.required && this.isAnswered(q)).length;
|
|
|
}
|
|
|
|
|
|
get progressPercent(): number {
|
|
|
return this.totalRequired ? Math.round((this.answeredRequired / this.totalRequired) * 100) : 0;
|
|
|
}
|
|
|
|
|
|
get allFieldsAnswered(): boolean {
|
|
|
return this.selectedQuestions.length > 0 && this.selectedQuestions.every(q => this.isAnswered(q));
|
|
|
}
|
|
|
|
|
|
get unansweredCount(): number {
|
|
|
return this.selectedQuestions.filter(q => !this.isAnswered(q)).length;
|
|
|
}
|
|
|
|
|
|
getCategoryQuestions(key: string): QAItem[] {
|
|
|
const norm = key.replace(/\s+/g, '').toLowerCase();
|
|
|
return this.selectedQuestions.filter(q => (q.category || '').replace(/\s+/g, '').toLowerCase() === norm);
|
|
|
}
|
|
|
|
|
|
getCategoryAnsweredCount(key: string): number {
|
|
|
return this.getCategoryQuestions(key).filter(q => this.isAnswered(q)).length;
|
|
|
}
|
|
|
|
|
|
getCategoryRequiredCount(key: string): number {
|
|
|
const qs = this.getCategoryQuestions(key);
|
|
|
const requiredCnt = qs.filter(q => q.required).length;
|
|
|
return requiredCnt || qs.length;
|
|
|
}
|
|
|
|
|
|
getCategoryPercent(key: string): number {
|
|
|
const denom = this.getCategoryRequiredCount(key) || 1;
|
|
|
return Math.round((this.getCategoryAnsweredCount(key) / denom) * 100);
|
|
|
}
|
|
|
|
|
|
showSubmissionSuccessPopup(): void {
|
|
|
this.showSubmissionPopup = true;
|
|
|
this.isSubmissionSuccess = true;
|
|
|
this.submissionMessage = 'Profile submitted successfully!';
|
|
|
}
|
|
|
|
|
|
redirectToHome(): void {
|
|
|
this.showSubmissionPopup = false;
|
|
|
this.router.navigate(['/']);
|
|
|
}
|
|
|
|
|
|
redirectToPreferences(): void {
|
|
|
this.showSubmissionPopup = false;
|
|
|
this.router.navigate(['/userpreferences'], {
|
|
|
queryParams: { user_id: this.userId }
|
|
|
});
|
|
|
}
|
|
|
|
|
|
closeSuccessModal(): void {
|
|
|
this.showSubmissionPopup = false;
|
|
|
|
|
|
const container = document.querySelector('.container');
|
|
|
const tabs = document.querySelectorAll('.tab');
|
|
|
|
|
|
if (container) {
|
|
|
container.classList.remove('blur-background');
|
|
|
}
|
|
|
|
|
|
tabs.forEach(tab => {
|
|
|
tab.classList.remove('blur-background');
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
closeModal(): void {
|
|
|
if (this.displayMode === 'modal') {
|
|
|
this.isModalOpen = false;
|
|
|
this.selectedQuestions = [];
|
|
|
this.answers = {};
|
|
|
this.selectedOption = '';
|
|
|
} else {
|
|
|
|
|
|
this.closeSuccessModal();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private checkExistingProfile(role: string): void {
|
|
|
this.isLoading = true;
|
|
|
|
|
|
this.qaService.getExistingProfile(role, this.userId).subscribe({
|
|
|
next: (existingData: any) => {
|
|
|
console.log('🟢 Existing profile data received:', existingData);
|
|
|
if (existingData && Object.keys(existingData).length > 0 && !existingData.error) {
|
|
|
this.isEditingExistingProfile = true;
|
|
|
this.existingProfileData = existingData;
|
|
|
console.log('🟢 Editing existing profile with data:', this.existingProfileData);
|
|
|
} else {
|
|
|
this.isEditingExistingProfile = false;
|
|
|
this.existingProfileData = null;
|
|
|
console.log('🟡 No existing profile found or empty data');
|
|
|
}
|
|
|
this.onSelect(role, 'page');
|
|
|
},
|
|
|
error: (err: HttpErrorResponse) => {
|
|
|
console.log('🟡 No existing profile found or error:', err.message);
|
|
|
this.isEditingExistingProfile = false;
|
|
|
this.existingProfileData = null;
|
|
|
this.onSelect(role, 'page');
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
onSelect(role: string, mode: 'modal' | 'page' = 'page'): void {
|
|
|
this.selectedOption = role;
|
|
|
this.displayMode = mode;
|
|
|
this.isModalOpen = mode === 'modal';
|
|
|
this.isLoading = true;
|
|
|
|
|
|
|
|
|
this.qaService.getCategories(role).subscribe({
|
|
|
next: (categories: CategoryMeta[]) => {
|
|
|
this.categoriesMeta = categories;
|
|
|
|
|
|
if (this.categoriesMeta.length > 0) {
|
|
|
this.selectedReviewCategory = this.categoriesMeta[0].key;
|
|
|
}
|
|
|
this.fetchQuestions(role);
|
|
|
},
|
|
|
error: (err: HttpErrorResponse) => {
|
|
|
console.error('Failed to load categories, using defaults', err);
|
|
|
|
|
|
this.categoriesMeta = this.getDefaultCategories();
|
|
|
|
|
|
if (this.categoriesMeta.length > 0) {
|
|
|
this.selectedReviewCategory = this.categoriesMeta[0].key;
|
|
|
}
|
|
|
this.fetchQuestions(role);
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
private getDefaultCategories(): CategoryMeta[] {
|
|
|
return [
|
|
|
{ key: 'Personal', title: 'Personal Information' },
|
|
|
{ key: 'Location', title: 'Location & Family' },
|
|
|
{ key: 'Lifestyle', title: 'Lifestyle & Preferences' },
|
|
|
{ key: 'Education', title: 'Education & Career' }
|
|
|
];
|
|
|
}
|
|
|
|
|
|
ngOnInit(): void {
|
|
|
this.route.queryParamMap.subscribe(params => {
|
|
|
const role = (params.get('role') || '').toLowerCase();
|
|
|
const userIdFromParams = params.get('user_id');
|
|
|
|
|
|
if (userIdFromParams) {
|
|
|
this.userId = Number(userIdFromParams);
|
|
|
} else {
|
|
|
this.userId = this.authService.userId || 0;
|
|
|
}
|
|
|
|
|
|
if (this.allowedRoles.has(role)) {
|
|
|
this.checkExistingProfile(role);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
|
|
|
if (this.categoriesMeta.length > 0) {
|
|
|
this.selectedReviewCategory = this.categoriesMeta[0].key;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
selectReviewCategory(categoryKey: string): void {
|
|
|
this.selectedReviewCategory = categoryKey;
|
|
|
}
|
|
|
|
|
|
ngOnDestroy(): void {
|
|
|
|
|
|
}
|
|
|
|
|
|
selectCategory(key: string): void {
|
|
|
this.selectedCategory = key;
|
|
|
const categoryIndex = this.categoriesMeta.findIndex(cat => cat.key === key);
|
|
|
this.currentTabIndex = categoryIndex >= 0 ? categoryIndex : this.categoriesMeta.length;
|
|
|
|
|
|
|
|
|
if (key === 'review' && this.categoriesMeta.length > 0) {
|
|
|
this.selectedReviewCategory = this.categoriesMeta[0].key;
|
|
|
}
|
|
|
|
|
|
this.enhanceUserExperience();
|
|
|
}
|
|
|
|
|
|
trackQuestion(index: number, item: QAItem): string {
|
|
|
return item.column_key;
|
|
|
}
|
|
|
|
|
|
fetchQuestions(role: string): void {
|
|
|
this.qaService.getQuestions(role).subscribe({
|
|
|
next: (data: QAItem[]) => {
|
|
|
this.selectedQuestions = data;
|
|
|
console.log('🟢 Questions loaded:', this.selectedQuestions.length);
|
|
|
|
|
|
this.initializeAnswers(this.selectedQuestions);
|
|
|
this.isLoading = false;
|
|
|
this.attemptedSubmit = false;
|
|
|
|
|
|
|
|
|
if (this.categoriesMeta.length > 0) {
|
|
|
this.selectedCategory = this.categoriesMeta[0].key;
|
|
|
this.currentTabIndex = 0;
|
|
|
}
|
|
|
|
|
|
if (role === 'marriage') {
|
|
|
this.displayMode = 'page';
|
|
|
this.isModalOpen = false;
|
|
|
} else if (this.displayMode === 'modal') {
|
|
|
this.isModalOpen = true;
|
|
|
} else {
|
|
|
this.isModalOpen = false;
|
|
|
}
|
|
|
|
|
|
setTimeout(() => {
|
|
|
this.enhanceUserExperience();
|
|
|
}, 500);
|
|
|
},
|
|
|
error: (err: HttpErrorResponse) => {
|
|
|
this.isLoading = false;
|
|
|
this.showSubmissionPopup = true;
|
|
|
this.isSubmissionSuccess = false;
|
|
|
this.submissionMessage = 'Failed to load questions. Please try again.';
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
private initializeAnswers(questions: QAItem[]): void {
|
|
|
this.answers = {};
|
|
|
console.log('🟢 Initializing answers. Editing existing profile:', this.isEditingExistingProfile);
|
|
|
console.log('🟢 Existing profile data:', this.existingProfileData);
|
|
|
|
|
|
questions.forEach(q => {
|
|
|
if (this.isEditingExistingProfile && this.existingProfileData) {
|
|
|
const existingValue = this.existingProfileData[q.column_key];
|
|
|
console.log(`🟢 Processing field ${q.column_key}:`, {
|
|
|
value: existingValue,
|
|
|
type: typeof existingValue,
|
|
|
inputType: q.input_type
|
|
|
});
|
|
|
|
|
|
if (q.input_type === 'multiselect') {
|
|
|
|
|
|
if (typeof existingValue === 'string' && existingValue) {
|
|
|
|
|
|
const cleanValue = existingValue.replace(/[\[\]"]/g, '');
|
|
|
this.answers[q.column_key] = cleanValue.split(',')
|
|
|
.map((item: string) => item.trim())
|
|
|
.filter(item => item.length > 0);
|
|
|
console.log(`🟢 Multiselect parsed: ${q.column_key} =`, this.answers[q.column_key]);
|
|
|
} else if (Array.isArray(existingValue)) {
|
|
|
this.answers[q.column_key] = existingValue.filter(item => item && item.length > 0);
|
|
|
} else {
|
|
|
this.answers[q.column_key] = [];
|
|
|
}
|
|
|
} else if (q.input_type === 'radio' || q.input_type === 'select') {
|
|
|
|
|
|
if (existingValue !== null && existingValue !== undefined && existingValue !== '') {
|
|
|
this.answers[q.column_key] = String(existingValue).trim();
|
|
|
console.log(`🟢 Radio/Select set: ${q.column_key} = "${this.answers[q.column_key]}"`);
|
|
|
} else {
|
|
|
this.answers[q.column_key] = '';
|
|
|
}
|
|
|
} else if (q.input_type === 'checkbox') {
|
|
|
|
|
|
if (existingValue === 1 || existingValue === '1' || existingValue === true || existingValue === 'true' || existingValue === 'Yes') {
|
|
|
this.answers[q.column_key] = true;
|
|
|
} else if (existingValue === 0 || existingValue === '0' || existingValue === false || existingValue === 'false' || existingValue === 'No') {
|
|
|
this.answers[q.column_key] = false;
|
|
|
} else {
|
|
|
this.answers[q.column_key] = false;
|
|
|
}
|
|
|
console.log(`🟢 Checkbox set: ${q.column_key} =`, this.answers[q.column_key]);
|
|
|
} else if (q.input_type === 'date' && existingValue) {
|
|
|
|
|
|
try {
|
|
|
if (existingValue instanceof Date) {
|
|
|
this.answers[q.column_key] = existingValue.toISOString().split('T')[0];
|
|
|
} else if (typeof existingValue === 'string') {
|
|
|
const date = new Date(existingValue);
|
|
|
if (!isNaN(date.getTime())) {
|
|
|
this.answers[q.column_key] = date.toISOString().split('T')[0];
|
|
|
} else {
|
|
|
this.answers[q.column_key] = existingValue;
|
|
|
}
|
|
|
} else {
|
|
|
this.answers[q.column_key] = existingValue;
|
|
|
}
|
|
|
} catch {
|
|
|
this.answers[q.column_key] = existingValue;
|
|
|
}
|
|
|
} else {
|
|
|
|
|
|
if (existingValue !== null && existingValue !== undefined) {
|
|
|
this.answers[q.column_key] = String(existingValue);
|
|
|
} else {
|
|
|
this.answers[q.column_key] = '';
|
|
|
}
|
|
|
console.log(`🟢 Text field set: ${q.column_key} = "${this.answers[q.column_key]}"`);
|
|
|
}
|
|
|
} else {
|
|
|
|
|
|
this.answers[q.column_key] = q.input_type === 'multiselect' ? [] :
|
|
|
q.input_type === 'checkbox' ? false : '';
|
|
|
console.log(`🟡 New profile - empty value for: ${q.column_key}`);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
console.log('🟢 Final answers object:', this.answers);
|
|
|
}
|
|
|
|
|
|
onAnswerChange(key: string, value: any): void {
|
|
|
this.answers[key] = value;
|
|
|
console.log(`🟢 Answer changed: ${key} =`, value);
|
|
|
}
|
|
|
|
|
|
isAnswered(q: QAItem): boolean {
|
|
|
const v = this.answers[q.column_key];
|
|
|
if (q.input_type === 'multiselect') {
|
|
|
return Array.isArray(v) && v.length > 0;
|
|
|
}
|
|
|
if (q.input_type === 'checkbox') {
|
|
|
return v !== undefined && v !== null;
|
|
|
}
|
|
|
return v !== undefined && v !== null && v !== '' && v !== false;
|
|
|
}
|
|
|
|
|
|
private normalizeForBackend(obj: Record<string, any>): Record<string, any> {
|
|
|
const out: Record<string, any> = {};
|
|
|
for (const [k, v] of Object.entries(obj)) {
|
|
|
if (!this.BIT_FIELDS.has(k)) {
|
|
|
out[k] = v;
|
|
|
continue;
|
|
|
}
|
|
|
if (v === undefined || v === null || v === '') {
|
|
|
out[k] = null;
|
|
|
continue;
|
|
|
}
|
|
|
if (typeof v === 'boolean') {
|
|
|
out[k] = v ? 1 : 0;
|
|
|
continue;
|
|
|
}
|
|
|
if (typeof v === 'number') {
|
|
|
out[k] = v !== 0 ? 1 : 0;
|
|
|
continue;
|
|
|
}
|
|
|
if (typeof v === 'string') {
|
|
|
const s = v.trim().toLowerCase();
|
|
|
if (['yes', 'true', 'y', '1'].includes(s)) {
|
|
|
out[k] = 1;
|
|
|
continue;
|
|
|
}
|
|
|
if (['no', 'false', 'n', '0'].includes(s)) {
|
|
|
out[k] = 0;
|
|
|
continue;
|
|
|
}
|
|
|
out[k] = v;
|
|
|
continue;
|
|
|
}
|
|
|
out[k] = v;
|
|
|
}
|
|
|
return out;
|
|
|
}
|
|
|
|
|
|
submitAnswers(): void {
|
|
|
this.attemptedSubmit = true;
|
|
|
|
|
|
const incompleteRequired = this.selectedQuestions.filter(q => q.required && !this.isAnswered(q));
|
|
|
if (incompleteRequired.length) {
|
|
|
const first = incompleteRequired[0];
|
|
|
const el = document.getElementById('q-' + first.column_key);
|
|
|
if (el) {
|
|
|
el.scrollIntoView({
|
|
|
behavior: 'smooth',
|
|
|
block: 'center',
|
|
|
inline: 'nearest'
|
|
|
});
|
|
|
|
|
|
el.classList.add('highlight-required');
|
|
|
setTimeout(() => {
|
|
|
el.classList.remove('highlight-required');
|
|
|
}, 2000);
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
this.isLoading = true;
|
|
|
this.isSubmitting = true;
|
|
|
|
|
|
const normalizedAnswers = this.normalizeForBackend(this.answers);
|
|
|
|
|
|
const fields: Record<string, any> = {};
|
|
|
this.selectedQuestions.forEach((q) => {
|
|
|
fields[q.column_key] = normalizedAnswers[q.column_key] ?? null;
|
|
|
});
|
|
|
|
|
|
const payload = {
|
|
|
user_id: this.userId,
|
|
|
...fields
|
|
|
};
|
|
|
|
|
|
console.log('🟢 Submitting payload:', payload);
|
|
|
|
|
|
const submitObservable = this.isEditingExistingProfile
|
|
|
? this.qaService.updateAnswers(this.selectedOption, this.userId, payload)
|
|
|
: this.qaService.submitAnswers(this.selectedOption, this.userId, payload);
|
|
|
|
|
|
submitObservable.subscribe({
|
|
|
next: (response) => {
|
|
|
this.isLoading = false;
|
|
|
this.isSubmitting = false;
|
|
|
console.log('🟢 Submission successful:', response);
|
|
|
|
|
|
|
|
|
const userId = this.userId;
|
|
|
if (userId) {
|
|
|
const existingProgress = localStorage.getItem(`user_progress_${userId}`);
|
|
|
let progressData: any = {};
|
|
|
|
|
|
if (existingProgress) {
|
|
|
try {
|
|
|
progressData = JSON.parse(existingProgress);
|
|
|
} catch (e) {
|
|
|
console.error('Error parsing existing progress:', e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
progressData.profileCompleted = true;
|
|
|
localStorage.setItem(`user_progress_${userId}`, JSON.stringify(progressData));
|
|
|
}
|
|
|
|
|
|
|
|
|
const profileCompletedEvent = new CustomEvent('profileCompleted', {
|
|
|
detail: {
|
|
|
userId: this.userId,
|
|
|
role: this.selectedOption,
|
|
|
timestamp: Date.now()
|
|
|
}
|
|
|
});
|
|
|
window.dispatchEvent(profileCompletedEvent);
|
|
|
|
|
|
this.showSubmissionSuccessPopup();
|
|
|
},
|
|
|
error: (err: HttpErrorResponse) => {
|
|
|
this.isLoading = false;
|
|
|
this.isSubmitting = false;
|
|
|
const reason = err.status === 0
|
|
|
? 'Network/CORS or server unreachable'
|
|
|
: (err.error?.error || err.message || 'Server error');
|
|
|
|
|
|
console.error('🔴 Submission error:', err);
|
|
|
this.showSubmissionPopup = true;
|
|
|
this.isSubmissionSuccess = false;
|
|
|
this.submissionMessage = `Submission failed: ${reason}. Please try again.`;
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
getMultiselectOptions(question: QAItem): string[] {
|
|
|
return question.options || [];
|
|
|
}
|
|
|
|
|
|
isMultiselectSelected(key: string, option: string): boolean {
|
|
|
return Array.isArray(this.answers[key]) && this.answers[key].includes(option);
|
|
|
}
|
|
|
|
|
|
@HostListener('document:click', ['$event'])
|
|
|
onDocumentClick(event: MouseEvent): void {
|
|
|
const target = event.target as HTMLElement;
|
|
|
if (!target.closest('.multiselect-dropdown-checkbox')) {
|
|
|
|
|
|
document.querySelectorAll('.form-group.dropdown-open').forEach(group => {
|
|
|
group.classList.remove('dropdown-open');
|
|
|
});
|
|
|
this.openDropdowns = {};
|
|
|
}
|
|
|
}
|
|
|
|
|
|
onMultiselectChange(event: Event, key: string, option?: string): void {
|
|
|
const input = event.target as HTMLInputElement;
|
|
|
|
|
|
if (!Array.isArray(this.answers[key])) {
|
|
|
this.answers[key] = [];
|
|
|
}
|
|
|
|
|
|
const value = option || input.value;
|
|
|
const isChecked = input.checked;
|
|
|
|
|
|
if (isChecked) {
|
|
|
if (!this.answers[key].includes(value)) {
|
|
|
this.answers[key].push(value);
|
|
|
}
|
|
|
} else {
|
|
|
this.answers[key] = this.answers[key].filter((v: string) => v !== value);
|
|
|
}
|
|
|
|
|
|
this.onAnswerChange(key, this.answers[key]);
|
|
|
}
|
|
|
|
|
|
isRadioSelected(key: string, option: string): boolean {
|
|
|
return this.answers[key] === option;
|
|
|
}
|
|
|
|
|
|
onRadioChange(key: string, option: string): void {
|
|
|
this.answers[key] = option;
|
|
|
this.onAnswerChange(key, option);
|
|
|
}
|
|
|
|
|
|
goToPreviousCategory(currentKey: string): void {
|
|
|
const currentIndex = this.categoriesMeta.findIndex(cat => cat.key === currentKey);
|
|
|
if (currentIndex > 0) {
|
|
|
this.selectedCategory = this.categoriesMeta[currentIndex - 1].key;
|
|
|
this.currentTabIndex = currentIndex - 1;
|
|
|
this.enhanceUserExperience();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
goToNextCategory(currentKey: string): void {
|
|
|
const currentIndex = this.categoriesMeta.findIndex(cat => cat.key === currentKey);
|
|
|
if (currentIndex < this.categoriesMeta.length - 1) {
|
|
|
this.selectedCategory = this.categoriesMeta[currentIndex + 1].key;
|
|
|
this.currentTabIndex = currentIndex + 1;
|
|
|
} else {
|
|
|
this.selectedCategory = 'review';
|
|
|
this.currentTabIndex = this.categoriesMeta.length;
|
|
|
|
|
|
if (this.categoriesMeta.length > 0) {
|
|
|
this.selectedReviewCategory = this.categoriesMeta[0].key;
|
|
|
}
|
|
|
}
|
|
|
this.enhanceUserExperience();
|
|
|
}
|
|
|
|
|
|
getAnswerDisplay(question: QAItem): string {
|
|
|
const value = this.answers[question.column_key];
|
|
|
if (value === null || value === undefined || value === '') {
|
|
|
return '(Not answered)';
|
|
|
}
|
|
|
if (Array.isArray(value)) {
|
|
|
return value.length > 0 ? value.join(', ') : '(Not answered)';
|
|
|
}
|
|
|
if (typeof value === 'boolean') {
|
|
|
return value ? 'Yes' : 'No';
|
|
|
}
|
|
|
return String(value);
|
|
|
}
|
|
|
|
|
|
getPlaceholder(question: QAItem): string {
|
|
|
if (question.placeholder) return question.placeholder;
|
|
|
switch (question.input_type) {
|
|
|
case 'date': return 'Select date';
|
|
|
case 'select': return 'Select option';
|
|
|
default: return 'Enter your answer';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
showError(q: QAItem): boolean {
|
|
|
return this.attemptedSubmit && !!q.required && !this.isAnswered(q);
|
|
|
}
|
|
|
|
|
|
toTitleCase(text: string): string {
|
|
|
return text.replace(/\w\S*/g, (txt) => {
|
|
|
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
|
|
});
|
|
|
}
|
|
|
|
|
|
getCategoryIcon(categoryKey: string): string {
|
|
|
const iconMap: { [key: string]: string } = {
|
|
|
'Personal': 'fa-user',
|
|
|
'Location': 'fa-home',
|
|
|
'Lifestyle': 'fa-heart',
|
|
|
'Education': 'fa-graduation-cap',
|
|
|
'review': 'fa-check'
|
|
|
};
|
|
|
|
|
|
return iconMap[categoryKey] || 'fa-question';
|
|
|
}
|
|
|
|
|
|
getCurrentCategoryIcon(): string {
|
|
|
return this.getCategoryIcon(this.selectedCategory);
|
|
|
}
|
|
|
|
|
|
getCurrentCategoryTitle(): string {
|
|
|
if (this.selectedCategory === 'review') {
|
|
|
return 'Review Your Information';
|
|
|
}
|
|
|
const category = this.categoriesMeta.find(c => c.key === this.selectedCategory);
|
|
|
return category?.title || 'Category';
|
|
|
}
|
|
|
|
|
|
getCurrentCategoryDescription(): string {
|
|
|
const descriptions: Record<string, string> = {
|
|
|
'Personal': 'Please provide your personal information',
|
|
|
'Location': 'Share your location and family details',
|
|
|
'Lifestyle': 'Describe your lifestyle and preferences',
|
|
|
'Education': 'Tell us about your education and career',
|
|
|
'review': 'Review all the information you have provided'
|
|
|
};
|
|
|
return descriptions[this.selectedCategory] || 'Complete this section';
|
|
|
}
|
|
|
|
|
|
getReviewCategoryTitle(categoryKey: string): string {
|
|
|
const category = this.categoriesMeta.find(c => c.key === categoryKey);
|
|
|
return category?.title || categoryKey;
|
|
|
}
|
|
|
|
|
|
getCategoryAnsweredCountByKey(categoryKey: string): number {
|
|
|
return this.getCategoryAnsweredCount(categoryKey);
|
|
|
}
|
|
|
|
|
|
getCategoryRequiredCountByKey(categoryKey: string): number {
|
|
|
return this.getCategoryRequiredCount(categoryKey);
|
|
|
}
|
|
|
}
|
|
|
|