feat: enhance form metadata handling and UI updates

- Added default values for 'version' and 'created_at' fields in the database service.
- Updated XLSFormParser to parse and include 'version' and 'created_at' from the form metadata.
- Changed font from 'Roboto' to 'Inter' across frontend components for consistency.
- Improved upload component to display default values for 'language', 'version', and 'created_at' in the form details.
- Adjusted progress display messages for clarity during file uploads.
This commit is contained in:
vee1e 2025-08-27 16:00:10 +05:30
parent 8eba3533dc
commit 6951569983
6 changed files with 78 additions and 42 deletions

View file

@ -61,6 +61,13 @@ class DatabaseService:
try:
form = await forms_collection.find_one({"_id": ObjectId(form_id)})
if form:
if 'version' not in form or form['version'] in (None, ''):
form['version'] = '1.0.0'
if 'created_at' not in form or form['created_at'] in (None, ''):
try:
form['created_at'] = form['_id'].generation_time.isoformat()
except Exception:
pass
form['id'] = str(form['_id'])
del form['_id']
return form
@ -97,6 +104,13 @@ class DatabaseService:
try:
forms = await forms_collection.find().sort("created_at", -1).to_list(length=10000)
for form in forms:
if 'version' not in form or form['version'] in (None, ''):
form['version'] = '1.0.0'
if 'created_at' not in form or form['created_at'] in (None, ''):
try:
form['created_at'] = form['_id'].generation_time.isoformat()
except Exception:
pass
form['id'] = str(form['_id'])
del form['_id']
return forms

View file

@ -578,7 +578,8 @@ class XLSFormParser:
raise error
start_form = time.time()
form_id = await self.db_service.save_form(form_metadata)
parsed_metadata = self._parse_form_metadata(forms_df)
form_id = await self.db_service.save_form(parsed_metadata)
form_time = time.time() - start_form
log_metric('form_process_time', form_time)
@ -601,7 +602,7 @@ class XLSFormParser:
log_metric('avg_one_option_process_time', avg_option_time)
form_title = self._get_form_title(forms_df)
form_version = '1.0.0'
form_version = parsed_metadata.get('version', '1.0.0')
groups = self._parse_questions(questions_df, options_df)
total_time = time.time() - start_all
@ -905,10 +906,18 @@ class XLSFormParser:
}
if not forms_df.empty:
if 'Language' in forms_df.columns:
metadata['language'] = forms_df.iloc[0]['Language']
if 'Title' in forms_df.columns:
metadata['title'] = forms_df.iloc[0]['Title']
first = forms_df.iloc[0]
if 'Language' in forms_df.columns and pd.notna(first['Language']):
metadata['language'] = str(first['Language']).strip()
if 'Title' in forms_df.columns and pd.notna(first['Title']):
metadata['title'] = str(first['Title']).strip()
if 'Version' in forms_df.columns and pd.notna(first['Version']):
metadata['version'] = str(first['Version']).strip()
if 'Created At' in forms_df.columns and pd.notna(first['Created At']):
try:
metadata['created_at'] = pd.to_datetime(first['Created At']).isoformat()
except Exception:
pass
return metadata

View file

@ -145,7 +145,7 @@ import { Subscription } from 'rxjs';
background: rgba(255, 255, 255, 0.1);
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-family: monospace;
font-family: 'Inter', sans-serif;
border: 1px solid rgba(255, 255, 255, 0.2);
}
}

View file

