diff --git a/.agent/skills/create-docx-documents/SKILL.md b/.agent/skills/create-docx-documents/SKILL.md new file mode 100644 index 00000000..f275a3aa --- /dev/null +++ b/.agent/skills/create-docx-documents/SKILL.md @@ -0,0 +1,883 @@ +--- +description: Guide for creating and generating Word (.docx) documents using docx.js +--- + +# Skill: Create DocX Documents + +## Purpose + +This skill guides you through creating Word documents (.docx) using the `docx` library (docx.js). It covers document structure, styling, tables, headers, paragraphs, and best practices for generating professional Word documents in the browser or Node.js. + +## When to Use This Skill + +- Generating Word documents for export functionality +- Creating reports, summaries, or documentation in .docx format +- Building complex documents with tables, headers, and formatting +- Implementing document download features +- Converting web content to Word format + +## Library Overview + +**docx.js** is a powerful library for generating .docx files with JavaScript/TypeScript. It works in both browser and Node.js environments. + +**Installation:** + +```bash +npm install docx --save +``` + +**Key Features:** + +- Declarative API +- Full TypeScript support +- Works in browser and Node.js +- Supports paragraphs, tables, images, headers, footers +- Rich formatting options +- No external dependencies + +## Core Concepts + +### Document Structure + +A Word document in docx.js consists of: + +``` +Document +└── Sections (one or more) + ├── Properties (margins, orientation, etc.) + └── Children (content) + ├── Paragraphs + ├── Tables + ├── Images + └── Other elements +``` + +### Basic Imports + +```javascript +import { + Document, + Paragraph, + TextRun, + HeadingLevel, + AlignmentType, + Table, + TableCell, + TableRow, + WidthType, + VerticalAlign, + BorderStyle, + PageOrientation, + HeightRule, + TableLayoutType, + Packer, +} from "docx"; +``` + +## Step-by-Step: Create a Simple Document + +### Step 1: Create Basic Document + +```javascript +import { Document, Paragraph, TextRun, Packer } from "docx"; +import { saveAs } from "file-saver"; + +// Create document +const doc = new Document({ + sections: [ + { + properties: {}, + children: [ + new Paragraph({ + children: [ + new TextRun({ + text: "Hello World", + bold: true, + size: 28, // Size in half-points (28 = 14pt) + }), + ], + }), + ], + }, + ], +}); + +// Generate and download +Packer.toBlob(doc).then((blob) => { + saveAs(blob, "example.docx"); +}); +``` + +### Step 2: Add Headers and Paragraphs + +```javascript +const doc = new Document({ + sections: [ + { + children: [ + // Heading 1 + new Paragraph({ + text: "Document Title", + heading: HeadingLevel.HEADING_1, + alignment: AlignmentType.CENTER, + }), + + // Heading 2 + new Paragraph({ + text: "Section Title", + heading: HeadingLevel.HEADING_2, + }), + + // Regular paragraph + new Paragraph({ + children: [ + new TextRun({ + text: "This is a regular paragraph with ", + size: 24, + }), + new TextRun({ + text: "bold text", + bold: true, + size: 24, + }), + new TextRun({ + text: " and ", + size: 24, + }), + new TextRun({ + text: "italic text", + italics: true, + size: 24, + }), + ], + }), + + // Empty paragraph for spacing + new Paragraph(""), + ], + }, + ], +}); +``` + +### Step 3: Create Tables + +```javascript +const table = new Table({ + width: { + size: 100, + type: WidthType.PERCENTAGE, + }, + rows: [ + // Header row + new TableRow({ + tableHeader: true, + children: [ + new TableCell({ + children: [new Paragraph({ text: "Name", bold: true })], + shading: { fill: "DDDDDD" }, + }), + new TableCell({ + children: [new Paragraph({ text: "Value", bold: true })], + shading: { fill: "DDDDDD" }, + }), + ], + }), + + // Data rows + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("Item 1")], + }), + new TableCell({ + children: [new Paragraph("100")], + }), + ], + }), + + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("Item 2")], + }), + new TableCell({ + children: [new Paragraph("200")], + }), + ], + }), + ], +}); + +// Add to document +const doc = new Document({ + sections: [ + { + children: [ + new Paragraph({ + text: "Data Table", + heading: HeadingLevel.HEADING_2, + }), + table, + ], + }, + ], +}); +``` + +## Using the WordDocumentBuilder Pattern + +For complex documents, use a builder pattern (as seen in `wordDocumentBuilder.js`): + +### Step 1: Create Builder Class + +```javascript +export class WordDocumentBuilder { + constructor(options = {}) { + this.sections = []; + this.currentSection = null; + this.options = { + defaultFontSize: 24, // 12pt + defaultFont: "Times New Roman", + margins: { + top: 720, // 0.5 inch + right: 720, + bottom: 720, + left: 720, + }, + ...options, + }; + } + + startSection(config = {}) { + this.currentSection = { + properties: { + margins: config.margins || this.options.margins, + page: config.page || {}, + }, + children: [], + }; + + if (config.orientation === "landscape") { + this.currentSection.properties.page = { + orientation: PageOrientation.LANDSCAPE, + }; + } + + return this; + } + + endSection() { + if (this.currentSection) { + this.sections.push(this.currentSection); + this.currentSection = null; + } + return this; + } + + addHeader(text, level = 1, options = {}) { + const headingLevels = { + 1: HeadingLevel.HEADING_1, + 2: HeadingLevel.HEADING_2, + 3: HeadingLevel.HEADING_3, + }; + + const paragraph = new Paragraph({ + heading: headingLevels[level] || HeadingLevel.HEADING_2, + alignment: options.alignment || AlignmentType.LEFT, + children: [ + new TextRun({ + text: text, + bold: options.bold !== false, + size: options.size || 32, + font: { name: options.font || this.options.defaultFont }, + }), + ], + }); + + this.addToCurrentSection(paragraph); + return this; + } + + addParagraph(text, options = {}) { + const paragraph = new Paragraph({ + alignment: options.alignment || AlignmentType.LEFT, + children: [ + new TextRun({ + text: text, + bold: options.bold || false, + size: options.size || this.options.defaultFontSize, + font: { name: options.font || this.options.defaultFont }, + }), + ], + }); + + this.addToCurrentSection(paragraph); + return this; + } + + addSpacing() { + this.addToCurrentSection(new Paragraph("")); + return this; + } + + addTable(rows, headers, options = {}) { + const tableRows = [ + this.createTableHeaderRow(headers), + ...this.createTableDataRows(rows), + ]; + + const table = new Table({ + width: { size: 100, type: WidthType.PERCENTAGE }, + rows: tableRows, + }); + + this.addToCurrentSection(table); + return this; + } + + createTableHeaderRow(headers) { + return new TableRow({ + tableHeader: true, + children: headers.map( + (header) => + new TableCell({ + shading: { fill: "DDDDDD" }, + children: [ + new Paragraph({ + alignment: AlignmentType.CENTER, + children: [ + new TextRun({ + text: header.text || header, + bold: true, + size: 22, + }), + ], + }), + ], + }) + ), + }); + } + + createTableDataRows(rows) { + return rows.map( + (rowData) => + new TableRow({ + children: rowData.map( + (cellText) => + new TableCell({ + children: [ + new Paragraph({ + children: [ + new TextRun({ + text: String(cellText), + size: 22, + }), + ], + }), + ], + }) + ), + }) + ); + } + + addToCurrentSection(content) { + if (!this.currentSection) { + this.startSection(); + } + this.currentSection.children.push(content); + } + + build() { + // Finalize current section + if (this.currentSection) { + this.endSection(); + } + + return new Document({ + sections: this.sections.map((section) => ({ + properties: section.properties, + children: section.children, + })), + }); + } + + reset() { + this.sections = []; + this.currentSection = null; + return this; + } +} +``` + +### Step 2: Use Builder Pattern + +```javascript +import { WordDocumentBuilder } from "@/utils/wordDocumentBuilder"; +import { Packer } from "docx"; +import { saveAs } from "file-saver"; + +// Create document using builder +const builder = new WordDocumentBuilder(); + +builder + .startSection() + .addHeader("Project Report", 1) + .addSpacing() + .addParagraph("This is the project description.") + .addSpacing() + .addHeader("Data Summary", 2) + .addTable( + [ + ["Finding 1", "High confidence", "10"], + ["Finding 2", "Moderate confidence", "8"], + ["Finding 3", "Low confidence", "5"], + ], + [{ text: "Finding" }, { text: "Assessment" }, { text: "References" }] + ) + .endSection(); + +const doc = builder.build(); + +// Generate and download +Packer.toBlob(doc).then((blob) => { + saveAs(blob, "report.docx"); +}); +``` + +## Common Patterns + +### Pattern 1: Multi-Section Document + +```javascript +const doc = new Document({ + sections: [ + // Portrait section + { + properties: { + page: { + orientation: PageOrientation.PORTRAIT, + }, + }, + children: [ + new Paragraph({ + text: "Portrait Section", + heading: HeadingLevel.HEADING_1, + }), + ], + }, + + // Landscape section + { + properties: { + page: { + orientation: PageOrientation.LANDSCAPE, + }, + }, + children: [ + new Paragraph({ + text: "Landscape Section", + heading: HeadingLevel.HEADING_1, + }), + ], + }, + ], +}); +``` + +### Pattern 2: Complex Table with Styling + +```javascript +const table = new Table({ + width: { + size: 100, + type: WidthType.PERCENTAGE, + }, + layout: TableLayoutType.FIXED, + borders: { + top: { size: 1, color: "000000", style: BorderStyle.SINGLE }, + bottom: { size: 1, color: "000000", style: BorderStyle.SINGLE }, + left: { size: 1, color: "000000", style: BorderStyle.SINGLE }, + right: { size: 1, color: "000000", style: BorderStyle.SINGLE }, + insideHorizontal: { size: 1, color: "000000", style: BorderStyle.SINGLE }, + insideVertical: { style: BorderStyle.NONE }, + }, + rows: [ + new TableRow({ + height: { height: 500, rule: HeightRule.EXACT }, + children: [ + new TableCell({ + width: { size: 30, type: WidthType.PERCENTAGE }, + verticalAlign: VerticalAlign.CENTER, + shading: { fill: "EEEEEE" }, + children: [ + new Paragraph({ + alignment: AlignmentType.CENTER, + children: [ + new TextRun({ + text: "Header 1", + bold: true, + size: 24, + }), + ], + }), + ], + }), + new TableCell({ + width: { size: 70, type: WidthType.PERCENTAGE }, + children: [new Paragraph("Content")], + }), + ], + }), + ], +}); +``` + +### Pattern 3: Text Formatting + +```javascript +new Paragraph({ + children: [ + new TextRun({ + text: "Normal text ", + size: 24, + }), + new TextRun({ + text: "bold text ", + bold: true, + size: 24, + }), + new TextRun({ + text: "italic text ", + italics: true, + size: 24, + }), + new TextRun({ + text: "underlined text ", + underline: {}, + size: 24, + }), + new TextRun({ + text: "colored text", + color: "FF0000", // Red + size: 24, + }), + ], +}); +``` + +### Pattern 4: Export in Vue Component + +```vue + + + +``` + +### Pattern 5: Text Sanitization + +Always sanitize user input before adding to document: + +```javascript +import { + sanitizeText, + sanitizeWithLimit, + safeText, +} from "@/utils/textSanitizer"; + +// Safe text handling +const sanitizedText = sanitizeWithLimit( + safeText(userInput), + 5000, // Max length + { + preserveNewlines: true, + preserveEmojis: false, + } +); + +builder.addParagraph(sanitizedText); +``` + +## Size Reference + +Font sizes in docx.js are in **half-points**: + +| Points | Half-Points | Usage | +| ------ | ----------- | ------------------- | +| 10pt | 20 | Small text | +| 11pt | 22 | Table cells | +| 12pt | 24 | Body text (default) | +| 14pt | 28 | Heading 3 | +| 16pt | 32 | Heading 2 | +| 18pt | 36 | Heading 1 | +| 20pt | 40 | Large headings | + +Margins are in **twips** (1/20 of a point): + +| Inches | Twips | +| ------ | ----- | +| 0.5" | 720 | +| 0.75" | 1080 | +| 1.0" | 1440 | + +## Generating the Document + +### Browser (with file-saver) + +```javascript +import { Packer } from "docx"; +import { saveAs } from "file-saver"; + +// Generate blob and download +Packer.toBlob(doc).then((blob) => { + saveAs(blob, "document.docx"); +}); +``` + +### Node.js + +```javascript +import { Packer } from "docx"; +import * as fs from "fs"; + +// Generate buffer and save +Packer.toBuffer(doc).then((buffer) => { + fs.writeFileSync("document.docx", buffer); +}); +``` + +### As Base64 + +```javascript +import { Packer } from "docx"; + +// Generate base64 string +Packer.toBase64String(doc).then((base64) => { + // Send to server or use as data URL + console.log(base64); +}); +``` + +## Best Practices + +### 1. Use Builder Pattern for Complex Documents + +```javascript +// ✅ Good - Readable and maintainable +const builder = new WordDocumentBuilder(); +builder + .startSection() + .addHeader("Title", 1) + .addParagraph("Content") + .endSection(); + +// ❌ Bad - Hard to read +const doc = new Document({ + sections: [ + { + children: [ + new Paragraph({ text: "Title", heading: HeadingLevel.HEADING_1 }), + new Paragraph({ text: "Content" }), + ], + }, + ], +}); +``` + +### 2. Always Sanitize User Input + +```javascript +// ✅ Good - Sanitized +builder.addParagraph(sanitizeText(userInput)); + +// ❌ Bad - Unsanitized +builder.addParagraph(userInput); +``` + +### 3. Handle Errors Gracefully + +```javascript +// ✅ Good - Error handling +try { + const doc = builder.build(); + const blob = await Packer.toBlob(doc); + saveAs(blob, "document.docx"); +} catch (error) { + console.error("Document generation failed:", error); + this.$bvToast.toast("Failed to generate document", { + variant: "danger", + }); +} +``` + +### 4. Use Consistent Styling + +```javascript +// ✅ Good - Consistent options +const builder = new WordDocumentBuilder({ + defaultFontSize: 24, + defaultFont: "Times New Roman", +}); + +// All paragraphs will use these defaults +builder.addParagraph("Text 1"); +builder.addParagraph("Text 2"); +``` + +### 5. Optimize for Large Documents + +```javascript +// ✅ Good - Process in chunks +const chunkSize = 100; +for (let i = 0; i < data.length; i += chunkSize) { + const chunk = data.slice(i, i + chunkSize); + chunk.forEach((item) => { + builder.addParagraph(item.text); + }); +} + +// ❌ Bad - All at once (may freeze browser) +data.forEach((item) => { + builder.addParagraph(item.text); +}); +``` + +## Common Pitfalls + +1. **Font Size Confusion**: Remember sizes are in half-points (24 = 12pt) +2. **Missing Sections**: Always wrap content in sections +3. **Table Width**: Use `WidthType.PERCENTAGE` for responsive tables +4. **Empty Paragraphs**: Use `new Paragraph('')` for spacing, not `\n` +5. **Text Sanitization**: Always sanitize user input to avoid XML errors +6. **Memory Issues**: For large documents, generate in chunks +7. **Async Operations**: Remember `Packer.toBlob()` returns a Promise + +## Testing Checklist + +- [ ] Document generates without errors +- [ ] File downloads correctly +- [ ] Content displays properly in Microsoft Word +- [ ] Content displays properly in Google Docs +- [ ] Content displays properly in LibreOffice +- [ ] Tables render correctly +- [ ] Formatting is preserved +- [ ] Special characters are handled +- [ ] Large documents don't freeze browser +- [ ] Error handling works correctly + +## Related Files + +- `src/utils/wordDocumentBuilder.js` - Builder class implementation +- `src/utils/textSanitizer.js` - Text sanitization utilities +- `src/services/wordExportService.js` - Export service +- `src/strategies/exportStrategies.js` - Export strategies + +## See Also + +- Official Documentation: https://docx.js.org/ +- API Reference: https://docx.js.org/api/ +- GitHub Repository: https://github.com/dolanmiu/docx +- Examples: https://github.com/dolanmiu/docx/tree/master/demo +- Playground: https://docxjs-editor.vercel.app/ + +## Quick Reference + +### Common Imports + +```javascript +import { + Document, + Paragraph, + TextRun, + HeadingLevel, + AlignmentType, + Table, + TableCell, + TableRow, + WidthType, + VerticalAlign, + BorderStyle, + PageOrientation, + Packer, +} from "docx"; +``` + +### Minimal Example + +```javascript +import { Document, Paragraph, Packer } from "docx"; +import { saveAs } from "file-saver"; + +const doc = new Document({ + sections: [ + { + children: [new Paragraph("Hello World")], + }, + ], +}); + +Packer.toBlob(doc).then((blob) => { + saveAs(blob, "example.docx"); +}); +``` diff --git a/.agent/skills/extend-evidence-profile/SKILL.md b/.agent/skills/extend-evidence-profile/SKILL.md new file mode 100644 index 00000000..cb6fa553 --- /dev/null +++ b/.agent/skills/extend-evidence-profile/SKILL.md @@ -0,0 +1,357 @@ +--- +description: Guide for extending the Evidence Profile with new GRADE-CERQual components +--- + +# Skill: Extend Evidence Profile + +## Purpose + +This skill guides you through adding new components to the Evidence Profile (GRADE-CERQual assessment), modifying existing components, or changing the assessment options. + +## When to Use This Skill + +- GRADE-CERQual methodology adds new assessment criteria +- Project requires custom assessment components +- Need to modify existing component options or labels +- Changing the scoring system (e.g., adding more granular options) + +## Current Evidence Profile Structure + +The Evidence Profile consists of 5 components: + +1. **Methodological Limitations** (4 options: 0-3) +2. **Coherence** (4 options: 0-3) +3. **Adequacy** (4 options: 0-3) +4. **Relevance** (4 options: 0-3) +5. **CERQual Assessment** (4 options: 0-3) + +Each component has: + +- `option`: Number (0-3) representing the selected level +- `explanation`: String with detailed justification +- `notes`: Optional additional notes + +## Critical Files to Modify + +### Frontend Components + +1. **`src/components/list/evidenceProfileForm.vue`** + + - Lines 27-386: Component-specific forms (left panel) + - Lines 388-714: Component-specific data displays (right panel) + - Purpose: Modal form for editing Evidence Profile + +2. **`src/components/list/editList.vue`** + + - Purpose: Worksheet view that displays Evidence Profile + - What to update: Display logic for new components + +3. **`src/components/project/viewProject.vue`** + - Location: `createFinding()` method (lines 1410-1455) + - Purpose: Initialize Evidence Profile structure when creating finding + - What to update: Add new component to initial structure + +### Backend + +4. **`isoq_server/auth_server/controllers/core.py`** + - Location: `checkIfAListHasCerqual()` (lines 871-881) + - Purpose: Validate Evidence Profile completeness + - What to update: Add validation for new component if required + +### Translation Files + +5. **`src/lang/en.json`**, **`src/lang/es.json`**, **`src/lang/pt.json`** + - Sections to update: + - `worksheet.questions.*` - Question text + - `worksheet.options.*` - Option labels + - `worksheet.tooltips.*` - Tooltip explanations + - `worksheet.domains.*` - Domain names + +## Step-by-Step Procedure + +### Adding a New Evidence Profile Component + +**Example: Adding a "Transferability" component** + +#### Step 1: Add to Initial Structure + +```javascript +// src/components/project/viewProject.vue - createFinding() method +evidence_profile: { + name: listName, + isoqf_id: this.lastId, + relevance: { explanation: '', option: null }, + adequacy: { explanation: '', option: null }, + coherence: { explanation: '', option: null }, + methodological_limitations: { explanation: '', option: null }, + cerqual: { explanation: '', option: null }, + transferability: { explanation: '', option: null } // NEW +} +``` + +#### Step 2: Add Form Section + +```vue + + +
+

