11<template >
22 <v-container fluid >
3- <v-row >
3+ <v-row v-if = " !parsingComplete " >
44 <v-col cols =" 12" >
55 <v-textarea
66 v-model =" bulkText"
@@ -19,23 +19,91 @@ Another {{blank}}
1919elo: 1200
2020tags: tagC"
2121 rows =" 15"
22- varient =" outlined"
22+ variant =" outlined"
2323 data-cy =" bulk-import-textarea"
2424 ></v-textarea >
2525 </v-col >
2626 </v-row >
27+
28+ <!-- Card Parsing Summary Section -->
29+ <v-row v-if =" parsingComplete" class =" mb-4" >
30+ <v-col cols =" 12" >
31+ <v-card border >
32+ <v-card-title >Parsing Summary</v-card-title >
33+ <v-card-text >
34+ <p >
35+ <strong >{{ parsedCards.length }}</strong > card(s) parsed and ready for import.
36+ </p >
37+ <div v-if =" parsedCards.length > 0" >
38+ <strong >Tags Found:</strong >
39+ <template v-if =" uniqueTags .length > 0 " >
40+ <v-chip v-for =" tag in uniqueTags" :key =" tag" class =" mr-1 mb-1" size =" small" label >
41+ {{ tag }}
42+ </v-chip >
43+ </template >
44+ <template v-else >
45+ <span class =" text--secondary" >No unique tags identified across parsed cards.</span >
46+ </template >
47+ </div >
48+ <!--
49+ Future enhancement: Add a paginated/scrollable list of parsed cards here for review.
50+ -->
51+ </v-card-text >
52+ </v-card >
53+ </v-col >
54+ </v-row >
55+
2756 <v-row >
2857 <v-col cols =" 12" >
58+ <!-- Button for initial parsing -->
2959 <v-btn
60+ v-if =" !parsingComplete"
3061 color =" primary"
3162 :loading =" processing"
32- :disabled =" !bulkText.trim()"
33- data-cy =" bulk-import-process -btn"
34- @click =" processCards "
63+ :disabled =" !bulkText.trim() || processing "
64+ data-cy =" bulk-import-parse -btn"
65+ @click =" handleInitialParse "
3566 >
36- Process Cards
67+ Parse Cards
3768 <v-icon end >mdi-play-circle-outline</v-icon >
3869 </v-btn >
70+
71+ <!-- Buttons for post-parsing stage -->
72+ <template v-if =" parsingComplete " >
73+ <v-btn
74+ color =" primary"
75+ class =" mr-2"
76+ :loading =" processing"
77+ :disabled =" parsedCards.length === 0 || processing || importAttempted"
78+ data-cy =" bulk-import-confirm-btn"
79+ @click =" confirmAndImportCards"
80+ >
81+ Confirm and Import {{ parsedCards.length }} Card(s)
82+ <v-icon end >mdi-check-circle-outline</v-icon >
83+ </v-btn >
84+ <v-btn
85+ v-if =" !importAttempted"
86+ variant =" outlined"
87+ color =" grey-darken-1"
88+ :disabled =" processing"
89+ data-cy =" bulk-import-edit-again-btn"
90+ @click =" resetToInputStage"
91+ >
92+ <v-icon start >mdi-pencil</v-icon >
93+ Edit Again
94+ </v-btn >
95+ <v-btn
96+ v-if =" importAttempted"
97+ variant =" outlined"
98+ color =" blue-darken-1"
99+ :disabled =" processing"
100+ data-cy =" bulk-import-add-another-btn"
101+ @click =" startNewBulkImport"
102+ >
103+ <v-icon start >mdi-plus-circle-outline</v-icon >
104+ Add Another Bulk Import
105+ </v-btn >
106+ </template >
39107 </v-col >
40108 </v-row >
41109 <v-row v-if =" results.length > 0" >
@@ -45,7 +113,11 @@ tags: tagC"
45113 <v-list-item
46114 v-for =" (result, index) in results"
47115 :key =" index"
48- :class =" { 'lime-lighten-5': result.status === 'success', 'red-lighten-5': result.status === 'error' }"
116+ :class =" {
117+ 'lime-lighten-5': result.status === 'success',
118+ 'red-lighten-5': result.status === 'error',
119+ 'force-dark-text': result.status === 'success' || result.status === 'error',
120+ }"
49121 >
50122 <v-list-item-title >
51123 <v-icon :color =" result.status === 'success' ? 'green' : 'red'" >
@@ -77,7 +149,14 @@ import { BlanksCardDataShapes } from '@vue-skuilder/courses';
77149import { getCurrentUser } from ' @vue-skuilder/common-ui' ;
78150import { getDataLayer , CourseDBInterface } from ' @vue-skuilder/db' ;
79151import { alertUser } from ' @vue-skuilder/common-ui' ; // For user feedback
80- import { ImportResult , processBulkCards , validateProcessorConfig , isValidBulkFormat } from ' @/utils/bulkImport' ;
152+ import {
153+ ImportResult ,
154+ ParsedCard ,
155+ parseBulkTextToCards ,
156+ importParsedCards ,
157+ validateProcessorConfig ,
158+ isValidBulkFormat ,
159+ } from ' @/utils/bulkImport' ;
81160
82161export default defineComponent ({
83162 name: ' BulkImportView' ,
@@ -90,11 +169,28 @@ export default defineComponent({
90169 data() {
91170 return {
92171 bulkText: ' ' ,
172+ parsedCards: [] as ParsedCard [],
173+ parsingComplete: false ,
174+ importAttempted: false ,
93175 results: [] as ImportResult [],
94- processing: false ,
176+ processing: false , // Will be used for both parsing and import stages
95177 courseDB: null as CourseDBInterface | null ,
96178 };
97179 },
180+ computed: {
181+ uniqueTags(): string [] {
182+ if (! this .parsedCards || this .parsedCards .length === 0 ) {
183+ return [];
184+ }
185+ const allTags = this .parsedCards .reduce ((acc , card ) => {
186+ if (card .tags && card .tags .length > 0 ) {
187+ acc .push (... card .tags );
188+ }
189+ return acc ;
190+ }, [] as string []);
191+ return [... new Set (allTags )].sort ();
192+ },
193+ },
98194 created() {
99195 if (this .courseCfg ?.courseID ) {
100196 this .courseDB = getDataLayer ().getCourseDB (this .courseCfg .courseID );
@@ -116,18 +212,76 @@ export default defineComponent({
116212 }
117213 },
118214 methods: {
119- async processCards() {
215+ resetToInputStage() {
216+ this .parsingComplete = false ;
217+ this .parsedCards = [];
218+ this .importAttempted = false ; // Reset import attempt flag
219+ // Optionally keep results if you want to show them even after going back
220+ // this.results = [];
221+ // this.bulkText = ''; // Optionally clear the bulk text
222+ },
223+
224+ startNewBulkImport() {
225+ this .bulkText = ' ' ;
226+ this .results = [];
227+ this .parsedCards = [];
228+ this .parsingComplete = false ;
229+ this .importAttempted = false ;
230+ },
231+
232+ handleInitialParse() {
120233 if (! this .courseDB ) {
121234 alertUser ({
122235 text: ' Database connection not available. Cannot process cards.' ,
123236 status: Status .error ,
124237 });
125- this .processing = false ;
126238 return ;
127239 }
128240
241+ // isValidBulkFormat calls alertUser internally if format is invalid.
129242 if (! isValidBulkFormat (this .bulkText )) {
243+ return ;
244+ }
245+
246+ this .processing = true ;
247+ this .results = []; // Clear previous import results
248+ this .parsedCards = []; // Clear previously parsed cards
249+ this .parsingComplete = false ; // Reset parsing complete state
250+
251+ try {
252+ this .parsedCards = parseBulkTextToCards (this .bulkText );
253+
254+ if (this .parsedCards .length === 0 ) {
255+ alertUser ({
256+ text: ' No cards could be parsed from the input. Please check the format and ensure cards are separated by two "---" lines and that cards have content.' ,
257+ status: Status .warning ,
258+ });
259+ this .processing = false ;
260+ return ;
261+ }
262+
263+ // Successfully parsed, ready for review stage
264+ this .parsingComplete = true ;
265+ } catch (error ) {
266+ console .error (' [BulkImportView] Error parsing bulk text:' , error );
267+ alertUser ({
268+ text: ` Error parsing cards: ${error instanceof Error ? error .message : ' Unknown error' } ` ,
269+ status: Status .error ,
270+ });
271+ } finally {
130272 this .processing = false ;
273+ }
274+ },
275+
276+ async confirmAndImportCards() {
277+ if (! this .courseDB ) {
278+ alertUser ({ text: ' Database connection lost before import.' , status: Status .error });
279+ this .processing = false ; // Ensure processing is false
280+ return ;
281+ }
282+ if (this .parsedCards .length === 0 ) {
283+ alertUser ({ text: ' No parsed cards to import.' , status: Status .warning });
284+ this .processing = false ; // Ensure processing is false
131285 return ;
132286 }
133287
@@ -142,82 +296,76 @@ export default defineComponent({
142296 }
143297
144298 this .processing = true ;
145- this .results = [];
299+ this .results = []; // Clear results from parsing stage or previous attempts
146300
147301 const currentUser = await getCurrentUser ();
148302 const userName = currentUser .getUsername ();
149303
150- // Use the BlanksCardDataShapes for the data structure
151304 const dataShapeToUse: DataShape = BlanksCardDataShapes [0 ];
152305
153306 if (! dataShapeToUse ) {
154- this .results .push ({
155- originalText: ' N/A - Configuration Error' ,
156- status: ' error' ,
157- message: ' Could not find BlanksCardDataShapes. Aborting.' ,
158- });
307+ alertUser ({ text: ' Critical: Could not find BlanksCardDataShapes. Aborting import.' , status: Status .error });
159308 this .processing = false ;
160309 return ;
161310 }
162311
163- // Log the course configuration to help with debugging
164- console .log (' [BulkImportView] Processing with course config:' , {
165- courseID: this .courseCfg .courseID ,
166- dataShapes: this .courseCfg .dataShapes ,
167- questionTypes: this .courseCfg .questionTypes ,
168- dataShapeToUse: dataShapeToUse .name ,
169- });
170-
171- // Extract course code from first dataShape in course config
172312 const configDataShape = this .courseCfg ?.dataShapes ?.[0 ];
173313 if (! configDataShape ) {
174- this .results .push ({
175- originalText: ' N/A - Configuration Error' ,
176- status: ' error' ,
177- message: ' No data shapes found in course configuration' ,
314+ alertUser ({
315+ text: ' Critical: No data shapes found in course configuration. Aborting import.' ,
316+ status: Status .error ,
178317 });
179318 this .processing = false ;
180319 return ;
181320 }
182321
183322 const codeCourse = NameSpacer .getDataShapeDescriptor (configDataShape .name ).course ;
184- console .log (` [BulkImportView] Using codeCourse: ${codeCourse } for note addition ` );
185323
186- // Prepare processor configuration
187- const config = {
324+ const processorConfig = {
188325 dataShape: dataShapeToUse ,
189326 courseCode: codeCourse ,
190327 userName: userName ,
191328 };
192329
193- // Validate processor configuration
194- const validation = validateProcessorConfig (config );
330+ const validation = validateProcessorConfig (processorConfig );
195331 if (! validation .isValid ) {
196- this .results .push ({
197- originalText: ' N/A - Configuration Error' ,
198- status: ' error' ,
199- message: validation .errorMessage || ' Invalid processor configuration' ,
332+ alertUser ({
333+ text: validation .errorMessage || ' Invalid processor configuration for import.' ,
334+ status: Status .error ,
200335 });
201336 this .processing = false ;
202337 return ;
203338 }
204339
205- // Process the cards
340+ console .log (' [BulkImportView] Starting import of parsed cards:' , {
341+ courseID: this .courseCfg .courseID ,
342+ dataShapeToUse: dataShapeToUse .name ,
343+ courseCode: codeCourse ,
344+ numberOfCards: this .parsedCards .length ,
345+ });
346+
206347 try {
207- this .results = await processBulkCards (this .bulkText , this .courseDB , config );
348+ this .results = await importParsedCards (this .parsedCards , this .courseDB , processorConfig );
208349 } catch (error ) {
209- console .error (' [BulkImportView] Error processing cards:' , error );
350+ console .error (' [BulkImportView] Error importing parsed cards:' , error );
210351 this .results .push ({
211- originalText: this . bulkText ,
352+ originalText: ' Bulk Operation Error ' ,
212353 status: ' error' ,
213- message: ` Error processing cards : ${error instanceof Error ? error .message : ' Unknown error' }` ,
354+ message: ` Critical error during import : ${error instanceof Error ? error .message : ' Unknown error' }` ,
214355 });
356+ } finally {
357+ this .processing = false ;
358+ this .importAttempted = true ; // Mark that an import attempt has been made
215359 }
216360
217- this .processing = false ;
218- if (! this .results .some ((r ) => r .status === ' error' )) {
219- // Potentially clear bulkText if all successful, or offer a button to do so
220- // this.bulkText = '';
361+ if (this .results .every ((r ) => r .status === ' success' ) && this .results .length > 0 ) {
362+ // All successful, optionally reset
363+ // this.bulkText = ''; // Clear input text
364+ // this.parsingComplete = false; // Go back to input stage
365+ // this.parsedCards = [];
366+ alertUser ({ text: ` ${this .results .length } card(s) imported successfully! ` , status: Status .success });
367+ } else if (this .results .some ((r ) => r .status === ' error' )) {
368+ alertUser ({ text: ' Some cards failed to import. Please review the results below.' , status: Status .warning });
221369 }
222370 },
223371 },
@@ -239,4 +387,16 @@ pre {
239387 border-radius : 4px ;
240388 margin-top : 5px ;
241389}
390+ .force-dark-text {
391+ color : rgba (0 , 0 , 0 , 0.87 ) !important ;
392+ }
393+ /* Ensure child elements also get dark text if not overridden */
394+ .force-dark-text .v-list-item-subtitle ,
395+ .force-dark-text .v-list-item-title ,
396+ .force-dark-text div , /* Ensure divs within the list item also get dark text */
397+ .force-dark-text summary {
398+ /* Ensure summary elements for <details> also get dark text */
399+ color : rgba (0 , 0 , 0 , 0.87 ) !important ;
400+ }
401+ /* Icons are handled by their :color prop, so no specific override needed here unless that changes. */
242402 </style >
0 commit comments