From ca5f7dd446f168c0ff97a93a342ec402c6a47489 Mon Sep 17 00:00:00 2001 From: Jennie Robinson Faber Date: Sat, 16 Aug 2025 17:21:52 +0100 Subject: [PATCH] style: update CSS for a bitmap aesthetic, enhance export options in templates, and streamline form field styling --- assets/css/main.css | 19 +- components/ExportOptions.vue | 718 +++++++++ .../conflict-resolution-framework.vue | 1193 +++------------ pages/templates/decision-framework.vue | 47 + pages/templates/membership-agreement.vue | 1354 +---------------- pages/templates/tech-charter.vue | 943 ++++-------- pages/wizards.vue | 3 +- 7 files changed, 1326 insertions(+), 2951 deletions(-) create mode 100644 components/ExportOptions.vue diff --git a/assets/css/main.css b/assets/css/main.css index 5f4debd..08a5c19 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -23,5 +23,20 @@ body { } .document-page { - @apply max-w-4xl mx-auto bg-white relative p-8 border-1 border-neutral-900 dark:border-neutral-100; -} \ No newline at end of file + @apply max-w-4xl mx-auto relative p-8 border-1 border-neutral-900 dark:border-neutral-100; +} + + +/* Bitmap aesthetic overrides - remove all rounded corners */ +* { + border-radius: 0 !important; + font-family: "Ubuntu", monospace !important; +} + +/* Form fields with bitmap styling */ +input, +textarea, +select { + font-family: "Ubuntu Mono", monospace !important; +} + diff --git a/components/ExportOptions.vue b/components/ExportOptions.vue new file mode 100644 index 0000000..c2e83b4 --- /dev/null +++ b/components/ExportOptions.vue @@ -0,0 +1,718 @@ + + + + + \ No newline at end of file diff --git a/pages/templates/conflict-resolution-framework.vue b/pages/templates/conflict-resolution-framework.vue index b2dec2f..68ce276 100644 --- a/pages/templates/conflict-resolution-framework.vue +++ b/pages/templates/conflict-resolution-framework.vue @@ -3,31 +3,17 @@ -
- -
-
-
- - - Markdown - - - - Copy Text - - - Copied - -
-
-
+ +
+ +
+
@@ -35,8 +21,7 @@

+ :data-org-name="formData.orgName || 'Organization'"> CONFLICT RESOLUTION FRAMEWORK

@@ -53,8 +38,7 @@ size="xl" class="w-full" :error="validationErrors.orgName" - @input="debouncedAutoSave" - /> + @input="debouncedAutoSave" />
@@ -65,11 +49,12 @@ size="xl" class="w-full" :error="validationErrors.orgType" - @change="autoSave" - /> + @change="autoSave" /> - + + @change="autoSave" />
@@ -95,44 +79,38 @@ label="Include this section" :ui="{ label: 'text-xs text-neutral-700 dark:text-neutral-300', - }" - /> + }" />
+ class="form-group-large">
+ class="checkbox-item"> + @change="autoSave" />
+ class="form-group-large"> + @input="debouncedAutoSave" />
@@ -148,14 +126,12 @@
+ class="checkbox-item"> + @change="autoSave" />
@@ -168,14 +144,14 @@

4. Primary Resolution Approach

- + + class="mt-2" />
{{ validationErrors.approach }}
@@ -187,8 +163,7 @@ id="anonymous-reporting" label="Allow anonymous reporting" help="Members can report issues without revealing their identity" - @change="autoSave" - /> + @change="autoSave" />
@@ -200,36 +175,36 @@
+ class="form-group-large">
+ class="checkbox-item"> + @change="autoSave" />
-
+
{{ validationErrors.reportReceivers }}
- + + @change="autoSave" /> @@ -238,8 +213,7 @@ id="support-people" label="Allow support people in mediation sessions" help="Parties can bring a trusted person for emotional support" - @change="autoSave" - /> + @change="autoSave" />
@@ -250,7 +224,9 @@
- + + @change="autoSave" /> - + + @change="autoSave" />
+ class="form-group-large">
+ class="checkbox-item"> + @change="autoSave" />
-
+
{{ validationErrors.processSteps }}
@@ -302,7 +277,8 @@
-
+

7. Documentation & Privacy

+ }" />
@@ -324,30 +299,31 @@ placeholder="Select documentation level..." size="xl" class="w-full" - @change="autoSave" - /> + @change="autoSave" /> - + + @change="autoSave" /> - + + @change="autoSave" />
@@ -359,44 +335,42 @@
+ class="form-group-large">
+ class="checkbox-item"> + @change="autoSave" />
-
+
{{ validationErrors.availableActions }}
+ class="form-group-large border-t border-neutral-200 dark:border-neutral-800 pt-4"> + @change="autoSave" />
-
+