+ {{ $t('worksheet.questions.transferability') }} +

+

+ {{ $t('worksheet.reminders.transferability') }} +

+ + + + {{ $t('worksheet.options.no_concerns') }} + * + + + {{ $t('worksheet.options.minor_concerns') }} + * + + + {{ $t('worksheet.options.moderate_concerns') }} + * + + + {{ $t('worksheet.options.serious_concerns') }} + * + + + +

+ + + {{ $t('worksheet.actions.clear_selection') }} + +

+ + + + + + + + + + + + + +
+``` + +#### Step 3: Add Right Panel Display + +```vue + + +
+ + + +
+``` + +#### Step 4: Update CERQual Summary Display + +```vue + + + + + +
{{ $t('worksheet.domains.transferability') }}
+

+ {{ displaySelectedOption(evidenceProfile[0].transferability.option) }} + +

+
+``` + +#### Step 5: Add Component Data Property + +```javascript +// src/components/list/evidenceProfileForm.vue - data() section +selectedOptions: { + type: null, + title: null, + methodological_limitations: { option: null, explanation: '', notes: '' }, + coherence: { option: null, explanation: '', notes: '' }, + adequacy: { option: null, explanation: '', notes: '' }, + relevance: { option: null, explanation: '', notes: '' }, + cerqual: { option: null, explanation: '', notes: '' }, + transferability: { option: null, explanation: '', notes: '' } // NEW +} +``` + +#### Step 6: Add Translations + +```json +// src/lang/en.json +{ + "worksheet": { + "questions": { + "transferability": "How transferable are the findings to other contexts?" + }, + "reminders": { + "transferability": "Consider the extent to which findings can be applied to other settings or populations." + }, + "domains": { + "transferability": "Transferability" + }, + "tooltips": { + "transferability": { + "no_concerns": "Findings are highly transferable to other contexts", + "minor_concerns": "Findings are mostly transferable with minor limitations", + "moderate_concerns": "Findings have moderate transferability concerns", + "serious_concerns": "Findings have serious transferability limitations" + } + } + } +} +``` + +Repeat for `es.json` and `pt.json`. + +#### Step 7: Update Backend Validation (Optional) + +If the new component should be required for publication: + +```python +# isoq_server/auth_server/controllers/core.py +def checkIfAListHasCerqual(project): + cnt = 0 + lists = api.get('isoqf_lists?project_id=%s' % project) + for list in lists: + # Check if both cerqual AND transferability are complete + if (list.get('cerqual').get('explanation') != '' and + list.get('transferability').get('explanation') != ''): + cnt += 1 + + if (cnt): + return json_cors_response({'status': True, 'message': 'Can Publish.'}) + else: + return json_cors_response({ + 'status': False, + 'message': 'The project must have complete GRADE-CERQual assessment including transferability.' + }) +``` + +### Modifying Existing Component Options + +**Example: Adding a 5th option "Very Serious Concerns" to Methodological Limitations** + +#### Step 1: Update Form Options + +```vue + + + {{ $t('worksheet.options.very_serious_concerns') }} + * + +``` + +#### Step 2: Add Translation + +```json +{ + "worksheet": { + "options": { + "very_serious_concerns": "Very serious concerns" + }, + "tooltips": { + "methodological_limitations": { + "very_serious_concerns": "Critical methodological flaws that severely undermine confidence" + } + } + } +} +``` + +#### Step 3: Update Display Logic + +Ensure all display methods handle the new option value (4). + +## Validation Checklist + +After adding/modifying components: + +- [ ] Initial structure updated in `createFinding()` +- [ ] Form section added in `evidenceProfileForm.vue` (left panel) +- [ ] Display section added in `evidenceProfileForm.vue` (right panel) +- [ ] CERQual summary updated to show new component +- [ ] Data properties added to `selectedOptions` +- [ ] Translations added in all 3 languages +- [ ] Backend validation updated (if required for publication) +- [ ] Tested creating a new finding → component appears +- [ ] Tested filling out the component → saves correctly +- [ ] Tested CERQual summary → shows new component +- [ ] Tested in all three languages +- [ ] No console errors + +## Common Pitfalls + +1. **Forgetting Initial Structure**: Must add to `createFinding()` or existing findings won't have the field +2. **Missing in CERQual Summary**: New components must be displayed in the CERQual assessment tab +3. **Data Property**: Must add to `selectedOptions` in data() +4. **Translation Keys**: Need translations for questions, options, tooltips, and domains +5. **Backend Compatibility**: Ensure backend can handle the new field structure + +## Related Files + +- `src/components/list/evidenceProfileForm.vue` - Main Evidence Profile form +- `src/components/list/editList.vue` - Worksheet view +- `src/components/project/viewProject.vue` - Finding creation +- `isoq_server/auth_server/controllers/core.py` - Backend validation +- `src/lang/*.json` - Translation files + +## See Also + +- CLAUDE.md section "Project Creation & Publication Flow" → "Worksheet: Completing the Evidence Profile" +- GRADE-CERQual official documentation: https://www.cerqual.org/ diff --git a/.agent/skills/global-instructions.md b/.agent/skills/global-instructions.md new file mode 100644 index 00000000..8ce8edc6 --- /dev/null +++ b/.agent/skills/global-instructions.md @@ -0,0 +1,4 @@ +# Regla Maestra + +- Antes de cada tarea, lee OBLIGATORIAMENTE el archivo @CLAUDE.md +- si creamos algo nuevo que no se encuentra en @CLAUDE.md, actualiza ese archivo con la nueva información diff --git a/.agent/skills/implement-export-integration/SKILL.md b/.agent/skills/implement-export-integration/SKILL.md new file mode 100644 index 00000000..15d833e7 --- /dev/null +++ b/.agent/skills/implement-export-integration/SKILL.md @@ -0,0 +1,737 @@ +--- +description: Guide for implementing export integrations to Google Sheets, Excel, and PowerPoint +--- + +# Skill: Implement Export Integration + +## Purpose + +This skill guides you through implementing export functionality to external formats like Google Sheets, Excel (xlsx), and PowerPoint (pptx) from the iSoQ frontend application. + +## When to Use This Skill + +- Adding Google Sheets export functionality +- Implementing Excel file generation +- Creating PowerPoint presentations from iSoQ data +- Adding new export formats to existing export features + +## Export Architecture + +### Frontend-Based Exports + +All exports are implemented in the **frontend** because: + +- No sensitive API keys needed (OAuth handles authentication) +- Direct browser-to-service communication +- No backend processing required +- Better user experience (immediate feedback) + +### Export Flow + +``` +User clicks "Export" → Frontend formats data → +Authentication (if needed) → Generate/Upload file → +Show success/download link +``` + +## Implementation Options + +### Option 1: Google Sheets Export (Recommended for Collaboration) + +**Libraries:** + +- `gapi-script` - Google API client +- Or direct `fetch()` calls to Google Sheets API + +**Pros:** + +- Real-time collaboration +- Automatic cloud storage +- Easy sharing +- No file size limits + +**Cons:** + +- Requires Google account +- OAuth authentication needed +- Internet connection required + +### Option 2: Excel Export (Recommended for Offline) + +**Libraries:** + +- `xlsx` (SheetJS) - Most popular, free +- `exceljs` - More features, larger bundle + +**Pros:** + +- Works offline +- No authentication needed +- Universal format +- Immediate download + +**Cons:** + +- File size limits +- No collaboration features +- Manual sharing required + +### Option 3: PowerPoint Export + +**Libraries:** + +- `pptxgenjs` - Generate PowerPoint presentations + +**Pros:** + +- Professional presentations +- Customizable layouts +- Works offline + +**Cons:** + +- More complex formatting +- Larger file sizes +- Limited interactivity + +## Step-by-Step: Google Sheets Export + +### Step 1: Install Dependencies + +```bash +npm install gapi-script --save +``` + +### Step 2: Set Up Google OAuth + +#### 2.1: Create Google Cloud Project + +1. Go to https://console.cloud.google.com/ +2. Create new project or select existing +3. Enable Google Sheets API +4. Create OAuth 2.0 credentials (Web application) +5. Add authorized JavaScript origins: `http://episte.lo:8090` +6. Add authorized redirect URIs: `http://episte.lo:8090` +7. Copy Client ID + +#### 2.2: Add Client ID to Environment + +```javascript +// config/dev.env.js +module.exports = merge(prodEnv, { + NODE_ENV: '"development"', + API_URL: '"http://localhost:8080"', + GOOGLE_CLIENT_ID: '"your-client-id-here.apps.googleusercontent.com"', +}); +``` + +### Step 3: Create Google Sheets Service + +```javascript +// src/services/googleSheetsService.js +import { gapi } from "gapi-script"; + +const CLIENT_ID = process.env.GOOGLE_CLIENT_ID; +const API_KEY = process.env.GOOGLE_API_KEY; // Optional +const SCOPES = "https://www.googleapis.com/auth/spreadsheets"; + +class GoogleSheetsService { + constructor() { + this.isInitialized = false; + this.isSignedIn = false; + } + + async init() { + if (this.isInitialized) return; + + return new Promise((resolve, reject) => { + gapi.load("client:auth2", async () => { + try { + await gapi.client.init({ + clientId: CLIENT_ID, + scope: SCOPES, + discoveryDocs: [ + "https://sheets.googleapis.com/$discovery/rest?version=v4", + ], + }); + + this.isInitialized = true; + this.isSignedIn = gapi.auth2.getAuthInstance().isSignedIn.get(); + resolve(); + } catch (error) { + reject(error); + } + }); + }); + } + + async signIn() { + await this.init(); + if (!this.isSignedIn) { + await gapi.auth2.getAuthInstance().signIn(); + this.isSignedIn = true; + } + } + + async signOut() { + if (this.isSignedIn) { + await gapi.auth2.getAuthInstance().signOut(); + this.isSignedIn = false; + } + } + + async createSpreadsheet(title, data) { + await this.signIn(); + + // Create spreadsheet + const response = await gapi.client.sheets.spreadsheets.create({ + properties: { + title: title, + }, + }); + + const spreadsheetId = response.result.spreadsheetId; + + // Add data + if (data && data.length > 0) { + await gapi.client.sheets.spreadsheets.values.update({ + spreadsheetId: spreadsheetId, + range: "Sheet1!A1", + valueInputOption: "RAW", + resource: { + values: data, + }, + }); + } + + return { + spreadsheetId, + url: `https://docs.google.com/spreadsheets/d/${spreadsheetId}`, + }; + } + + async exportToExistingSheet(spreadsheetId, sheetName, data) { + await this.signIn(); + + await gapi.client.sheets.spreadsheets.values.update({ + spreadsheetId: spreadsheetId, + range: `${sheetName}!A1`, + valueInputOption: "RAW", + resource: { + values: data, + }, + }); + + return { + spreadsheetId, + url: `https://docs.google.com/spreadsheets/d/${spreadsheetId}`, + }; + } +} + +export default new GoogleSheetsService(); +``` + +### Step 4: Format iSoQ Data for Export + +```javascript +// src/utils/exportFormatters.js + +export function formatProjectForExport(project, lists, references) { + const data = []; + + // Header row + data.push([ + "Project Title", + "Authors", + "Review Question", + "Finding", + "CERQual Assessment", + "References", + ]); + + // Data rows + lists.forEach((list) => { + const refs = list.references + .map((refId) => { + const ref = references.find((r) => r.id === refId); + return ref ? `${ref.authors} (${ref.year})` : ""; + }) + .join("; "); + + data.push([ + project.name, + project.authors, + project.review_question, + list.name, + getCerqualLabel(list.cerqual.option), + refs, + ]); + }); + + return data; +} + +export function formatEvidenceProfileForExport(list, evidenceProfile) { + const data = []; + + // Title + data.push([`Evidence Profile: ${list.name}`]); + data.push([]); // Empty row + + // Components + const components = [ + "Methodological Limitations", + "Coherence", + "Adequacy", + "Relevance", + "CERQual Assessment", + ]; + + const fields = [ + "methodological_limitations", + "coherence", + "adequacy", + "relevance", + "cerqual", + ]; + + data.push(["Component", "Assessment", "Explanation"]); + + fields.forEach((field, index) => { + const component = evidenceProfile[field]; + data.push([ + components[index], + getConcernLabel(component.option), + component.explanation || "", + ]); + }); + + return data; +} + +function getCerqualLabel(option) { + const labels = { + 0: "High confidence", + 1: "Moderate confidence", + 2: "Low confidence", + 3: "Very low confidence", + }; + return labels[option] || "Not assessed"; +} + +function getConcernLabel(option) { + const labels = { + 0: "No concerns", + 1: "Minor concerns", + 2: "Moderate concerns", + 3: "Serious concerns", + }; + return labels[option] || "Not assessed"; +} +``` + +### Step 5: Add Export Button to Component + +```vue + + + + +``` + +## Step-by-Step: Excel Export + +### Step 1: Install Dependencies + +```bash +npm install xlsx --save +``` + +### Step 2: Create Excel Export Service + +```javascript +// src/services/excelExportService.js +import * as XLSX from "xlsx"; + +class ExcelExportService { + exportToExcel(data, filename) { + // Create workbook + const wb = XLSX.utils.book_new(); + + // Create worksheet from data + const ws = XLSX.utils.aoa_to_sheet(data); + + // Add worksheet to workbook + XLSX.utils.book_append_sheet(wb, ws, "Sheet1"); + + // Generate Excel file and trigger download + XLSX.writeFile(wb, `${filename}.xlsx`); + } + + exportMultipleSheets(sheets, filename) { + // sheets = [{ name: 'Sheet1', data: [[...]] }, ...] + const wb = XLSX.utils.book_new(); + + sheets.forEach((sheet) => { + const ws = XLSX.utils.aoa_to_sheet(sheet.data); + XLSX.utils.book_append_sheet(wb, ws, sheet.name); + }); + + XLSX.writeFile(wb, `${filename}.xlsx`); + } + + exportWithFormatting(data, filename) { + const wb = XLSX.utils.book_new(); + const ws = XLSX.utils.aoa_to_sheet(data); + + // Set column widths + ws["!cols"] = [ + { wch: 30 }, // Column A + { wch: 20 }, // Column B + { wch: 50 }, // Column C + { wch: 20 }, // Column D + { wch: 15 }, // Column E + { wch: 40 }, // Column F + ]; + + // Bold header row + const range = XLSX.utils.decode_range(ws["!ref"]); + for (let C = range.s.c; C <= range.e.c; ++C) { + const address = XLSX.utils.encode_col(C) + "1"; + if (!ws[address]) continue; + ws[address].s = { + font: { bold: true }, + fill: { fgColor: { rgb: "CCCCCC" } }, + }; + } + + XLSX.utils.book_append_sheet(wb, ws, "Export"); + XLSX.writeFile(wb, `${filename}.xlsx`); + } +} + +export default new ExcelExportService(); +``` + +### Step 3: Add Excel Export Button + +```vue + + + Export to Excel + +``` + +```javascript +import ExcelExportService from '@/services/excelExportService' +import { formatProjectForExport } from '@/utils/exportFormatters' + +methods: { + exportToExcel() { + const data = formatProjectForExport( + this.project, + this.lists, + this.references + ) + + const filename = `iSoQ_${this.project.name}_${Date.now()}` + ExcelExportService.exportToExcel(data, filename) + + this.$bvToast.toast('Excel file downloaded', { + title: 'Export Complete', + variant: 'success' + }) + } +} +``` + +## Step-by-Step: PowerPoint Export + +### Step 1: Install Dependencies + +```bash +npm install pptxgenjs --save +``` + +### Step 2: Create PowerPoint Export Service + +```javascript +// src/services/powerpointExportService.js +import pptxgen from "pptxgenjs"; + +class PowerPointExportService { + exportToPowerPoint(project, lists, evidenceProfiles) { + const pptx = new pptxgen(); + + // Title slide + const titleSlide = pptx.addSlide(); + titleSlide.addText(project.name, { + x: 0.5, + y: 1.5, + w: 9, + h: 1, + fontSize: 32, + bold: true, + align: "center", + }); + titleSlide.addText(project.authors, { + x: 0.5, + y: 3, + w: 9, + h: 0.5, + fontSize: 18, + align: "center", + }); + + // Review question slide + const questionSlide = pptx.addSlide(); + questionSlide.addText("Review Question", { + x: 0.5, + y: 0.5, + w: 9, + h: 0.5, + fontSize: 24, + bold: true, + }); + questionSlide.addText(project.review_question, { + x: 0.5, + y: 1.5, + w: 9, + h: 2, + fontSize: 16, + }); + + // Findings slides + lists.forEach((list, index) => { + const slide = pptx.addSlide(); + + // Finding title + slide.addText(`Finding ${index + 1}: ${list.name}`, { + x: 0.5, + y: 0.5, + w: 9, + h: 0.5, + fontSize: 20, + bold: true, + }); + + // Evidence Profile table + const ep = evidenceProfiles[index]; + const tableData = [ + ["Component", "Assessment"], + [ + "Methodological Limitations", + this.getConcernLabel(ep.methodological_limitations.option), + ], + ["Coherence", this.getConcernLabel(ep.coherence.option)], + ["Adequacy", this.getConcernLabel(ep.adequacy.option)], + ["Relevance", this.getConcernLabel(ep.relevance.option)], + ["CERQual", this.getCerqualLabel(ep.cerqual.option)], + ]; + + slide.addTable(tableData, { + x: 0.5, + y: 1.5, + w: 9, + h: 3, + fontSize: 14, + border: { pt: 1, color: "000000" }, + fill: { color: "F7F7F7" }, + }); + }); + + // Save file + pptx.writeFile({ fileName: `iSoQ_${project.name}.pptx` }); + } + + getConcernLabel(option) { + const labels = { + 0: "No concerns", + 1: "Minor concerns", + 2: "Moderate concerns", + 3: "Serious concerns", + }; + return labels[option] || "Not assessed"; + } + + getCerqualLabel(option) { + const labels = { + 0: "High confidence", + 1: "Moderate confidence", + 2: "Low confidence", + 3: "Very low confidence", + }; + return labels[option] || "Not assessed"; + } +} + +export default new PowerPointExportService(); +``` + +## Common Patterns + +### Pattern 1: Export with Progress Indicator + +```javascript +async exportWithProgress() { + this.exportProgress = 0 + this.exporting = true + + try { + // Step 1: Authenticate (25%) + await GoogleSheetsService.signIn() + this.exportProgress = 25 + + // Step 2: Format data (50%) + const data = formatProjectForExport(...) + this.exportProgress = 50 + + // Step 3: Create spreadsheet (75%) + const result = await GoogleSheetsService.createSpreadsheet(...) + this.exportProgress = 75 + + // Step 4: Complete (100%) + this.exportProgress = 100 + window.open(result.url, '_blank') + + } finally { + this.exporting = false + this.exportProgress = 0 + } +} +``` + +### Pattern 2: Export Multiple Formats + +```javascript +async exportMultipleFormats() { + const data = formatProjectForExport(...) + + // Parallel exports + await Promise.all([ + ExcelExportService.exportToExcel(data, 'export'), + GoogleSheetsService.createSpreadsheet('Export', data) + ]) +} +``` + +### Pattern 3: Scheduled/Batch Export + +```javascript +async batchExportProjects(projectIds) { + for (const projectId of projectIds) { + const project = await this.loadProject(projectId) + const data = formatProjectForExport(project, ...) + await ExcelExportService.exportToExcel(data, project.name) + + // Delay to avoid rate limits + await new Promise(resolve => setTimeout(resolve, 1000)) + } +} +``` + +## Testing Checklist + +- [ ] Google OAuth flow works correctly +- [ ] User can sign in/out +- [ ] Spreadsheet created with correct data +- [ ] Excel file downloads successfully +- [ ] PowerPoint file generates correctly +- [ ] Data formatting is correct +- [ ] Special characters handled properly +- [ ] Large datasets don't crash browser +- [ ] Error messages are user-friendly +- [ ] Works in all supported browsers +- [ ] Offline mode handled gracefully (for Excel/PPT) + +## Common Pitfalls + +1. **OAuth Redirect Issues**: Ensure redirect URIs match exactly in Google Console +2. **CORS Errors**: Use official Google libraries, not direct fetch +3. **Large Datasets**: Consider pagination or chunking for >1000 rows +4. **Memory Issues**: Don't load all data at once, stream if possible +5. **Browser Compatibility**: Test xlsx library in all target browsers +6. **File Size Limits**: Excel has row limits (~1M rows), warn users + +## Related Files + +- `src/services/googleSheetsService.js` - Google Sheets integration +- `src/services/excelExportService.js` - Excel export +- `src/services/powerpointExportService.js` - PowerPoint export +- `src/utils/exportFormatters.js` - Data formatting utilities +- `src/components/project/actionButtons.vue` - Export buttons +- `config/dev.env.js` - Environment configuration + +## See Also + +- Google Sheets API: https://developers.google.com/sheets/api +- SheetJS (xlsx): https://docs.sheetjs.com/ +- PptxGenJS: https://gitbrent.github.io/PptxGenJS/ diff --git a/.agent/skills/modify-publication-requirements/SKILL.md b/.agent/skills/modify-publication-requirements/SKILL.md new file mode 100644 index 00000000..9ea46bd9 --- /dev/null +++ b/.agent/skills/modify-publication-requirements/SKILL.md @@ -0,0 +1,255 @@ +--- +description: Guide for modifying project publication requirements and validation logic +--- + +# Skill: Modify Publication Requirements + +## Purpose + +This skill guides you through modifying the publication requirements for iSoQ projects, including adding/removing required fields, changing validation logic, and updating error messages. + +## When to Use This Skill + +- Adding new required fields for project publication +- Removing or making optional existing required fields +- Changing validation rules (e.g., minimum character lengths, email formats) +- Updating validation error messages +- Modifying backend publication checks + +## Critical Files to Modify + +### Frontend Validation + +1. **`src/utils/project.js`** + + - Location: `Project.validations()` method (lines 20-103) + - Purpose: Frontend validation of all required fields + - What to update: + - Add/remove field checks in the validation logic + - Update error messages in `responses.state` + - Modify the main error message (line 91) + +2. **`src/components/organization/organizationForm.vue`** + - Location: Form fields and validation state + - Purpose: Display form fields and validation errors + - What to update: + - Add/remove form fields in template + - Add/remove validation state properties (lines 320, 339) + - Add/remove `b-form-invalid-feedback` components + +### Backend Validation + +3. **`isoq_server/auth_server/controllers/core.py`** + - Location: `checkIfCanPublish()` function (lines 829-851) + - Purpose: Backend validation before publication + - What to update: + - Add/remove validation check functions + - Update validation error messages +4. **`isoq_server/auth_server/controllers/core.py`** + - Location: `checkBasicInformation()` function (lines 904-936) + - Purpose: Validate basic project information fields + - What to update: + - Add/remove fields in `only_check` array (line 906) + - Update field validation logic + +### Translation Files + +5. **`src/lang/en.json`**, **`src/lang/es.json`**, **`src/lang/pt.json`** + - Purpose: Internationalized error messages and labels + - What to update: + - Add translation keys for new fields under `project.*` + - Add validation error messages under `project.validation.*` + - Update in all three language files + +## Step-by-Step Procedure + +### Adding a New Required Field + +**Example: Adding a required "funding_source" field** + +#### Step 1: Add Frontend Validation + +```javascript +// src/utils/project.js - Add to validations() method +// After line 81 (license check) +if ( + data.funding_source === "" || + data.funding_source === null || + data.funding_source === undefined +) { + responses.state.funding_source = false; + cnt++; +} +``` + +#### Step 2: Update Form Component + +```vue + + + + + + + {{ $t('project.validation.funding_source_required') }} + + +``` + +#### Step 3: Add Validation State + +```javascript +// src/components/organization/organizationForm.vue +// In data() - line 320 +state: { + // ... existing fields + funding_source: null; +} +``` + +#### Step 4: Add Backend Validation + +```python +# isoq_server/auth_server/controllers/core.py +# In checkBasicInformation() - line 906 +only_check = ['id', 'name', 'authors', 'author', 'author_email', + 'review_question', 'complete_by_author', 'lists_authors', + 'funding_source'] # Add here +``` + +#### Step 5: Add Translations + +```json +// src/lang/en.json +{ + "project": { + "funding_source": "Funding Source", + "validation": { + "funding_source_required": "Funding source is required for publication" + } + } +} +``` + +Repeat for `es.json` and `pt.json`. + +#### Step 6: Test + +1. Create a new project without the field → Should show validation error +2. Fill the field → Validation should pass +3. Try to publish without the field → Should be blocked +4. Test in all three languages + +### Removing a Required Field + +**Example: Making "url_doi" optional** + +#### Step 1: Remove Frontend Validation + +```javascript +// src/utils/project.js +// Comment out or remove lines 68-71 +// if (data.published_status && (data.url_doi === '' || ...)) { +// responses.state.url_doi = false +// cnt++ +// } +``` + +#### Step 2: Update Form (Optional) + +If you want to keep the field but make it optional, just remove the validation feedback. + +#### Step 3: Remove Backend Validation + +```python +# isoq_server/auth_server/controllers/core.py +# Remove 'url_doi' from only_check array if present +``` + +#### Step 4: Update Translations + +Update validation messages to indicate the field is now optional. + +### Changing Validation Rules + +**Example: Change review_question minimum length from 3 to 10 characters** + +#### Step 1: Update Frontend Validation + +```javascript +// src/utils/project.js - line 63 +if ( + data.review_question === "" || + data.review_question === null || + data.review_question === undefined || + data.review_question.trim().length < 10 +) { + responses.state.review_question = false; + cnt++; +} +``` + +#### Step 2: Update Backend Validation + +```python +# isoq_server/auth_server/controllers/core.py - line 930 +if key == 'review_question' and len(value) <= 9: # Changed from 2 to 9 + response[key] = 'txt corto' +``` + +#### Step 3: Update Error Messages + +```json +// src/lang/en.json +{ + "project": { + "validation": { + "review_question_min_length": "Review question must be at least 10 characters" + } + } +} +``` + +## Validation Checklist + +After making changes, verify: + +- [ ] Frontend validation updated in `project.js` +- [ ] Form component updated in `organizationForm.vue` +- [ ] Validation state properties added/removed +- [ ] Backend validation updated in `core.py` +- [ ] Translation keys added in all 3 language files (en, es, pt) +- [ ] Error messages are clear and helpful +- [ ] Tested creating a project without the field +- [ ] Tested publishing with/without the field +- [ ] Tested in all three languages +- [ ] No console errors or warnings +- [ ] Backend returns appropriate error messages + +## Common Pitfalls + +1. **Forgetting Backend Validation**: Always update both frontend AND backend +2. **Missing Translations**: Must update all 3 language files +3. **Validation State**: Don't forget to add state properties in organizationForm.vue +4. **Conditional Fields**: Remember fields like `lists_authors` that depend on other fields +5. **Error Messages**: Make sure error messages are user-friendly and actionable + +## Related Files + +- `src/utils/project.js` - Frontend validation logic +- `src/components/organization/organizationForm.vue` - Project properties form +- `src/components/project/propertiesProject.vue` - Wrapper component +- `isoq_server/auth_server/controllers/core.py` - Backend validation +- `src/lang/*.json` - Translation files + +## See Also + +- CLAUDE.md section "Project Creation & Publication Flow" → "Required Fields for Publication" +- CLAUDE.md section "Project Creation & Publication Flow" → "Publication Requirements" diff --git a/.agent/skills/modify-publication-requirements/resources/modification-checklist.md b/.agent/skills/modify-publication-requirements/resources/modification-checklist.md new file mode 100644 index 00000000..398bb840 --- /dev/null +++ b/.agent/skills/modify-publication-requirements/resources/modification-checklist.md @@ -0,0 +1,82 @@ +# Modification Checklist + +Use this checklist when modifying publication requirements to ensure nothing is missed. + +## Adding a New Required Field + +- [ ] **Frontend Validation** (`src/utils/project.js`) + + - [ ] Add field check in `Project.validations()` method + - [ ] Add field to `responses.state` object + - [ ] Increment `cnt` if validation fails + +- [ ] **Form Component** (`src/components/organization/organizationForm.vue`) + + - [ ] Add form field in template + - [ ] Add validation state property in `data()` + - [ ] Add `b-form-invalid-feedback` component + - [ ] Bind field to `formData` with `v-model` + +- [ ] **Backend Validation** (`isoq_server/auth_server/controllers/core.py`) + + - [ ] Add field to `only_check` array in `checkBasicInformation()` + - [ ] Add field validation logic if needed + +- [ ] **Translations** (all 3 files) + + - [ ] Add label in `project.*` section (`en.json`, `es.json`, `pt.json`) + - [ ] Add validation message in `project.validation.*` + - [ ] Add description if needed + +- [ ] **Testing** + - [ ] Test creating project without field → shows error + - [ ] Test filling field → validation passes + - [ ] Test publishing without field → blocked + - [ ] Test in all 3 languages (EN, ES, PT) + - [ ] Check browser console for errors + +## Removing a Required Field + +- [ ] **Frontend Validation** (`src/utils/project.js`) + + - [ ] Remove or comment out field check + - [ ] Remove from `responses.state` + +- [ ] **Form Component** (`src/components/organization/organizationForm.vue`) + + - [ ] Remove validation feedback (or make optional) + - [ ] Remove validation state property (optional) + +- [ ] **Backend Validation** (`isoq_server/auth_server/controllers/core.py`) + + - [ ] Remove field from `only_check` array + +- [ ] **Translations** + + - [ ] Update validation messages to indicate optional + - [ ] Or remove validation messages entirely + +- [ ] **Testing** + - [ ] Test creating project without field → no error + - [ ] Test publishing without field → allowed + - [ ] Test in all 3 languages + +## Changing Validation Rules + +- [ ] **Frontend Validation** (`src/utils/project.js`) + + - [ ] Update validation logic (e.g., min length, regex) + +- [ ] **Backend Validation** (`isoq_server/auth_server/controllers/core.py`) + + - [ ] Update corresponding backend logic + +- [ ] **Translations** + + - [ ] Update error messages to reflect new rules + +- [ ] **Testing** + - [ ] Test edge cases (exactly at limit, just below, just above) + - [ ] Test with invalid data → shows error + - [ ] Test with valid data → passes + - [ ] Test in all 3 languages diff --git a/.agent/skills/troubleshoot-production-issues/SKILL.md b/.agent/skills/troubleshoot-production-issues/SKILL.md new file mode 100644 index 00000000..3f75b380 --- /dev/null +++ b/.agent/skills/troubleshoot-production-issues/SKILL.md @@ -0,0 +1,408 @@ +--- +description: Guide for diagnosing and resolving production issues in iSoQ projects +--- + +# Skill: Troubleshoot Production Issues + +## Purpose + +This skill provides systematic procedures for diagnosing and resolving common production issues in iSoQ, particularly those related to project publication, data integrity, and user-reported problems. + +## When to Use This Skill + +- User reports inability to publish a project +- Data appears missing or corrupted +- Validation errors are unclear +- References, findings, or extracted data not displaying +- Publication status inconsistencies +- Performance issues with large projects + +## Common Production Issues + +### Issue 1: Cannot Publish Project + +**Symptoms:** + +- "Publish" button disabled or shows error +- Validation error message appears +- Project stuck in "private" mode + +**Diagnostic Steps:** + +#### Step 1: Check Project Metadata + +```javascript +// Check if all required fields are filled +Required fields: +- name (min 3 chars) +- authors (not empty) +- author (min 3 chars) +- author_email (valid email) +- review_question (min 3 chars) +- license_type (selected) +- complete_by_author OR lists_authors +- If published_status=true: url_doi (valid URL) +``` + +**Location:** Project Properties tab in `viewProject.vue` + +#### Step 2: Check References + +```javascript +// Verify at least 1 reference exists +GET /isoqf_references?project_id={project_id} +// Should return array with length > 0 +``` + +**Common causes:** + +- No references uploaded +- References deleted after findings created +- References not properly saved + +**Fix:** Go to Step 1 (References) and upload/import references + +#### Step 3: Check Findings with References + +```javascript +// Verify at least 1 finding has references assigned +GET /isoqf_lists?project_id={project_id} +// Check each list: list.references.length > 0 +``` + +**Common causes:** + +- Finding created but no references assigned +- References removed from finding +- Finding deleted + +**Fix:** + +1. Go to organization view → project findings list +2. Click on finding +3. Assign references to the finding + +#### Step 4: Check CERQual Completion + +```javascript +// Verify at least 1 finding has CERQual explanation +GET /isoqf_lists?project_id={project_id} +// Check: list.cerqual.explanation !== '' +``` + +**Common causes:** + +- CERQual assessment not completed +- Only option selected, no explanation written +- Explanation deleted + +**Fix:** + +1. Go to Worksheet for the finding +2. Click on CERQual assessment +3. Write detailed explanation (required!) + +#### Step 5: Run Backend Validation + +```javascript +// Call the validation endpoint directly +GET /api/project/can_publish?id={project_id}&workspace={org_id} +``` + +**Possible error messages:** + +- "You need at least one reference loaded" → Go to Step 2 +- "You need at least one reference assigned to a review finding" → Go to Step 3 +- "You need to have at least one review finding with a complete GRADE-CERQual assessment" → Go to Step 4 +- "Your request to publish has been denied because information is missing" → Go to Step 1 + +### Issue 2: Missing or Corrupted Data + +**Symptoms:** + +- References show in Step 1 but not in Worksheet +- Findings appear empty +- Extracted data missing +- Characteristics or assessments not displaying + +**Diagnostic Steps:** + +#### Step 1: Verify Data Existence + +```javascript +// Check if data actually exists in database +GET /isoqf_references?project_id={project_id} +GET /isoqf_lists?project_id={project_id} +GET /isoqf_findings?list_id={list_id} +GET /isoqf_characteristics?project_id={project_id} +GET /isoqf_assessments?project_id={project_id} +GET /isoqf_extracted_data?finding_id={finding_id} +``` + +#### Step 2: Check Data Relationships + +```javascript +// Verify IDs match correctly +Reference IDs in finding.references should exist in isoqf_references +Finding.list_id should match a list in isoqf_lists +Extracted data finding_id should match finding.id +``` + +**Common causes:** + +- References deleted but still referenced in findings +- Finding deleted but list remains +- Mismatched IDs after data migration + +**Fix:** + +- If references missing: Re-upload references +- If relationships broken: May need database cleanup script +- If IDs mismatched: Contact backend developer + +#### Step 3: Check Permissions + +```javascript +// Verify user has access to the data +User must be: +- Owner of the organization, OR +- In project.can_write array, OR +- In project.can_read array (read-only) +``` + +**Location:** Check in `isoq_server/auth_server/controllers/core.py` → `@secure` decorator + +### Issue 3: Validation Errors Are Unclear + +**Symptoms:** + +- Error message doesn't specify which field is missing +- Multiple validation errors at once +- Error message in wrong language + +**Diagnostic Steps:** + +#### Step 1: Check Frontend Validation State + +```javascript +// In browser console while on Project Properties +// Check validation state object +this.$refs.organizationForm.state; +// Will show which fields are invalid (false values) +``` + +#### Step 2: Check Translation Keys + +```javascript +// Verify translation keys exist +// In browser console: +this.$t("project.validation.field_name"); +// Should return translated message, not the key itself +``` + +**Common causes:** + +- Translation key missing in language file +- Wrong language file loaded +- Validation state not properly set + +**Fix:** + +1. Add missing translation keys to `src/lang/*.json` +2. Ensure all 3 language files have the key +3. Clear browser cache and reload + +#### Step 3: Check Backend Error Messages + +```javascript +// Backend errors come from core.py +// Check the response in Network tab (DevTools) +// Look for 'message' field in response +``` + +**Common causes:** + +- Backend returns generic error +- Frontend doesn't display backend message +- Error message not internationalized + +**Fix:** + +- Update error messages in `core.py` +- Ensure frontend displays `response.data.message` + +### Issue 4: Performance Issues + +**Symptoms:** + +- Slow loading of projects with many references +- Worksheet takes long to open +- Browser freezes when editing large tables + +**Diagnostic Steps:** + +#### Step 1: Check Data Volume + +```javascript +// Count items in project +References: GET /isoqf_references?project_id={id} → .length +Findings: GET /isoqf_lists?project_id={id} → .length +Characteristics: GET /isoqf_characteristics?project_id={id} → .length +Assessments: GET /isoqf_assessments?project_id={id} → .length +``` + +**Thresholds:** + +- \> 100 references: May cause slowness +- \> 50 findings: Worksheet may be slow +- \> 1000 characteristic items: Table rendering slow + +#### Step 2: Check Network Requests + +```javascript +// In DevTools Network tab, check: +- Number of API calls on page load +- Size of responses (MB) +- Time to first byte (TTFB) +``` + +**Common causes:** + +- N+1 query problem (multiple API calls) +- Large payload sizes +- No pagination on tables + +**Fix:** + +- Implement pagination for large tables +- Add loading indicators +- Consider lazy loading for large datasets + +#### Step 3: Check Browser Console + +```javascript +// Look for warnings about: +- Large arrays being rendered +- Memory leaks +- Infinite loops in watchers +``` + +**Fix:** + +- Add virtual scrolling for large tables +- Optimize Vue watchers +- Use `v-show` instead of `v-if` for frequently toggled elements + +## Quick Diagnostic Checklist + +When user reports an issue, run through this checklist: + +### Publication Issues + +- [ ] All required project fields filled? +- [ ] At least 1 reference exists? +- [ ] At least 1 finding has references? +- [ ] At least 1 finding has CERQual explanation? +- [ ] User is project owner or has write access? +- [ ] Project not locked by another user? + +### Data Display Issues + +- [ ] Data exists in database (check API)? +- [ ] IDs match correctly (references, findings, etc.)? +- [ ] User has permission to view data? +- [ ] No console errors in browser? +- [ ] Translation keys exist for all languages? + +### Performance Issues + +- [ ] How many references/findings in project? +- [ ] Network requests reasonable (\< 10 on load)? +- [ ] Response sizes reasonable (\< 1MB)? +- [ ] Browser console shows no memory warnings? + +## Common Error Messages and Solutions + +| Error Message | Cause | Solution | +| --------------------------------------------------------------------------------------- | ------------------------------------- | ---------------------------------------------- | +| "You need at least one reference loaded" | No references in project | Upload references in Step 1 | +| "You need at least one reference assigned to a review finding" | Findings exist but have no references | Assign references to finding | +| "You need to have at least one review finding with a complete GRADE-CERQual assessment" | CERQual explanation missing | Complete CERQual in Worksheet | +| "Your request to publish has been denied because information is missing" | Required project fields missing | Fill all required fields in Project Properties | +| "Cannot read properties of undefined" | Data structure mismatch | Check if referenced data exists | +| "Property or method is not defined" | Component error | Check browser console for details | + +## Tools and Resources + +### Browser DevTools + +- **Console**: Check for JavaScript errors +- **Network**: Monitor API calls and responses +- **Vue DevTools**: Inspect component state and props + +### API Testing + +Use browser DevTools Network tab or tools like Postman to test: + +``` +GET /api/project/can_publish?id={project_id}&workspace={org_id} +GET /isoqf_projects/{project_id} +GET /isoqf_lists?project_id={project_id} +GET /isoqf_references?project_id={project_id} +``` + +### Database Queries + +If you have database access: + +```javascript +// Check project +db.isoqf_projects.findOne({ id: "project_id" }); + +// Check lists with CERQual +db.isoqf_lists.find({ + project_id: "project_id", + "cerqual.explanation": { $ne: "" }, +}); + +// Check references +db.isoqf_references.find({ project_id: "project_id" }).count(); +``` + +## Escalation Path + +If issue cannot be resolved: + +1. **Collect Information:** + + - Project ID + - Organization ID + - User ID + - Exact error message + - Steps to reproduce + - Browser console errors + - Network tab screenshots + +2. **Check Logs:** + + - Backend logs for API errors + - Browser console for frontend errors + +3. **Create Bug Report:** + - Include all collected information + - Specify environment (dev/staging/prod) + - Tag with severity level + +## Related Files + +- `src/utils/project.js` - Frontend validation +- `src/components/organization/organizationForm.vue` - Project form +- `src/components/project/viewProject.vue` - Main project view +- `src/components/list/editList.vue` - Worksheet +- `isoq_server/auth_server/controllers/core.py` - Backend validation + +## See Also + +- CLAUDE.md section "Project Creation & Publication Flow" → "Publication Requirements" +- CLAUDE.md section "Common Gotchas" +- Skill: `modify-publication-requirements` - For changing validation logic diff --git a/.agent/skills/troubleshoot-production-issues/resources/common-errors.md b/.agent/skills/troubleshoot-production-issues/resources/common-errors.md new file mode 100644 index 00000000..da13ddac --- /dev/null +++ b/.agent/skills/troubleshoot-production-issues/resources/common-errors.md @@ -0,0 +1,169 @@ +# Common Publication Errors + +This document lists common errors users encounter when trying to publish projects and their solutions. + +## Error: "You need at least one reference loaded in your project" + +**Cause:** Project has no references uploaded. + +**Solution:** + +1. Go to Project → My Data → Step 1: References +2. Upload references via: + - File upload (RIS, BibTeX, etc.), OR + - PubMed import +3. Verify references appear in the table +4. Try publishing again + +**Prevention:** Upload references before creating findings. + +--- + +## Error: "You need at least one reference assigned to a review finding" + +**Cause:** Project has references and findings, but no finding has references assigned to it. + +**Solution:** + +1. Go to Organization view → click on project +2. Click on a finding in the list +3. In the Worksheet, assign references to the finding +4. Save changes +5. Try publishing again + +**Prevention:** Always assign references when creating findings. + +--- + +## Error: "You need to have at least one review finding with a complete GRADE-CERQual assessment" + +**Cause:** No finding has a completed CERQual explanation. + +**Solution:** + +1. Go to Worksheet for a finding +2. Complete all 4 Evidence Profile components: + - Methodological Limitations + - Coherence + - Adequacy + - Relevance +3. **Most important:** Complete CERQual Assessment with detailed explanation +4. Save changes +5. Try publishing again + +**Prevention:** Complete the entire Evidence Profile before attempting to publish. + +--- + +## Error: "Your request to publish has been denied because information is missing" + +**Cause:** One or more required project metadata fields are empty. + +**Solution:** + +1. Go to Project → Project Properties tab +2. Fill in all required fields (marked in red): + - Title of Review (min 3 characters) + - Authors + - Corresponding Author (min 3 characters) + - Corresponding Author Email (valid email) + - Review Question (min 3 characters) + - License Type + - Complete by Author OR List of Authors + - If published: URL/DOI +3. Save changes +4. Try publishing again + +**Prevention:** Fill all required fields when creating the project. + +--- + +## Error: "Cannot read properties of undefined (reading 'length')" + +**Cause:** Component trying to access data that doesn't exist yet. + +**Solution:** + +1. Refresh the page +2. If error persists, check browser console for details +3. May indicate data corruption - check if references/findings exist + +**Prevention:** Ensure data is loaded before accessing it in components. + +--- + +## Error: Fields highlighted in red but no error message + +**Cause:** Frontend validation failed but error message missing. + +**Solution:** + +1. Check which fields are highlighted +2. Verify field meets requirements: + - Name: min 3 characters + - Email: valid format + - Review Question: min 3 characters +3. Check browser console for translation key errors +4. May need to add translation keys + +**Prevention:** Ensure all translation keys exist in language files. + +--- + +## Error: "Project is locked by another user" + +**Cause:** Another user is currently editing the project. + +**Solution:** + +1. Wait for the other user to finish editing +2. Ask the other user to close the project +3. If user is no longer editing, wait 5 minutes for lock to expire +4. Try again + +**Prevention:** Coordinate with team members before editing shared projects. + +--- + +## Error: Changes not saving / Auto-save conflict + +**Cause:** Project is in read-only mode or locked. + +**Solution:** + +1. Check if you have write permissions +2. Check if project is locked by another user +3. Refresh the page and try again +4. If offline, wait until connection is restored + +**Prevention:** Ensure you have write access before making changes. + +--- + +## Error: "Missing translation key" or text shows as "project.field_name" + +**Cause:** Translation key doesn't exist in language file. + +**Solution:** + +1. Add missing key to `src/lang/en.json` +2. Add same key to `src/lang/es.json` +3. Add same key to `src/lang/pt.json` +4. Rebuild and refresh + +**Prevention:** Always add translations when adding new fields. + +--- + +## Error: Extracted data not showing + +**Cause:** Extracted data not created for references in finding. + +**Solution:** + +1. Go to Worksheet → Extracted Data section +2. Verify there's a row for each reference +3. If missing, may need to recreate finding or manually add extracted data +4. Save changes + +**Prevention:** Ensure extracted data is created when adding references to findings. diff --git a/.babelrc b/.babelrc index eedd5336..6c5eac91 100644 --- a/.babelrc +++ b/.babelrc @@ -16,6 +16,21 @@ ], "plugins": [ "transform-vue-jsx", - "@babel/plugin-transform-runtime" - ] + "@babel/plugin-transform-runtime", + "@babel/plugin-proposal-class-properties" + ], + "env": { + "test": { + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "node": "current" + } + } + ] + ] + } + } } \ No newline at end of file diff --git a/.gemini/settings.json b/.gemini/settings.json new file mode 100644 index 00000000..a14a3225 --- /dev/null +++ b/.gemini/settings.json @@ -0,0 +1,14 @@ +{ + "general": { + "previewFeatures": true + }, + "context": { + "fileName": ["CLAUDE.md"] + }, + "mcpServers": { + "chrome-devtools": { + "command": "npx", + "args": ["chrome-devtools-mcp@latest"] + } + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index f5b79336..5231cc76 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ config/prod.env.js *.njsproj *.sln src/components/camelot/char_demo.json + +# Test coverage +coverage/ diff --git a/CLAUDE.md b/CLAUDE.md index 5b01b54b..9981a3cf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,241 +1,115 @@ -# iSoQ Web - AI Agent Guide - -## Project Overview -**Interactive Summary of Qualitative Findings (iSoQ)** - A Vue.js 2 application for creating and managing interactive summaries of qualitative research findings using the GRADE-CERQual approach. Built for the Epistemonikos Foundation. - -**Tech Stack:** Vue 2.6, Vuex, Vue Router, Bootstrap-Vue, Webpack 4, Axios -**Node Version:** >= 14.0.0 - -## Architecture - -### Component Organization -Components are organized by feature domain: -- `src/components/project/` - Core iSoQ project management (ViewTable.vue, viewProject.vue, UploadReferences.vue) -- `src/components/list/` - Worksheet/list editing (editList.vue, evidenceProfileForm.vue) -- `src/components/organization/` - Workspace management -- `src/components/previewContent/` - Public preview views (previewContentSoQf.vue, previewContentWorksheet.vue) -- `src/components/tableActions/` - Reusable table components (ActionTable.vue, Filters.vue) -- `src/components/charsOfStudies/`, `methAssessments/` - Domain-specific data display - -### State Management -Single Vuex store at `src/store.js`: -- **Authentication state:** User login status, permissions (`can_write_other_orgs`, `is_owner`) -- **Key actions:** `login`, `logout`, `getLogginInfo` (checks auth on route navigation) -- **Auth persistence:** Uses cookies (session-based), token stored in `localStorage` as `l_s` -- Promise-based login check stored in `state.promise` for route guard synchronization - -### Routing -- **Mode:** Hash-based routing (`mode: 'hash'`) -- **Auth guard:** `beforeEach` calls `getLogginInfo`, checks `meta.requiresAuth` -- **Route naming:** Routes use camelCase names (e.g., `viewProject`, `editList`) -- **Key routes:** - - `/workspace/:org_id/isoqf/:id` → Project view - - `/worksheet/:id/edit` → Worksheet editor - - `/preview/isoq/:org_id/:isoqf_id/:token` → Public preview -- **Title handling:** Routes set `document.title` via `meta.title` - -### API Communication -Custom API wrapper at `src/utils/Api.js`: -\`\`\`javascript -import Api from '@/utils/Api' -Api.get('/path', params) -Api.post('/path', data) -Api.put('/path', data) -Api.delete('/path') -\`\`\` -- **Base URL:** Set via `process.env.API_URL` -- **Auth:** Token from `localStorage.getItem('l_s')` → Header: `Authorization: Token session="..."` -- **Alternative:** Direct axios calls for auth endpoints (`/auth/login`, `/auth/logout`) - -### Internationalization (i18n) -- Plugin at `src/plugins/i18n.js` using vue-i18n -- Locale files: `src/lang/en.json` -- Default locale: `en` -- Usage in components: `$t('key')` or `{{ $t('key') }}` -- Route translation helper: `$i18nRoute` (Trans plugin) - -## Development Workflow - -### Local Development Setup - -**IMPORTANT:** Full-stack development requires starting multiple services in order: - -1. **Start Database (Docker)** - \`\`\`bash - # Launch Docker container with database - docker compose up # or docker-compose up - \`\`\` - -2. **Start Backend Server** - \`\`\`bash - cd ~/dev/epistemonikos/isoq_server - conda activate isoq-server - # Run backend server command (typically runs on port 8080) - \`\`\` - -3. **Start Frontend (this repository)** - \`\`\`bash - cd ~/dev/epistemonikos/isoq_web - nvm use isoq - npm run dev # or npm start - \`\`\` - -4. **Access Application** - - URL: `http://episte.lo:8090` - - Test user credentials: Ask a team member for dev credentials - -### Starting Development Server -\`\`\`bash -npm run dev -# Or explicitly: -npm start -\`\`\` -- **Dev server:** Runs on `http://episte.lo:8090` (or next available port) -- **Backend dependency:** Frontend proxies API calls to `http://localhost:8080` (backend must be running) -- **Proxy setup:** All API endpoints (`/auth`, `/api`, `/users`, `/organizations`, `/project`) proxy to backend -- **Hot reload:** Enabled via webpack-dev-server -- **DNS requirement:** `episte.lo` must resolve to localhost (add to `/etc/hosts` if needed) - -### Building for Production -\`\`\`bash -npm run build -\`\`\` -- **Output:** `dist/` directory -- **Asset path:** Relative (`assetsPublicPath: './'`) -- **Source maps:** Disabled in production (`productionSourceMap: false`) -- **No gzip** by default - -### Deployment -Manual deployment process: -1. Ensure `master` branch is up to date with desired changes -2. SSH into production server -3. Pull latest code from `master` branch -4. Run deployment script/commands on server -5. No automated CI/CD pipeline currently configured - -### Code Quality -\`\`\`bash -npm run lint -\`\`\` -- **Linter:** ESLint with `babel-eslint` parser -- **Style guide:** Standard JS + Vue Essential rules -- **Currently disabled** in build process (`useEslint: false` in config) -- Debugger statements allowed in development - -### No Testing Framework -No test files or test runner configured. If adding tests, you'll need to set up Jest or similar. - -## Key Patterns & Conventions - -### Component Loading -Lazy loading with webpack chunks for route components: -\`\`\`javascript -const MainPage = () => import(/* webpackChunkName: "home" */ '@/components/MainPage') -\`\`\` - -### Data Validation -Project publishing requires validation via `src/utils/project.js`: -- Email validation: `Project.validEmail(email)` -- URL validation: `Project.validUrl(url)` -- Full validation: `Project.validations(data)` checks required fields for public projects -- Returns structured error state for form field highlighting - -### Bootstrap-Vue Usage -Heavy use of Bootstrap-Vue components: -- Tables: `` with filters, sorting, pagination -- Modals: `` for dialogs -- Forms: ``, ``, etc. -- Layout: ``, ``, `` -- See `ViewTable.vue` for typical table patterns with dropdown filters - -### FontAwesome Icons -Icons added to library in `src/main.js`: -\`\`\`javascript -import { faEdit, faCopy, faTrash, ... } from '@fortawesome/free-solid-svg-icons' -library.add(faEdit, faCopy, ...) -\`\`\` -Usage: `` - -### Styling -- Main styles: `src/assets/styles/main.scss` -- Per-component styles: ` diff --git a/src/components/Organizations.vue b/src/components/Organizations.vue index ae3f5e42..001afb95 100644 --- a/src/components/Organizations.vue +++ b/src/components/Organizations.vue @@ -2,7 +2,7 @@
-