@ -36,10 +36,10 @@ import { FormPreviewService } from '../../services/form-preview.service';
*ngIf="isValidating">
Validating {{validationProgress.current}}/{{validationProgress.total}}...
</div>
<div class="progress-counter"
[ngClass]="{'counter-orange': isValidating || isUploading, 'counter-purple': isParsing, 'counter-red': isDeletingAll}"
<div class="progress-counter"
[ngClass]="{'counter-orange': isValidating || isUploading, 'counter-purple': isParsing, 'counter-red': isDeletingAll}"
*ngIf="isUploading">
Processing {{uploadProgress.current}}/{{uploadProgress.total}}...
Processing {{uploadProgress.total}} form(s) concurrently...
</div>
<div class="progress-counter"
[ngClass]="{'counter-orange': isValidating || isUploading, 'counter-purple': isParsing, 'counter-red': isDeletingAll}"
@ -284,7 +284,7 @@ import { FormPreviewService } from '../../services/form-preview.service';
<mat-spinner *ngIf="loadingFormId === form.id" diameter="24" class="loading-spinner"></mat-spinner>
<div class="form-details">
<h4 class="form-title" [title]="form.title">{{form.title}}</h4>
<p>{{form.language}} {{form.version}} {{form.created_at | date:'short'}} </p>
<p>{{form.language || 'en'}} {{form.version || '1.0.0'}} {{(form.created_at ? (form.created_at | date:'short') : 'Unknown')}}</p>
</div>
</div>
@ -1037,7 +1037,7 @@ import { FormPreviewService } from '../../services/form-preview.service';
}
.form-title {
max-width: 300px;
max-width: 1200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@ -1221,7 +1221,7 @@ import { FormPreviewService } from '../../services/form-preview.service';
}
.form-title {
max-width: 200px;
max-width: 320px;
}
.file-name {
@ -1235,7 +1235,7 @@ import { FormPreviewService } from '../../services/form-preview.service';
}
.form-title {
max-width: 150px;
max-width: 220px;
}
.file-name {
@ -1440,7 +1440,7 @@ import { FormPreviewService } from '../../services/form-preview.service';
margin: 0;
padding: 1rem;
color: #ffffff;
font-family: 'Courier New', monospace;
font-family: 'Inter', sans-serif;
font-size: 0.8rem;
line-height: 1.4;
white-space: pre-wrap;
@ -1630,32 +1630,37 @@ export class UploadComponent implements OnInit, OnChanges {
if (this.selectedFiles.length === 0 || !this.allValid) return;
this.isUploading = true;
this.uploadProgress = { current: 0, total: this.selectedFiles.length };
let uploaded = 0;
// Simulate per-file upload progress if possible
const uploadNext = (index: number) => {
if (index >= this.selectedFiles.length) {
this.isUploading = false;
this.selectedFiles = [];
this.validationResults = {};
this.allValid = false;
this.loadForms();
return;
}
this.formService.uploadFiles([this.selectedFiles[index]]).subscribe({
next: () => {
uploaded++;
this.uploadProgress.current = uploaded;
uploadNext(index + 1);
},
error: (error: any) => {
uploaded++;
this.uploadProgress.current = uploaded;
console.error('Upload failed:', error);
uploadNext(index + 1);
// Send all files at once for concurrent processing
this.formService.uploadFiles(this.selectedFiles).subscribe({
next: (results: any[]) => {
this.uploadProgress.current = this.selectedFiles.length;
// Show results for each file
const successful = results.filter(r => !r.error).length;
const failed = results.filter(r => r.error).length;
if (failed === 0) {
this.snackBar.open(`✅ Successfully processed ${successful} form(s)!`, 'Close', { duration: 5000 });
} else {
this.snackBar.open(`⚠️ Processed ${successful} form(s), ${failed} failed. Check console for details.`, 'Close', { duration: 7000 });
}
});
};
uploadNext(0);
// Wait a moment for database operations to complete, then refresh the forms list
setTimeout(() => {
this.isUploading = false;
this.selectedFiles = [];
this.validationResults = {};
this.allValid = false;
this.loadForms();
}, 1000); // 1 second delay to ensure database operations complete
},
error: (error: any) => {
this.isUploading = false;
console.error('Upload failed:', error);
this.snackBar.open(`❌ Upload failed: ${error.error?.message || error.message || 'Unknown error'}`, 'Close', { duration: 5000 });
}
});
}
private resetUploadState(): void {
@ -1665,13 +1670,16 @@ export class UploadComponent implements OnInit, OnChanges {
}
loadForms(): void {
console.log('Loading forms from database...');
this.formService.getAllForms().subscribe({
next: (response) => {
console.log(`Loaded ${response.forms.length} forms from database`);
this.parsedForms = response.forms;
this.applySearch();
},
error: (error: any) => {
console.error('Failed to load forms:', error);
this.snackBar.open('❌ Failed to refresh forms list', 'Close', { duration: 3000 });
}
});
}

View file

@ -6,7 +6,7 @@
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="favicon.png">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body class="mat-typography">

View file

@ -5,12 +5,17 @@
html, body {
height: 100%;
margin: 0;
font-family: 'Roboto', sans-serif;
font-family: 'Inter', sans-serif !important;
background: #000000;
color: #ffffff;
overflow: hidden;
}
/* Force Inter for all text elements */
body, p, span, div, h1, h2, h3, h4, h5, h6, input, textarea, button, select, option {
font-family: 'Inter', sans-serif !important;
}
body {
background: #000000;
min-height: 100vh;