9. Special Circumstances

+ }" />
@@ -415,14 +388,12 @@
+ class="checkbox-item"> + @change="autoSave" />
@@ -433,19 +404,22 @@

10. Implementation Details

- + + @input="debouncedAutoSave" />
- + + @change="autoSave" /> + class="form-group-large"> + @change="autoSave" />
@@ -480,8 +451,7 @@ type="date" size="xl" class="w-full mb-0" - @change="autoSave" - /> + @change="autoSave" />
@@ -490,8 +460,7 @@ type="date" size="xl" class="w-full mb-0" - @change="autoSave" - /> + @change="autoSave" />
@@ -500,7 +469,8 @@
-
+

11. Reflection Process

+ }" />
- + + @change="autoSave" /> - + + @input="debouncedAutoSave" />
-
+

12. Direct Resolution Guidelines

+ }" />
+ class="form-group-large">
+ class="checkbox-item"> + @change="autoSave" />
+ class="form-group-large border-t border-neutral-200 dark:border-neutral-800 pt-4"> + @change="autoSave" /> + class="form-group-large border-t border-neutral-200 dark:border-neutral-800 pt-4"> + @change="autoSave" />
-

13. Responsible Contact People Structure

+

+ 13. Responsible Contact People Structure +

- + + @change="autoSave" /> + class="form-group-large"> + @input="debouncedAutoSave" /> + class="form-group-large"> + @change="autoSave" />
@@ -652,19 +615,19 @@

14. Formal Complaint Requirements

- +
+ class="checkbox-item"> + @change="autoSave" />
@@ -672,30 +635,26 @@
+ class="form-group-large"> + @change="autoSave" /> + class="form-group-large"> + @change="autoSave" />
@@ -705,8 +664,7 @@ id="require-external-advice" label="Require external legal advice for complex complaints" help="Seek external expertise for multi-party or staff/director complaints" - @change="autoSave" - /> + @change="autoSave" />
@@ -722,36 +680,31 @@ id="minutes-of-settlement" label="Require 'Minutes of Settlement' for resolved complaints" help="Agreements must be documented in writing and signed by both parties" - @change="autoSave" - /> + @change="autoSave" />
+ class="form-group-large"> + @change="autoSave" /> + class="form-group-large"> + @change="autoSave" />
@@ -759,7 +712,8 @@
-
+

16. External Resources & Redress

+ }" />
@@ -780,30 +733,31 @@ id="include-human-rights" label="Include Human Rights Commission information" help="Reference external discrimination complaint options" - @change="autoSave" - /> + @change="autoSave" /> - + + @input="debouncedAutoSave" /> - + + @input="debouncedAutoSave" />
@@ -811,81 +765,34 @@
+ class="border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-950 p-5 rounded-lg shadow-sm">

📄 Policy Document Preview

+ title="Hide preview"> ✕
-
-
- - -
-
- - {{ showPreview ? "👁️ Hide Preview" : "👁️ Show Preview" }} - - - - - - - - - Download Policy (Markdown) - - - Copy Text - - - Copied - + v-html="markdownToHtml(generatePolicyDocument())">
+ + +
+ +
@@ -963,7 +870,13 @@ const responseTimeOptions = [ "Within 2 weeks", ]; -const resolutionTimeOptions = ["1 week", "2 weeks", "30 days", "60 days", "90 days"]; +const resolutionTimeOptions = [ + "1 week", + "2 weeks", + "30 days", + "60 days", + "90 days", +]; const docLevelOptions = [ "Minimal - outcomes only", @@ -1024,7 +937,13 @@ const acknowledgmentTimeOptions = [ "Within 2 weeks", ]; -const reviewTimeOptions = ["2 weeks", "1 month", "6 weeks", "2 months", "3 months"]; +const reviewTimeOptions = [ + "2 weeks", + "1 month", + "6 weeks", + "2 months", + "3 months", +]; const sectionsEnabled = ref({ values: true, @@ -1198,14 +1117,18 @@ const validateForm = () => { } // Required checkbox groups (must have at least one checked) - const checkedConflictTypes = conflictTypes.value.filter((item) => item.checked); + const checkedConflictTypes = conflictTypes.value.filter( + (item) => item.checked + ); if (checkedConflictTypes.length === 0) { errors.conflictTypes = "Please select at least one type of conflict"; } // Note: Guiding Principles & Values section is optional - no validation needed - const checkedReportReceivers = reportReceivers.value.filter((item) => item.checked); + const checkedReportReceivers = reportReceivers.value.filter( + (item) => item.checked + ); if (checkedReportReceivers.length === 0) { errors.reportReceivers = "Please select at least one report receiver"; } @@ -1215,7 +1138,9 @@ const validateForm = () => { errors.processSteps = "Please select at least one process step"; } - const checkedAvailableActions = availableActions.value.filter((item) => item.checked); + const checkedAvailableActions = availableActions.value.filter( + (item) => item.checked + ); if (checkedAvailableActions.length === 0) { errors.availableActions = "Please select at least one available action"; } @@ -1263,8 +1188,9 @@ const completionPercentage = computed(() => { ...specialCircumstances.value, ]; - const filledInputs = allInputs.filter((val) => val && val.toString().trim() !== "") - .length; + const filledInputs = allInputs.filter( + (val) => val && val.toString().trim() !== "" + ).length; const checkedBoxes = checkboxInputs.filter((item) => item.checked).length; const totalFields = allInputs.length + checkboxInputs.length; @@ -1288,10 +1214,12 @@ const loadSavedData = () => { // Load checkbox arrays if (parsedData.coreValues) coreValues.value = parsedData.coreValues; - if (parsedData.conflictTypes) conflictTypes.value = parsedData.conflictTypes; + if (parsedData.conflictTypes) + conflictTypes.value = parsedData.conflictTypes; if (parsedData.reportReceivers) reportReceivers.value = parsedData.reportReceivers; - if (parsedData.processSteps) processSteps.value = parsedData.processSteps; + if (parsedData.processSteps) + processSteps.value = parsedData.processSteps; if (parsedData.availableActions) availableActions.value = parsedData.availableActions; if (parsedData.specialCircumstances) @@ -1423,711 +1351,30 @@ watch( { deep: true } ); -// Toggle section visibility -const toggleSection = (sectionName) => { - sectionsEnabled.value[sectionName] = !sectionsEnabled.value[sectionName]; - autoSave(); -}; +// Export data for the ExportOptions component +const exportData = computed(() => ({ + formData: formData.value, + orgName: formData.value.orgName || "Organization", + orgType: formData.value.orgType, + memberCount: formData.value.memberCount, + sectionsEnabled: sectionsEnabled.value, + coreValues: formData.value.coreValues, + principles: formData.value.principles, + policies: { + memberInvolvement: formData.value.memberInvolvement, + communicationGuidelines: formData.value.communicationGuidelines, + processSteps: formData.value.processSteps, + escalationCriteria: formData.value.escalationCriteria, + mediation: formData.value.mediation, + finalDecision: formData.value.finalDecision, + learning: formData.value.learning, + emergencyProcedures: formData.value.emergencyProcedures, + annualReview: formData.value.annualReview + }, + exportedAt: new Date().toISOString(), + section: "conflict-resolution-framework" +})); -// Export functions -const exportPDF = async () => { - if (process.server || typeof window === "undefined") { - console.warn("PDF export attempted on server side"); - return; - } - - try { - const orgName = formData.value.orgName || "organization"; - const filename = `${orgName.replace( - /[^a-zA-Z0-9]/g, - "_" - )}_conflict_resolution_policy.pdf`; - await exportToPDF(".document-page", filename); - } catch (error) { - console.error("PDF export failed:", error); - alert( - `PDF generation failed: ${ - error?.message || "Unknown error" - }\n\nFalling back to print dialog.` - ); - window.print(); - } -}; - -const handlePrint = () => { - window.print(); -}; - -// Removed exportText - focusing on Markdown as the primary text export format - -const exportMarkdown = () => { - const content = generateMarkdownContent(); - const orgName = formData.value.orgName || "organization"; - const filename = `${orgName.replace( - /[^a-zA-Z0-9]/g, - "_" - )}_conflict_resolution_policy.md`; - downloadFile(content, filename, "text/markdown"); -}; - -const copyPlainText = async () => { - const content = generatePlainTextContent(); - try { - await copyToClipboard(content); - copySuccess.value = true; - setTimeout(() => (copySuccess.value = false), 1500); - } catch (error) { - console.error("Copy to clipboard failed:", error); - } -}; - -const generatePlainTextContent = () => { - let text = generatePolicyDocument(); - - // Normalize line endings - text = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); - - // Convert links: [label](url) -> label (url) - text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)"); - - // Remove inline code backticks - text = text.replace(/`([^`]+)`/g, "$1"); - - // Remove emphasis markers while keeping content - text = text - .replace(/\*\*\*([^*]+)\*\*\*/g, "$1") - .replace(/\*\*([^*]+)\*\*/g, "$1") - .replace(/\*([^*]+)\*/g, "$1") - .replace(/___([^_]+)___/g, "$1") - .replace(/__([^_]+)__/g, "$1") - .replace(/_([^_]+)_/g, "$1"); - - // Headings: turn "# Heading" into "Heading" with a blank line after - text = text.replace(/^#{1,6}\s*(.+)\s*$/gm, (_m, heading) => `${heading}\n\n`); - - // Horizontal rules -> ensure blank line - text = text.replace(/^---\s*$/gm, "\n\n"); - - // Tables: strip separator rows and space-separate columns - text = text - .split("\n") - .map((line) => { - const trimmed = line.trim(); - if (/^\|.*\|$/.test(trimmed)) { - const cells = trimmed - .slice(1, -1) - .split("|") - .map((c) => c.trim()); - if (cells.every((c) => /^-+$/.test(c))) return ""; // separator row - return cells.join(" | "); - } - return line; - }) - .join("\n"); - - // Ensure blank line before major section keywords if stuck to previous line - text = text.replace(/([^\n])\n(\S)/g, (_m, a, b) => `${a}\n${b}`); - - // Collapse 3+ blank lines to exactly two - text = text.replace(/\n{3,}/g, "\n\n"); - - // Trim trailing spaces on each line - text = text.replace(/[ \t]+$/gm, ""); - - return text.trim() + "\n"; -}; - -const copyToClipboard = async (text) => { - if (process.server || typeof window === "undefined") { - throw new Error("Clipboard not available on server"); - } - if (navigator.clipboard && navigator.clipboard.writeText) { - await navigator.clipboard.writeText(text); - return; - } - const textarea = document.createElement("textarea"); - textarea.value = text; - textarea.style.position = "fixed"; - textarea.style.opacity = "0"; - document.body.appendChild(textarea); - textarea.focus(); - textarea.select(); - const successful = document.execCommand("copy"); - document.body.removeChild(textarea); - if (!successful) { - throw new Error("execCommand copy failed"); - } -}; - -const generatePolicyDocument = () => { - const orgName = formData.value.orgName || "[Organization Name]"; - const valuesText = generateValuesSection(); - const conflictTable = generateConflictTypesTable(); - const proceduresText = generateProceduresSection(); - const definitionsText = generateDefinitionsSection(); - - return `# ${orgName} Conflict Resolution Policy - -## Purpose - -Disagreements in groups are par for the course. But ignoring conflicts, or managing them poorly, can deeply harm individuals and our whole community. - -Addressing conflict head-on is ***a way of caring for each other***. - -Our policies and procedures around conduct and conflict are the tools we rely on to prevent harm and intervene early when things go wrong. - -This policy aims to offer a straightforward, consistently enforced, and transparent approach to resolving conflicts and disputes. These issues could emerge in relation to ${ - orgName.endsWith("s") ? orgName + "'" : orgName + "'s" - } programs, governance, or the actions of its staff${ - formData.value.orgType === "Worker Cooperative" || - formData.value.orgType === "Consumer Cooperative" - ? ", members," - : "" - } or directors. - -## Who does this policy apply to? - -Staff${ - formData.value.orgType === "Worker Cooperative" || - formData.value.orgType === "Consumer Cooperative" - ? ", members," - : "" - } and community members must comply with the ${orgName} Conflict Resolution Policy and related by-laws and policies as a condition of ${ - formData.value.orgType === "Worker Cooperative" - ? "membership, employment," - : "employment," - } or participation. Failure to cooperate may result in termination${ - formData.value.orgType === "Worker Cooperative" || - formData.value.orgType === "Consumer Cooperative" - ? " or removal from membership" - : "" - }. - -## What policy should be used? - -${conflictTable} - -${sectionsEnabled.value.values ? valuesText : ""} - -${definitionsText} - -## Responsibility for implementation - -This policy acknowledges and respects the governance structure of ${orgName}, which states that the ${getMediatorStructureText()} ${ - getMediatorStructureText().includes("mediators") ? "are" : "is" - } responsible for conflict resolution operations and activities, while the ${ - formData.value.orgType === "Worker Cooperative" - ? "Board of Directors" - : "governing body" - } is responsible for matters related to policy, decisions, activities, and governance. - -${proceduresText} - ---- - -*This policy was created on ${ - formData.value.createdDate || "[Date]" - } and is scheduled for review ${ - formData.value.reviewSchedule?.toLowerCase() || "as needed" - }.* - -*Next review date: ${formData.value.reviewDate || "[To be scheduled]"}*`; -}; - -const getApproachDescription = (approach) => { - const approaches = { - restorative: "restorative/loving justice", - mediation: "mediation-first", - progressive: "progressive discipline", - hybrid: "hybrid", - }; - return approaches[approach] || approach; -}; - -const generateValuesSection = () => { - const selectedValues = coreValues.value.filter((v) => v.checked); - const customValues = formData.value.customValues?.trim(); - - if (selectedValues.length === 0 && !customValues) { - return ""; - } - - let valuesText = "## Guiding principles\n\n"; - - if (selectedValues.length > 0) { - valuesText += `Our core values of ${selectedValues - .map((v) => v.label) - .join(", ")} apply in every interaction.\n\n`; - } - - if (customValues) { - valuesText += `\n${customValues}\n\n`; - } - - valuesText += `- Our skills and resources will be developed and used to resolve conflicts in a way that is ${getApproachDescription( - formData.value.approach - )}–based whenever possible.\n`; - valuesText += - "- All parties to a complaint will *actively participate* and strive to achieve a *collaborative* outcome at the earliest possible stage of the process.\n"; - valuesText += `- Information about a complaint will only be given to parties directly involved, ${getMediatorStructureText()}, and others on a need-to-know basis as determined by ${ - formData.value.mediatorType === "Standing committee" - ? "the committee" - : "the mediator" - }.\n`; - valuesText += - "- The parties will be provided with clear and understandable reasons for complaint decisions. All parties will be provided with updates during the review process.\n"; - valuesText += `- Complaints will be dealt with promptly and resolved as quickly as possible (target: ${ - formData.value.resolutionTarget || "[time not specified]" - }).\n`; - valuesText += - "- Review of complaints will be fair, impartial, and respectful, allowing all parties to have their perspectives heard.\n"; - valuesText += - "- The review of complaints will be thorough and as detailed as possible based on the information provided by the parties.\n"; - valuesText += - "- The process will be accessible and clearly communicated to members.\n\n"; - - const orgPossessive = - formData.value.orgName && formData.value.orgName.endsWith("s") - ? formData.value.orgName + "'" - : formData.value.orgName + "'s"; - - if (formData.value.anonymousReporting) { - valuesText += `This process is ${orgPossessive} responsibility. Members have the right to request a mediator if required, and anonymous reporting is supported.\n\n`; - } else { - valuesText += `This process is ${orgPossessive} responsibility. Members have the right to request a mediator if required.\n\n`; - } - - return valuesText; -}; - -const generateConflictTypesTable = () => { - const selectedConflicts = conflictTypes.value.filter((v) => v.checked); - - let table = - "| **Who Can File** | **Type of Complaint** | **Policy Reference** | **Additional Notes** |\n"; - table += "| --- | --- | --- | --- |\n"; - - const whoCanFile = getWhoCanFile(); - - selectedConflicts.forEach((conflict) => { - const policyRef = getConflictPolicyReference(conflict.label); - const notes = getConflictNotes(conflict.label); - table += `| ${whoCanFile} | ${conflict.label} | ${policyRef} | ${notes} |\n`; - }); - - return table; -}; - -const getWhoCanFile = () => { - const orgType = formData.value.orgType; - if (orgType === "Worker Cooperative" || orgType === "Consumer Cooperative") { - return "Directors, staff, members"; - } - return "Directors, staff, community members"; -}; - -const getConflictPolicyReference = (conflictType) => { - const references = { - "Interpersonal disputes between members": "This policy", - "Code of Conduct violations": "[[Code of Conduct]]", - "Harassment or discrimination": "[[Code of Conduct]]", - "Work performance issues": "[[HR Policy]]", - "Conflicts of interest": "[[Conflict of Interest Policy]]", - "External organization disputes": "Per agreement dispute clause", - "Financial disagreements": "This policy", - }; - return references[conflictType] || "This policy"; -}; - -const getConflictNotes = (conflictType) => { - const notes = { - "Harassment or discrimination": "May also pursue through Human Rights Tribunal", - "Work performance issues": "Directly through HR procedures", - "Conflicts of interest": "Addressed alongside the organization's specific policy", - "External organization disputes": - "Resolved per agreement's dispute resolution clause", - }; - return notes[conflictType] || "-"; -}; - -const getMediatorStructureText = () => { - const mediatorType = formData.value.mediatorType; - const typeMap = { - "Internal trained mediators": "trained internal mediators", - "External professional mediators": "external mediators", - "Rotating member facilitators": "rotating member facilitators", - "Standing committee": "Conflict Resolution Committee", - "Decided case-by-case": "designated mediators", - }; - return typeMap[mediatorType] || mediatorType; -}; - -const generateDefinitionsSection = () => { - let definitions = "## Definitions\n\n"; - - definitions += - "- **Conflict** and **dispute** are ongoing experiences of tension and misunderstandings, often leading to interpersonal discord. These terms are used interchangeably.\n"; - - if (formData.value.mediatorType === "Standing committee") { - definitions += `- The **Conflict Resolution Committee** is a ${ - formData.value.orgType === "Worker Cooperative" - ? "standing committee of the Board" - : "designated committee" - } to which unresolved formal complaints are sent for review and recommendations.\n`; - } - - definitions += - "- A **complainant** is the individual lodging a complaint against another related individual, policy, or practice.\n"; - - if (formData.value.mediatorType !== "External professional mediators") { - definitions += `- The **Internal Advisor** is the ${ - formData.value.orgType === "Worker Cooperative" - ? "Board-appointed" - : "organization-appointed" - } mediator who facilitates the conflict resolution process. They work to achieve a satisfactory solution by acting as an intermediary and convening authority.\n`; - } - - definitions += - "- A **respondent** is an individual against whom a complaint has been made, and/or someone responsible for the policy or activity complained about.\n"; - - const selectedReceivers = reportReceivers.value.filter((v) => v.checked); - if (selectedReceivers.length > 0) { - definitions += `- **Responsible contact people (RCP)** are those who are accountable for assisting in conflict resolution and addressing formal complaints. In our organization, this includes: ${selectedReceivers - .map((r) => r.label.toLowerCase()) - .join(", ")}. They do not act as advocates for any party in the conflict.\n`; - } - - if (formData.value.supportPeople) { - definitions += - "- **Support people** are individuals not connected to the conflicts or disputes being addressed, which either the complainant or respondent may choose to have in attendance at mediation meetings.\n"; - } - - return definitions + "\n"; -}; - -const generateProceduresSection = () => { - const selectedActions = availableActions.value.filter((v) => v.checked); - - let procedures = "## Procedures\n\n"; - procedures += - "We do our best to resolve conflicts at the lowest possible escalation step (direct resolution), but agree to escalate conflicts (to assisted resolution) if they are not resolved.\n\n"; - - // Reflection Section - if (sectionsEnabled.value.reflection) { - procedures += generateReflectionSection(); - } - - // Direct Resolution Section - procedures += generateDirectResolutionSection(); - - // Assisted Resolution Section - procedures += generateAssistedResolutionSection(); - - // Formal Complaints Section - procedures += generateFormalComplaintsSection(); - - if (selectedActions.length > 0) { - procedures += "### Available Actions and Consequences\n\n"; - procedures += - "Depending on the nature and severity of the conflict, the following actions may be taken:\n\n"; - selectedActions.forEach((action) => { - procedures += `- ${action.label}\n`; - }); - procedures += "\n"; - } - - if (formData.value.appealProcess) { - procedures += "### Appeals Process\n\n"; - procedures += - "Parties may request a review of decisions made through this conflict resolution process. Appeals must be submitted in writing within 30 days of the decision.\n\n"; - } - - // Settlement Documentation - if (formData.value.requireMinutesOfSettlement) { - procedures += generateSettlementSection(); - } - - const selectedCircumstances = specialCircumstances.value.filter((v) => v.checked); - if (sectionsEnabled.value.special && selectedCircumstances.length > 0) { - procedures += "### Special Circumstances\n\n"; - selectedCircumstances.forEach((circumstance) => { - procedures += `- ${circumstance.label}\n`; - }); - procedures += "\n"; - } - - // External Resources - if (sectionsEnabled.value.externalResources) { - procedures += generateExternalResourcesSection(); - } - - return procedures; -}; - -const generateReflectionSection = () => { - let section = "### Reflection\n\n"; - section += `Before escalating a conflict (timeframe: ${ - formData.value.reflectionPeriod?.toLowerCase() || "as needed" - }), we encourage the following reflection process:\n\n`; - - section += - "1. Set aside some time to think through what happened. What was the other person's behaviour? How did it affect you? *Distinguish other people's **actions** from your **feelings** about them.*\n"; - section += "2. Consider what uncertainties or misunderstandings may have occurred.\n"; - section += - "3. Distinguish disagreement from personal hostility. Disagreement and dissent are part of healthy discussion. Hostility is not.\n"; - section += - "4. Use your personal support system (friends, family, therapist, etc.) to work through and clarify your perspective.\n"; - section += - "5. Ask yourself what part you played, how you could have behaved differently, and what your needs are.\n\n"; - - if (formData.value.customReflectionPrompts?.trim()) { - section += "#### Additional Reflection Questions\n\n"; - section += formData.value.customReflectionPrompts + "\n\n"; - } - - return section; -}; - -const generateDirectResolutionSection = () => { - let section = "## Direct Resolution\n\n"; - section += - "A *direct resolution* process occurs when individuals communicate their concerns and work together to resolve disputes without filing an informal or formal complaint.\n\n"; - - if (formData.value.requireDirectAttempt) { - section += - "**Note: Direct resolution attempts are required before escalation in most cases.**\n\n"; - } - - section += "### Have a Conversation\n\n"; - section += - "When there is a disagreement, the involved people should first **communicate with each other** about their concerns.\n\n"; - - section += - "1. Choose a time and place to meet that is private and agreeable to both.\n"; - section += "2. Allow a reasonable amount of time.\n"; - section += - "3. The point of the meeting is not to determine who is right or wrong, but rather to reach **a mutual understanding**. Achieving this requires patience and a willingness to listen to the other person's perspective without immediately dismissing it as incorrect.\n"; - section += - '4. Express your thoughts and feelings directly and without belittling or dismissing the other person\'s perspective. Use "I" statements and active listening techniques.\n'; - section += - "5. Communicate your **wants** and **needs** and make **offers** and **requests**.\n"; - section += - "6. During the conversation, try your best to learn how to avoid miscommunication and misunderstandings in the future.\n"; - - if (formData.value.documentDirectResolution) { - section += - "7. Keep a written record of the resolution of this conversation, agreed to by both parties.\n\n"; - } else { - section += "\n"; - } - - // Escalating Bandwidth - const selectedChannels = communicationChannels.value.filter((c) => c.checked); - if (sectionsEnabled.value.directResolution && selectedChannels.length > 0) { - section += "#### Escalating Bandwidth\n\n"; - section += - "Whenever a misunderstanding or conflict arises, **escalate the bandwidth of the channel**:\n\n"; - selectedChannels.forEach((channel, index) => { - section += `${index + 1}. ${channel.label}\n`; - }); - section += - "\nMove from text to voice, voice to video, and ultimately to in-person when possible.\n\n"; - } - - return section; -}; - -const generateAssistedResolutionSection = () => { - let section = "## Assisted Resolution\n\n"; - section += "### Informal Complaints\n\n"; - section += - "If talking things out doesn't work, you can ask a responsible contact person for help in writing. Mention that you're making an *informal complaint* and seeking assistance.\n\n"; - - section += generateResponsibleContactTable(); - - const responseTime = - formData.value.initialResponse?.toLowerCase() || "a reasonable timeframe"; - section += `When someone makes an informal complaint, the responsible contact person (RCP) will ask if they have tried to resolve the issue themselves and will talk to the Internal Advisor if necessary. Within ${ - responseTime.startsWith("within") ? responseTime.substring(6).trim() : responseTime - } of the complaint being made, the person in charge will work to settle the matter through the informal complaint resolution process.\n\n`; - - section += "#### Procedure\n\n"; - section += - "The RCP will speak with each person separately to hear their perspectives and to review the direct resolution process.\n\n"; - section += - "The parties will be invited to attend informal meetings, negotiations, facilitated discussions, or mediation. These invitations will be extended by either the RCP, the Conflict Resolution Committee, or the Internal Advisor.\n\n"; - section += `Depending on their training and neutrality, either the RCP or the ${ - formData.value.internalAdvisorType?.toLowerCase() || "Internal Advisor" - } will act as a facilitator or mediator in the selected process.\n\n`; - section += - "If the chosen process results in an acceptable informal outcome for both parties, the matter will be considered resolved.\n\n"; - - return section; -}; - -const generateFormalComplaintsSection = () => { - let section = "### Formal Complaints\n\n"; - section += `If informal resolution efforts do not result in an acceptable outcome within ${ - formData.value.formalReviewTime?.toLowerCase() || "a reasonable timeframe" - } or to the satisfaction of the complainant, they may file a *formal complaint* in writing.\n\n`; - - const selectedElements = formalComplaintElements.value.filter((e) => e.checked); - if (selectedElements.length > 0) { - section += "The written complaint must include:\n\n"; - selectedElements.forEach((element, index) => { - section += `${index + 1}. ${element.label}\n`; - }); - section += "\n"; - } - - section += "It should be submitted to the appropriate RCP.\n\n"; - if (formData.value.requireExternalAdvice) { - section += - "*Seeking external advice and expertise to aid this process is recommended for complex complaints.*\n\n"; - } - - section += generateResponsibleContactTable("formal"); - - const ackTime = formData.value.formalAcknowledgmentTime?.toLowerCase() || "one week"; - section += `The RCP will acknowledge receipt of the complaint within ${ - ackTime.startsWith("within") ? ackTime.substring(6).trim() : ackTime - } and forward it to the Internal Advisor (if not self). The Internal Advisor will then proceed to:\n\n`; - - section += - "1. Review the complaint to ensure all information is included and that enough information is present to assess the situation and respond.\n"; - section += - "2. Assess and make note of organizational by-laws, policies and codes that might have been violated.\n"; - section += "3. If needed, seek advice from external sources of expertise.\n\n"; - - if (formData.value.requireExternalAdvice) { - section += - "> **Tip:** If the complaint involves multiple complainants or respondents who are staff and/or Directors, seek external legal advice.\n\n"; - } - - return section; -}; - -const generateResponsibleContactTable = (type = "informal") => { - const orgType = formData.value.orgType; - const isCooperative = - orgType === "Worker Cooperative" || orgType === "Consumer Cooperative"; - - let table = `#### Responsible Contact People (${type} complaints)\n\n`; - table += - "| Complainant | First Contact | If First Contact Is the Respondent | Additional Steps |\n"; - table += "| --- | --- | --- | --- |\n"; - - // Staff row - const staffLiaison = - formData.value.staffLiaison || "staff liaison on the Conflict Resolution Committee"; - table += `| **Staff** | Immediate supervisor | ${ - isCooperative ? "One or both Co-EDs" : "Executive Director" - } | If supervisor is ED and the Respondent, contact ${staffLiaison} |\n`; - - // ED/Leadership row - if (isCooperative) { - table += `| **Executive Director (ED)** | ${ - formData.value.boardChairRole?.includes("Chair") - ? "Chair of the Board of Directors" - : "Board Chair" - } | ${staffLiaison} | If Respondents involve multiple directors, contact the Internal Advisor |\n`; - } else { - table += `| **Executive Director** | ${ - formData.value.boardChairRole?.includes("Chair") - ? "Chair of the Board" - : "Board Chair" - } | ${staffLiaison} | If involving multiple directors, contact the Internal Advisor |\n`; - } - - // Director row - table += `| **Director** | ${ - isCooperative ? "One or both Co-EDs" : "Executive Director" - } | Internal Advisor | - |\n`; - - // Member/Public row - const memberType = isCooperative ? "Member of the organization" : "Community member"; - table += `| **${memberType}, or member of the public** | Designated staff | ${ - isCooperative ? "ED" : "Executive Director" - } | If the Respondent is the ED, then contact the Internal Advisor |\n\n`; - - return table; -}; - -const generateSettlementSection = () => { - let section = "#### Reaching an Agreement\n\n"; - section += - 'Any resolution of a complaint that is agreed upon through direct negotiation must be documented in writing and signed by both the complainant and respondent. These "Minutes of Settlement" will be kept confidential and only shared with the Internal Advisor, Staff, Board, legal counsel, or other parties who need to know to fulfill their organizational duties.\n\n'; - - section += "Considerations when agreeing should include:\n\n"; - section += - "1. Is the agreement within the scope of the parties' decision-making powers in relation to their organizational role?\n"; - section += "2. Is the agreement realistic and durable?\n"; - section += "3. Does the agreement in any way compromise the organization?\n"; - section += - "4. Are there elements of the agreement that impact the organization's operations, policies, reputation, external relationships or public perceptions?\n"; - section += `5. Does it align with our values and commitment to ${getApproachDescription( - formData.value.approach - )}?\n\n`; - - section += `**Confidentiality Level**: ${ - formData.value.settlementConfidentiality || "Need-to-know basis" - }\n`; - section += `**File Retention**: ${ - formData.value.conflictFileRetention || "5 years" - }\n\n`; - - return section; -}; - -const generateExternalResourcesSection = () => { - if (!sectionsEnabled.value.externalResources) return ""; - - let section = "## Other Redress\n\n"; - - if (formData.value.includeHumanRights) { - section += - "An individual who is not satisfied with the outcome of a **harassment** complaint process may file a discrimination complaint with the [Canadian Human Rights Commission](https://www.chrc-ccdp.gc.ca/eng).\n\n"; - } - - if (formData.value.additionalResources?.trim()) { - section += "### Additional Resources\n\n"; - section += formData.value.additionalResources + "\n\n"; - } - - if (formData.value.acknowledgments?.trim()) { - section += "## Acknowledgments\n\n"; - section += formData.value.acknowledgments + "\n\n"; - } else { - section += "## Acknowledgments\n\n"; - section += - "This work is inspired in part by the work of the Media Arts Network of Ontario led by Sheila Wilmot, January 2016.\n\n"; - } - - return section; -}; - -// Removed generateTextContent - using generatePolicyDocument directly - -const generateMarkdownContent = () => { - return generatePolicyDocument(); -}; - -const downloadFile = (content, filename, mimeType) => { - const blob = new Blob([content], { type: mimeType }); - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.download = filename; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); -}; - -// Initialize -onMounted(() => { - loadSavedData(); -}); + \ No newline at end of file diff --git a/pages/templates/tech-charter.vue b/pages/templates/tech-charter.vue index 3bc0e3d..3101e0d 100644 --- a/pages/templates/tech-charter.vue +++ b/pages/templates/tech-charter.vue @@ -3,49 +3,17 @@ + +
+ +
+
- -
-
-
- - - Copy Text - - - Copied - - - - Markdown - -
-
-
-
@@ -59,52 +27,72 @@
- +
-

- Define Your Principles +

+ Charter Purpose

+

+ Describe what this charter will guide and why it matters to + your group. +

-
- - Select your technology principles - -
+
+