Workspaces

+

{{ $t('menu.workspaces') }}

@@ -10,7 +10,7 @@ -

My Workspace

+

{{ $t('menu.my_workspace') }}

+
+ + + + +
+ + {{ $t('account.verifying_email') }} +
+ + {{ $t('account.email_verified') }} + + + {{ $t('account.verification_failed') }} +
+ {{ $t('common.login') }} +
+
+
+
+
+
+
+ + + diff --git a/src/components/WhatsNew.vue b/src/components/WhatsNew.vue index 08aeb855..375fa64e 100644 --- a/src/components/WhatsNew.vue +++ b/src/components/WhatsNew.vue @@ -2,102 +2,74 @@
-

What's New

+

{{ $t('whats_new.title') }}

-

What's new in iSoQ?

-

Here you will find the latest updates and improvements to iSoQ Version 1.0.0.

- -

February 2025

-

Changing your assessment won’t delete your explanation

+

{{ $t('whats_new.subtitle') }}

+

{{ $t('whats_new.desc') }}

+ +

{{ $t('updates.feb_2025') }}

+

{{ $t('updates.assessment_no_delete_title') }}

    -
  • - Based on user feedback, iSoQ will no longer delete the text you have written in an explanation box if you change your choice of level of concern or level of confidence. -
  • +
  • {{ $t('updates.assessment_no_delete_desc') }}
