mirror of
https://github.com/vee1e/bulk-questionnaire-upload.git
synced 2026-09-01 17:57:10 +00:00
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:
parent
8eba3533dc
commit
6951569983
6 changed files with 78 additions and 42 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue