py-match / src /app /question-answer /question-answer.component.ts
Swetha
fix
d1ee381
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';
// Add this simple pipe at the top of the file
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 } = {};
// Add new properties for text input popup
showTextInputPopup: boolean = false;
popupQuestion: QAItem | null = null;
popupTextValue: string = '';
constructor(
private qaService: QuestionAnswerService,
private router: Router,
private route: ActivatedRoute,
private authService: AuthService
) { }
// Add this method to check if the field should use popup overlay
isAdditionalRemarksField(question?: QAItem): boolean {
if (question) {
// Check if this is specifically the "Additional Remarks" field
const label = question.label?.toLowerCase();
const columnKey = question.column_key?.toLowerCase();
// Only show popup for Additional Remarks and similar specific fields
const popupFields = [
'additional remarks',
'remark',
'remarks'
// Remove other fields if you only want Additional Remarks
];
return popupFields.some(field =>
label?.includes(field) || columnKey?.includes(field)
);
}
// For the popup overlay check (when no question parameter)
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)
);
}
// Add these methods for dropdown functionality
toggleDropdown(columnKey: string): void {
// Close all other dropdowns first
this.openDropdowns = {};
// Toggle the current dropdown
this.openDropdowns[columnKey] = !this.openDropdowns[columnKey];
// Add/remove class to form group for z-index management
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)) {
// Select all
this.answers[question.column_key] = [...options];
} else {
// Deselect all
this.answers[question.column_key] = [];
}
this.onAnswerChange(question.column_key, this.answers[question.column_key]);
}
// Add these methods for text input popup functionality
openTextInputPopup(question: QAItem): void {
// Only open popup for specific fields (like Additional Remarks)
if (this.isAdditionalRemarksField(question)) {
this.popupQuestion = question;
this.popupTextValue = this.answers[question.column_key] || '';
this.showTextInputPopup = true;
// Initialize textarea height after view updates
setTimeout(() => {
this.initializeTextareaHeight();
});
}
// For other fields, do nothing (no popup will open)
}
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();
}
// Auto-expanding textarea functionality for popup only
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();
// Set cursor to end of text
const length = textarea.value.length;
textarea.setSelectionRange(length, length);
}
}
adjustTextareaHeight(textarea: HTMLTextAreaElement): void {
// Reset height to auto to get the correct scrollHeight
textarea.style.height = 'auto';
// Set the height to scrollHeight to expand, with max limit
const maxHeight = 400;
const newHeight = Math.min(textarea.scrollHeight, maxHeight);
textarea.style.height = newHeight + 'px';
// Enable scrolling only when content exceeds max height
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() {
// Responsive behavior placeholder
}
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;
// Remove blur effects from main content
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');
});
}
// Single closeModal method that handles both scenarios
closeModal(): void {
if (this.displayMode === 'modal') {
this.isModalOpen = false;
this.selectedQuestions = [];
this.answers = {};
this.selectedOption = '';
} else {
// For page mode, close the success modal
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;
// Fetch categories first, then questions
this.qaService.getCategories(role).subscribe({
next: (categories: CategoryMeta[]) => {
this.categoriesMeta = categories;
// Set default review category
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);
// Fallback to default categories if API fails
this.categoriesMeta = this.getDefaultCategories();
// Set default review category
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);
}
});
// Set default review category
if (this.categoriesMeta.length > 0) {
this.selectedReviewCategory = this.categoriesMeta[0].key;
}
}
// Add this method to handle review category selection
selectReviewCategory(categoryKey: string): void {
this.selectedReviewCategory = categoryKey;
}
ngOnDestroy(): void {
// Clean up if needed
}
selectCategory(key: string): void {
this.selectedCategory = key;
const categoryIndex = this.categoriesMeta.findIndex(cat => cat.key === key);
this.currentTabIndex = categoryIndex >= 0 ? categoryIndex : this.categoriesMeta.length;
// Initialize selected review category when entering review tab
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;
// Set initial category after questions are loaded
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') {
// Handle multiselect fields
if (typeof existingValue === 'string' && existingValue) {
// Clean and split comma-separated string
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') {
// Handle radio buttons and select fields
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') {
// Handle checkbox fields
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) {
// Handle date fields
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 {
// Handle text, textarea, number fields
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 {
// Initialize empty values for new profile
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);
// Update progress tracking
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));
}
// Dispatch event for progress updates
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')) {
// Remove dropdown-open class from all form groups
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;
// Ensure review category is set when navigating to review
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);
}
}