-

Users with “view only” rights now see Worksheets

+

{{ $t('updates.view_only_worksheets_title') }}

    -
  • - When a user is invited to a project but assigned “view only” rights they can now see (but not edit) all pages including project properties, My Data section, iSoQ table and Worksheets with the Evidence Profile. -
  • +
  • {{ $t('updates.view_only_worksheets_desc') }}
-

Accessibility: Increase and decrease text size

+

{{ $t('updates.accessibility_title') }}

    -
  • - You will now find a +A and -A in the top right corner of the screen so that you can adjust the size of the font throughout iSoQ. -
  • +
  • {{ $t('updates.accessibility_desc') }}
-

Prompt to check your references, success starts here!

+

{{ $t('updates.check_refs_title') }}

    -
  • - When you import your references in Step 1 of the MyData section of iSoQ, your imported studies are now displayed, thus prompting you to look them over before manually moving to Step 2. It is important to make sure that your references have imported correctly and are complete. You cannot currently edit your references within iSoQ. -
  • +
  • {{ $t('updates.check_refs_desc') }}
-

December 2024

-

Narrower margins = easier viewing of content

+ +

{{ $t('updates.dec_2024') }}

+

{{ $t('updates.margins_title') }}

    -
  • - The margins have been reduced throughout the platform to maximize what you are able to see in tables and modals. -
  • +
  • {{ $t('updates.margins_desc') }}
-

New section in help tab – iSoQ training

+

{{ $t('updates.help_training_title') }}

    -
  • - We have added links to iSoQ training webinars on the Help page -
  • +
  • {{ $t('updates.help_training_desc') }}
-

May 2024

- -

Facilitating access to information on iSoQ development

+ +

{{ $t('updates.may_2024') }}

+

{{ $t('updates.newsletter_title') }}

    -
  • - Users are now prompted to sign-up to the GRADE-CERQual newsletter when they create an account or change their password. GRADE-CERQual newsletters are sent three times a year and include the latest news about the iSoQ platform, upcoming training or webinars, and other resources for applying the GRADE-CERQual approach -
  • +
  • {{ $t('updates.newsletter_desc') }}
-

April 2024

- -

We have redesigned the My Data page for easier navigation

+ +

{{ $t('updates.apr_2024') }}

+

{{ $t('updates.mydata_redesign_title') }}

    -
  • - A table of contents on the left makes visualising and moving between each step easy -
  • -
  • - Edit and delete buttons for each row in the tables (step 3&4) remain visible even when scrolling -
  • -
  • - Scroll over the Authors names to see the title of the paper in Steps 3&4 -
  • +
  • {{ $t('updates.mydata_redesign_li1') }}
  • +
  • {{ $t('updates.mydata_redesign_li2') }}
  • +
  • {{ $t('updates.mydata_redesign_li3') }}
-

February 2024

- -

No more jumping!

+ +

{{ $t('updates.feb_2024') }}

+

{{ $t('updates.no_jumping_title') }}

    -
  • - Previously iSoQ defaulted to jumping to the top of the page when the user navigated between pages. Now iSoQ holds your spot on the page thus reducing scroll time. -
  • -
  • - You will notice this enhancement especially when navigating back and forth between the iSoQ table and he GRADE-CERQual Assessment Worksheets for each finding. -
  • +
  • {{ $t('updates.no_jumping_li1') }}
  • +
  • {{ $t('updates.no_jumping_li2') }}
-

October 2023

- -

Leaving a project simplified

+ +

{{ $t('updates.oct_2023') }}

+

{{ $t('updates.leave_project_title') }}

    -
  • - Users can now leave a project that was shared with them by clicking the "leave" button in My Workspace -
  • -
  • - This will remove the project entirely from My Workspace -
  • +
  • {{ $t('updates.leave_project_li1') }}
  • +
  • {{ $t('updates.leave_project_li2') }}
-
diff --git a/src/components/backToTop.vue b/src/components/backToTop.vue index c004f612..b02e4b5f 100644 --- a/src/components/backToTop.vue +++ b/src/components/backToTop.vue @@ -1,6 +1,6 @@ diff --git a/src/components/camelot/CamelotAssessmentCard.vue b/src/components/camelot/CamelotAssessmentCard.vue new file mode 100644 index 00000000..65644ae0 --- /dev/null +++ b/src/components/camelot/CamelotAssessmentCard.vue @@ -0,0 +1,130 @@ + + + diff --git a/src/components/camelot/CamelotStepFourHeader.vue b/src/components/camelot/CamelotStepFourHeader.vue new file mode 100644 index 00000000..582593aa --- /dev/null +++ b/src/components/camelot/CamelotStepFourHeader.vue @@ -0,0 +1,120 @@ + + + + + diff --git a/src/components/camelot/CamelotStepFourTable.vue b/src/components/camelot/CamelotStepFourTable.vue new file mode 100644 index 00000000..a19bbb5b --- /dev/null +++ b/src/components/camelot/CamelotStepFourTable.vue @@ -0,0 +1,155 @@ + + + diff --git a/src/components/camelot/CustomFieldsManager.vue b/src/components/camelot/CustomFieldsManager.vue new file mode 100644 index 00000000..ace09071 --- /dev/null +++ b/src/components/camelot/CustomFieldsManager.vue @@ -0,0 +1,388 @@ + + + + + diff --git a/src/components/camelot/EditReferenceModal.vue b/src/components/camelot/EditReferenceModal.vue new file mode 100644 index 00000000..af7036ea --- /dev/null +++ b/src/components/camelot/EditReferenceModal.vue @@ -0,0 +1,494 @@ + + + + + diff --git a/src/components/camelot/ExportCSVButton.vue b/src/components/camelot/ExportCSVButton.vue new file mode 100644 index 00000000..16dff8ea --- /dev/null +++ b/src/components/camelot/ExportCSVButton.vue @@ -0,0 +1,60 @@ + + + diff --git a/src/components/camelot/ManageColumnsButton.vue b/src/components/camelot/ManageColumnsButton.vue new file mode 100644 index 00000000..e44189bd --- /dev/null +++ b/src/components/camelot/ManageColumnsButton.vue @@ -0,0 +1,226 @@ + + + diff --git a/src/components/camelot/Responses.vue b/src/components/camelot/Responses.vue index e8213b2a..57125552 100644 --- a/src/components/camelot/Responses.vue +++ b/src/components/camelot/Responses.vue @@ -6,7 +6,20 @@ {{ getText() }}
-

{{ text }}

+
+
+
{{ $t('camelot.step_four.legend.not_completed') }}
+
+

+ {{ $t('camelot.responses.explanation') }}: + + +

@@ -31,103 +44,103 @@ export default { default: '' } }, - data () { + data() { return { // Define any local state here if needed options: [ [ { - text: 'Meta domain Research against Research design domains', + text: this.$t('camelot.assessment_form.descriptions.meta_research_vs_design'), values: [ - { text: 'No or minimal concerns', value: 'A', color: '#1065AB' }, - { text: 'Minor concerns', value: 'B', color: '#8EC4DE' }, - { text: 'Moderate concerns', value: 'C', color: '#F6A482' }, - { text: 'Serious concerns', value: 'D', color: '#B31529' }, - { text: 'Unclear', value: 'E', color: '#B3B3B3' } + { text: this.$t('camelot.responses.no_minimal'), value: 'A', color: '#1065AB' }, + { text: this.$t('camelot.responses.minor'), value: 'B', color: '#8EC4DE' }, + { text: this.$t('camelot.responses.moderate'), value: 'C', color: '#F6A482' }, + { text: this.$t('camelot.responses.serious'), value: 'D', color: '#B31529' }, + { text: this.$t('camelot.responses.unclear'), value: 'E', color: '#B3B3B3' } ] }, { - text: 'Meta domain Stakeholders against Research design domains', + text: this.$t('camelot.assessment_form.descriptions.meta_stakeholders_vs_design'), values: [ - { text: 'No or minimal concerns', value: 'A', color: '#1065AB' }, - { text: 'Minor concerns', value: 'B', color: '#8EC4DE' }, - { text: 'Moderate concerns', value: 'C', color: '#F6A482' }, - { text: 'Serious concerns', value: 'D', color: '#B31529' }, - { text: 'Unclear', value: 'E', color: '#B3B3B3' } + { text: this.$t('camelot.responses.no_minimal'), value: 'A', color: '#1065AB' }, + { text: this.$t('camelot.responses.minor'), value: 'B', color: '#8EC4DE' }, + { text: this.$t('camelot.responses.moderate'), value: 'C', color: '#F6A482' }, + { text: this.$t('camelot.responses.serious'), value: 'D', color: '#B31529' }, + { text: this.$t('camelot.responses.unclear'), value: 'E', color: '#B3B3B3' } ] }, { - text: 'Meta domain Researchers against Research design domains', + text: this.$t('camelot.assessment_form.descriptions.meta_researchers_vs_design'), values: [ - { text: 'No or minimal concerns', value: 'A', color: '#1065AB' }, - { text: 'Minor concerns', value: 'B', color: '#8EC4DE' }, - { text: 'Moderate concerns', value: 'C', color: '#F6A482' }, - { text: 'Serious concerns', value: 'D', color: '#B31529' }, - { text: 'Unclear', value: 'E', color: '#B3B3B3' } + { text: this.$t('camelot.responses.no_minimal'), value: 'A', color: '#1065AB' }, + { text: this.$t('camelot.responses.minor'), value: 'B', color: '#8EC4DE' }, + { text: this.$t('camelot.responses.moderate'), value: 'C', color: '#F6A482' }, + { text: this.$t('camelot.responses.serious'), value: 'D', color: '#B31529' }, + { text: this.$t('camelot.responses.unclear'), value: 'E', color: '#B3B3B3' } ] }, { - text: 'Meta domain Context against Research design domains', + text: this.$t('camelot.assessment_form.descriptions.meta_context_vs_design'), values: [ - { text: 'No or minimal concerns', value: 'A', color: '#1065AB' }, - { text: 'Minor concerns', value: 'B', color: '#8EC4DE' }, - { text: 'Moderate concerns', value: 'C', color: '#F6A482' }, - { text: 'Serious concerns', value: 'D', color: '#B31529' }, - { text: 'Unclear', value: 'E', color: '#B3B3B3' } + { text: this.$t('camelot.responses.no_minimal'), value: 'A', color: '#1065AB' }, + { text: this.$t('camelot.responses.minor'), value: 'B', color: '#8EC4DE' }, + { text: this.$t('camelot.responses.moderate'), value: 'C', color: '#F6A482' }, + { text: this.$t('camelot.responses.serious'), value: 'D', color: '#B31529' }, + { text: this.$t('camelot.responses.unclear'), value: 'E', color: '#B3B3B3' } ] } ], [ { - text: 'Meta domain Research against Research conduct domains', + text: this.$t('camelot.assessment_form.descriptions.meta_research_vs_conduct'), values: [ - { text: 'No or minimal concerns', value: 'A', color: '#1065AB' }, - { text: 'Minor concerns', value: 'B', color: '#8EC4DE' }, - { text: 'Moderate concerns', value: 'C', color: '#F6A482' }, - { text: 'Serious concerns', value: 'D', color: '#B31529' }, - { text: 'Unclear', value: 'E', color: '#B3B3B3' } + { text: this.$t('camelot.responses.no_minimal'), value: 'A', color: '#1065AB' }, + { text: this.$t('camelot.responses.minor'), value: 'B', color: '#8EC4DE' }, + { text: this.$t('camelot.responses.moderate'), value: 'C', color: '#F6A482' }, + { text: this.$t('camelot.responses.serious'), value: 'D', color: '#B31529' }, + { text: this.$t('camelot.responses.unclear'), value: 'E', color: '#B3B3B3' } ] }, { - text: 'Meta domain Stakeholders against Research conduct domains', + text: this.$t('camelot.assessment_form.descriptions.meta_stakeholders_vs_conduct'), values: [ - { text: 'No or minimal concerns', value: 'A', color: '#1065AB' }, - { text: 'Minor concerns', value: 'B', color: '#8EC4DE' }, - { text: 'Moderate concerns', value: 'C', color: '#F6A482' }, - { text: 'Serious concerns', value: 'D', color: '#B31529' }, - { text: 'Unclear', value: 'E', color: '#B3B3B3' } + { text: this.$t('camelot.responses.no_minimal'), value: 'A', color: '#1065AB' }, + { text: this.$t('camelot.responses.minor'), value: 'B', color: '#8EC4DE' }, + { text: this.$t('camelot.responses.moderate'), value: 'C', color: '#F6A482' }, + { text: this.$t('camelot.responses.serious'), value: 'D', color: '#B31529' }, + { text: this.$t('camelot.responses.unclear'), value: 'E', color: '#B3B3B3' } ] }, { - text: 'Meta domain Researchers against Research conduct domains', + text: this.$t('camelot.assessment_form.descriptions.meta_researchers_vs_conduct'), values: [ - { text: 'No or minimal concerns', value: 'A', color: '#1065AB' }, - { text: 'Minor concerns', value: 'B', color: '#8EC4DE' }, - { text: 'Moderate concerns', value: 'C', color: '#F6A482' }, - { text: 'Serious concerns', value: 'D', color: '#B31529' }, - { text: 'Unclear', value: 'E', color: '#B3B3B3' } + { text: this.$t('camelot.responses.no_minimal'), value: 'A', color: '#1065AB' }, + { text: this.$t('camelot.responses.minor'), value: 'B', color: '#8EC4DE' }, + { text: this.$t('camelot.responses.moderate'), value: 'C', color: '#F6A482' }, + { text: this.$t('camelot.responses.serious'), value: 'D', color: '#B31529' }, + { text: this.$t('camelot.responses.unclear'), value: 'E', color: '#B3B3B3' } ] }, { - text: 'Meta domain Context against Research conduct domains', + text: this.$t('camelot.assessment_form.descriptions.meta_context_vs_conduct'), values: [ - { text: 'No or minimal concerns', value: 'A', color: '#1065AB' }, - { text: 'Minor concerns', value: 'B', color: '#8EC4DE' }, - { text: 'Moderate concerns', value: 'C', color: '#F6A482' }, - { text: 'Serious concerns', value: 'D', color: '#B31529' }, - { text: 'Unclear', value: 'E', color: '#B3B3B3' } + { text: this.$t('camelot.responses.no_minimal'), value: 'A', color: '#1065AB' }, + { text: this.$t('camelot.responses.minor'), value: 'B', color: '#8EC4DE' }, + { text: this.$t('camelot.responses.moderate'), value: 'C', color: '#F6A482' }, + { text: this.$t('camelot.responses.serious'), value: 'D', color: '#B31529' }, + { text: this.$t('camelot.responses.unclear'), value: 'E', color: '#B3B3B3' } ] } ], [ { - text: 'Research design domains against Research conduct domains', + text: this.$t('camelot.assessment_form.descriptions.design_vs_conduct'), values: [ - { text: 'No or minimal concerns', value: 'A', color: '#1065AB' }, - { text: 'Minor concerns', value: 'B', color: '#8EC4DE' }, - { text: 'Moderate concerns', value: 'C', color: '#F6A482' }, - { text: 'Serious concerns', value: 'D', color: '#B31529' }, - { text: 'Unclear', value: 'E', color: '#B3B3B3' } + { text: this.$t('camelot.responses.no_minimal'), value: 'A', color: '#1065AB' }, + { text: this.$t('camelot.responses.minor'), value: 'B', color: '#8EC4DE' }, + { text: this.$t('camelot.responses.moderate'), value: 'C', color: '#F6A482' }, + { text: this.$t('camelot.responses.serious'), value: 'D', color: '#B31529' }, + { text: this.$t('camelot.responses.unclear'), value: 'E', color: '#B3B3B3' } ] } ] @@ -157,4 +170,16 @@ export default { width: 20px; height: 20px; } + +.assessment-circle { + width: 20px; + height: 20px; + border-radius: 50%; + flex-shrink: 0; +} + +.circle-not-completed { + border: 2px dashed #B3B3B3; + background-color: transparent; +} diff --git a/src/components/camelot/StepFour.vue b/src/components/camelot/StepFour.vue index a1436d34..3d1f639a 100644 --- a/src/components/camelot/StepFour.vue +++ b/src/components/camelot/StepFour.vue @@ -1,577 +1,265 @@ @@ -33,9 +33,11 @@ v-if="option.option" class="option-circle mr-2" :style="{ backgroundColor: getOptionColor(option.option) }" - /> -

{{ getOptionText(option.option) }}

-

{{ option.text }}

+ > +
+

{{ getOptionText(option.option) }}

+

{{ option.text }}

+
  @@ -47,7 +49,7 @@
+ >

{{ getOptionText(data.item.firstStage0.option) }}

{{ data.item.firstStage0.text }}

@@ -60,7 +62,7 @@
+ >

{{ getOptionText(data.item.firstStage1.option) }}

{{ data.item.firstStage1.text }}

@@ -73,7 +75,7 @@
+ >

{{ getOptionText(data.item.firstStage2.option) }}

{{ data.item.firstStage2.text }}

@@ -86,7 +88,7 @@
+ >

{{ getOptionText(data.item.firstStage3.option) }}

{{ data.item.firstStage3.text }}

@@ -101,7 +103,7 @@
+ >

{{ getOptionText(data.item.secondStage0.option) }}

{{ data.item.secondStage0.text }}

@@ -114,7 +116,7 @@
+ >

{{ getOptionText(data.item.secondStage1.option) }}

{{ data.item.secondStage1.text }}

@@ -127,7 +129,7 @@
+ >

{{ getOptionText(data.item.secondStage2.option) }}

{{ data.item.secondStage2.text }}

@@ -140,7 +142,7 @@
+ >

{{ getOptionText(data.item.secondStage3.option) }}

{{ data.item.secondStage3.text }}

@@ -156,9 +158,11 @@ v-if="option.option" class="option-circle mr-2" :style="{ backgroundColor: getOptionColor(option.option) }" - /> -

{{ getOptionText(option.option) }}

-

{{ option.text }}

+ >
+
+

{{ getOptionText(option.option) }}

+

{{ option.text }}

+
  @@ -183,30 +187,30 @@ export default { data () { return { fields: [ - { key: 'authors', label: 'Authors' }, - { key: 'lastStage', label: 'Overall assessment' }, + { key: 'authors', label: this.$t('camelot.assessment_table.headers.authors') }, + { key: 'lastStage', label: this.$t('camelot.assessment_table.headers.overall_assessment') }, // Research design (firstStage) columns - { key: 'firstStage0', label: 'Research', thClass: 'first-stage-col' }, - { key: 'firstStage1', label: 'Stakeholders', thClass: 'first-stage-col' }, - { key: 'firstStage2', label: 'Researchers', thClass: 'first-stage-col' }, - { key: 'firstStage3', label: 'Context', thClass: 'first-stage-col' }, + { key: 'firstStage0', label: this.$t('camelot.assessment_table.headers.research'), thClass: 'first-stage-col' }, + { key: 'firstStage1', label: this.$t('camelot.assessment_table.headers.stakeholders'), thClass: 'first-stage-col' }, + { key: 'firstStage2', label: this.$t('camelot.assessment_table.headers.researchers'), thClass: 'first-stage-col' }, + { key: 'firstStage3', label: this.$t('camelot.assessment_table.headers.context'), thClass: 'first-stage-col' }, // Research conduct (secondStage) columns - { key: 'secondStage0', label: 'Research', thClass: 'second-stage-col' }, - { key: 'secondStage1', label: 'Stakeholders', thClass: 'second-stage-col' }, - { key: 'secondStage2', label: 'Researchers', thClass: 'second-stage-col' }, - { key: 'secondStage3', label: 'Context', thClass: 'second-stage-col' }, - { key: 'thirdStage', label: 'Researchers domain' } + { key: 'secondStage0', label: this.$t('camelot.assessment_table.headers.research'), thClass: 'second-stage-col' }, + { key: 'secondStage1', label: this.$t('camelot.assessment_table.headers.stakeholders'), thClass: 'second-stage-col' }, + { key: 'secondStage2', label: this.$t('camelot.assessment_table.headers.researchers'), thClass: 'second-stage-col' }, + { key: 'secondStage3', label: this.$t('camelot.assessment_table.headers.context'), thClass: 'second-stage-col' }, + { key: 'thirdStage', label: this.$t('camelot.assessment_table.headers.researchers_domain') } ] } }, methods: { getOptionText (option) { const optionsMap = { - 'A': 'No or minimal concern', - 'B': 'Minor concerns', - 'C': 'Moderate concerns', - 'D': 'Serious concerns', - 'E': 'Unclear' + 'A': this.$t('camelot.assessment_table.options.no_minimal'), + 'B': this.$t('camelot.assessment_table.options.minor'), + 'C': this.$t('camelot.assessment_table.options.moderate'), + 'D': this.$t('camelot.assessment_table.options.serious'), + 'E': this.$t('camelot.assessment_table.options.unclear') } return optionsMap[option] || option }, diff --git a/src/components/camelot/assessment/CamelotAssessmentSummaryTable.vue b/src/components/camelot/assessment/CamelotAssessmentSummaryTable.vue new file mode 100644 index 00000000..4ae4af68 --- /dev/null +++ b/src/components/camelot/assessment/CamelotAssessmentSummaryTable.vue @@ -0,0 +1,628 @@ + + + + + diff --git a/src/components/camelot/characteristics/CharacteristicsTable.vue b/src/components/camelot/characteristics/CharacteristicsTable.vue index 7a53a033..ce0a9aa7 100644 --- a/src/components/camelot/characteristics/CharacteristicsTable.vue +++ b/src/components/camelot/characteristics/CharacteristicsTable.vue @@ -1,18 +1,34 @@ diff --git a/src/components/contentGuidance.vue b/src/components/contentGuidance.vue index 0c3cca51..d8e1a195 100644 --- a/src/components/contentGuidance.vue +++ b/src/components/contentGuidance.vue @@ -1,47 +1,49 @@ diff --git a/src/components/editReviewFinding.vue b/src/components/editReviewFinding.vue index ec8e3318..2d1d5796 100644 --- a/src/components/editReviewFinding.vue +++ b/src/components/editReviewFinding.vue @@ -1,16 +1,16 @@ @@ -34,7 +34,7 @@ - diff --git a/src/components/list/editListActionButtons.vue b/src/components/list/editListActionButtons.vue index 33097784..2c60e431 100644 --- a/src/components/list/editListActionButtons.vue +++ b/src/components/list/editListActionButtons.vue @@ -11,10 +11,8 @@ id="exportButton" variant="outline-secondary" block - :disabled="exportState.isLoading" @click="exportToWord()"> - - {{ exportState.isLoading ? 'Exportando...' : 'Export to MS-Word' }} + {{ $t('actionButtons.export') }} {{ $t('actionButtons.to_ms_word') }} - Print or save as PDF + {{ $t('publish.print_pdf') }} - Edit + {{ $t('common.edit') }} - - - - - - {{ exportState.currentStep }} - - - - - - - - - {{ exportState.error }} - - -
- + diff --git a/src/components/list/editListEvidenceProfile.vue b/src/components/list/editListEvidenceProfile.vue index 87c7c048..cdd0a039 100644 --- a/src/components/list/editListEvidenceProfile.vue +++ b/src/components/list/editListEvidenceProfile.vue @@ -1,21 +1,13 @@